Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow zero bins in tally triggers #2928

Merged
merged 14 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/source/io_formats/tallies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ The ``<tally>`` element accepts the following sub-elements:

*Default*: None

:allow_zero:
Whether to allow zero tally bins to be ignored when assessing the
convergece of the precision trigger. If True, only nonzero tally scores
will be compared to the trigger's threshold.

*Default*: False

:scores:
The score(s) in this tally to which the trigger should be applied.

Expand Down
1 change: 1 addition & 0 deletions include/openmc/tallies/trigger.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ enum class TriggerMetric {
struct Trigger {
TriggerMetric metric; //!< The type of uncertainty (e.g. std dev) measured
double threshold; //!< Uncertainty value below which trigger is satisfied
bool allow_zero; //!< Whether to allow zero tally bins to be ignored
int score_index; //!< Index of the relevant score in the tally's arrays
};

Expand Down
29 changes: 27 additions & 2 deletions openmc/trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ class Trigger(EqualityMixin):
relative error of scores.
threshold : float
The threshold for the trigger type.
allow_zero : bool
Whether to allow zero tally bins to be ignored.

.. versionadded:: 0.14.1

Attributes
----------
Expand All @@ -27,18 +31,22 @@ class Trigger(EqualityMixin):
The threshold for the trigger type.
scores : list of str
Scores which should be checked against the trigger
allow_zero : bool
Whether to allow zero tally bins to be ignored.

"""

def __init__(self, trigger_type: str, threshold: float):
def __init__(self, trigger_type: str, threshold: float, allow_zero: bool=False):
self.trigger_type = trigger_type
self.threshold = threshold
self.allow_zero = allow_zero
self._scores = []

def __repr__(self):
string = 'Trigger\n'
string += '{: <16}=\t{}\n'.format('\tType', self._trigger_type)
string += '{: <16}=\t{}\n'.format('\tThreshold', self._threshold)
string += '{: <16}=\t{}\n'.format('\tAllow Zero', self._allow_zero)
string += '{: <16}=\t{}\n'.format('\tScores', self._scores)
return string

Expand All @@ -60,6 +68,15 @@ def threshold(self):
def threshold(self, threshold):
cv.check_type('tally trigger threshold', threshold, Real)
self._threshold = threshold

@property
def allow_zero(self):
return self._allow_zero

@allow_zero.setter
def allow_zero(self, allow_zero):
cv.check_type('tally trigger allows zeroes', allow_zero, bool)
self._allow_zero = allow_zero

@property
def scores(self):
Expand Down Expand Up @@ -88,6 +105,8 @@ def to_xml_element(self):
element = ET.Element("trigger")
element.set("type", self._trigger_type)
element.set("threshold", str(self._threshold))
if self._allow_zero:
element.set("allow_zero", "true")
if len(self._scores) != 0:
element.set("scores", ' '.join(self._scores))
return element
Expand All @@ -110,7 +129,13 @@ def from_xml_element(cls, elem: ET.Element):
# Generate trigger object
trigger_type = elem.get("type")
threshold = float(elem.get("threshold"))
trigger = cls(trigger_type, threshold)
allow_zero = str(elem.get("allow_zero", "false")).lower()
# Try to convert to bool. Let Trigger error out on instantiation.
if allow_zero in ("true", "1"):
allow_zero = True
elif allow_zero in ("false", "0"):
allow_zero = False
trigger = cls(trigger_type, threshold, allow_zero)

# Add scores if present
scores = elem.get("scores")
Expand Down
10 changes: 8 additions & 2 deletions src/tallies/tally.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,12 @@ void Tally::init_triggers(pugi::xml_node node)
"Must specify trigger threshold for tally {} in tally XML file", id_));
}

// Read whether to allow zero-tally bins to be ignored.
bool allow_zero = false;
if (check_for_node(trigger_node, "allow_zero")) {
allow_zero = get_node_value_bool(trigger_node, "allow_zero");
}

// Read the trigger scores.
vector<std::string> trigger_scores;
if (check_for_node(trigger_node, "scores")) {
Expand All @@ -702,7 +708,7 @@ void Tally::init_triggers(pugi::xml_node node)
if (score_str == "all") {
triggers_.reserve(triggers_.size() + this->scores_.size());
for (auto i_score = 0; i_score < this->scores_.size(); ++i_score) {
triggers_.push_back({metric, threshold, i_score});
triggers_.push_back({metric, threshold, allow_zero, i_score});
}
} else {
int i_score = 0;
Expand All @@ -716,7 +722,7 @@ void Tally::init_triggers(pugi::xml_node node)
"{} but it was listed in a trigger on that tally",
score_str, id_));
}
triggers_.push_back({metric, threshold, i_score});
triggers_.push_back({metric, threshold, allow_zero, i_score});
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/tallies/trigger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ void check_tally_triggers(double& ratio, int& tally_id, int& score)
auto uncert_pair =
get_tally_uncertainty(i_tally, trigger.score_index, filter_index);

// if there is a score without contributions, set ratio to inf and
// exit early
if (uncert_pair.first == -1) {
// If there is a score without contributions, set ratio to inf and
// exit early, unless zero score is allowed for this trigger.
if (uncert_pair.first == -1 && !trigger.allow_zero) {
ratio = INFINITY;
score = t.scores_[trigger.score_index];
tally_id = t.id_;
Expand Down Expand Up @@ -167,7 +167,7 @@ void check_triggers()
// See if the current batch is one for which the triggers must be checked.
if (!settings::trigger_on)
return;
if (current_batch < n_batches)
if (current_batch <= n_batches)
return;
if (((current_batch - n_batches) % interval) != 0)
return;
Expand Down
40 changes: 40 additions & 0 deletions tests/unit_tests/test_triggers.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,43 @@ def test_tally_trigger_null_score(run_in_tmpdir):
total_batches = sp.n_realizations + sp.n_inactive
assert total_batches == pincell.settings.trigger_max_batches


def test_tally_trigger_zero_allowed(run_in_tmpdir):
pincell = openmc.examples.pwr_pin_cell()

# create an energy filter below and around the O-16(n,p) threshold
e_filter = openmc.EnergyFilter([0.0, 1e7, 2e7])

# create a tally with triggers applied
tally = openmc.Tally()
tally.filters = [e_filter]
tally.scores = ['(n,p)']
tally.nuclides = ["O16"]

# 100% relative error: should be immediately satisfied in nonzero bin
trigger = openmc.Trigger('rel_err', 1.0)
trigger.scores = ['(n,p)']
trigger.allow_zero = True

tally.triggers = [trigger]

pincell.tallies = [tally]

pincell.settings.particles = 1000 # we need a few more particles for this
pincell.settings.trigger_active = True
pincell.settings.trigger_max_batches = 50
pincell.settings.trigger_batch_interval = 20

sp_file = pincell.run()

with openmc.StatePoint(sp_file) as sp:
# verify that the first bin is zero and the second is nonzero
tally_out = sp.get_tally(id=tally.id)
below, above = tally_out.mean.squeeze()
assert below == 0.0, "Tally events observed below expected threshold"
assert above > 0, "No tally events observed. Test with more particles."

# we expect that the trigger fires before max batches are hit
total_batches = sp.n_realizations + sp.n_inactive
assert total_batches < pincell.settings.trigger_max_batches