Skip to content

Commit

Permalink
Allow zero bins in tally triggers (openmc-dev#2928)
Browse files Browse the repository at this point in the history
Co-authored-by: Paul Romano <[email protected]>
  • Loading branch information
2 people authored and church89 committed Jul 18, 2024
1 parent 4489744 commit fe5d1f0
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 7 deletions.
12 changes: 12 additions & 0 deletions docs/source/io_formats/tallies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,18 @@ The ``<tally>`` element accepts the following sub-elements:

*Default*: None

:ignore_zeros:
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.

.. note:: The ``ignore_zeros`` option can cause the tally trigger to fire
prematurely if there are no hits in any bins at the first
evalulation. It is the user's responsibility to specify enough
particles per batch to get a nonzero score in at least one bin.

*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 ignore_zeros; //!< Whether to allow zero tally bins to be ignored
int score_index; //!< Index of the relevant score in the tally's arrays
};

Expand Down
28 changes: 26 additions & 2 deletions openmc/trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ class Trigger(EqualityMixin):
relative error of scores.
threshold : float
The threshold for the trigger type.
ignore_zeros : bool
Whether to allow zero tally bins to be ignored. Note that this option
can cause the trigger to fire prematurely if there are zero scores in
any bin at the first evaluation.
.. versionadded:: 0.14.1
Attributes
----------
Expand All @@ -27,18 +33,22 @@ class Trigger(EqualityMixin):
The threshold for the trigger type.
scores : list of str
Scores which should be checked against the trigger
ignore_zeros : 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, ignore_zeros: bool = False):
self.trigger_type = trigger_type
self.threshold = threshold
self.ignore_zeros = ignore_zeros
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('\tIgnore Zeros', self._ignore_zeros)
string += '{: <16}=\t{}\n'.format('\tScores', self._scores)
return string

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

@property
def ignore_zeros(self):
return self._ignore_zeros

@ignore_zeros.setter
def ignore_zeros(self, ignore_zeros):
cv.check_type('tally trigger ignores zeros', ignore_zeros, bool)
self._ignore_zeros = ignore_zeros

@property
def scores(self):
return self._scores
Expand Down Expand Up @@ -88,6 +107,8 @@ def to_xml_element(self):
element = ET.Element("trigger")
element.set("type", self._trigger_type)
element.set("threshold", str(self._threshold))
if self._ignore_zeros:
element.set("ignore_zeros", "true")
if len(self._scores) != 0:
element.set("scores", ' '.join(self._scores))
return element
Expand All @@ -110,7 +131,10 @@ 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)
ignore_zeros = str(elem.get("ignore_zeros", "false")).lower()
# Try to convert to bool. Let Trigger error out on instantiation.
ignore_zeros = ignore_zeros in ('true', '1')
trigger = cls(trigger_type, threshold, ignore_zeros)

# 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 @@ -690,6 +690,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 ignore_zeros = false;
if (check_for_node(trigger_node, "ignore_zeros")) {
ignore_zeros = get_node_value_bool(trigger_node, "ignore_zeros");
}

// Read the trigger scores.
vector<std::string> trigger_scores;
if (check_for_node(trigger_node, "scores")) {
Expand All @@ -703,7 +709,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, ignore_zeros, i_score});
}
} else {
int i_score = 0;
Expand All @@ -717,7 +723,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, ignore_zeros, i_score});
}
}
}
Expand Down
6 changes: 3 additions & 3 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 scores are ignored for this trigger.
if (uncert_pair.first == -1 && !trigger.ignore_zeros) {
ratio = INFINITY;
score = t.scores_[trigger.score_index];
tally_id = t.id_;
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_ignored(run_in_tmpdir):
pincell = openmc.examples.pwr_pin_cell()

# create an energy filter below and around the O-16(n,p) threshold (1.02e7 eV)
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.ignore_zeros = 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

0 comments on commit fe5d1f0

Please sign in to comment.