Skip to content

Commit c976653

Browse files
tjlabosspaulromano
andauthored
Allow zero bins in tally triggers (#2928)
Co-authored-by: Paul Romano <paul.k.romano@gmail.com>
1 parent d1366c0 commit c976653

6 files changed

Lines changed: 90 additions & 7 deletions

File tree

‎docs/source/io_formats/tallies.rst‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,18 @@ The ``<tally>`` element accepts the following sub-elements:
100100

101101
*Default*: None
102102

103+
:ignore_zeros:
104+
Whether to allow zero tally bins to be ignored when assessing the
105+
convergece of the precision trigger. If True, only nonzero tally scores
106+
will be compared to the trigger's threshold.
107+
108+
.. note:: The ``ignore_zeros`` option can cause the tally trigger to fire
109+
prematurely if there are no hits in any bins at the first
110+
evalulation. It is the user's responsibility to specify enough
111+
particles per batch to get a nonzero score in at least one bin.
112+
113+
*Default*: False
114+
103115
:scores:
104116
The score(s) in this tally to which the trigger should be applied.
105117

‎include/openmc/tallies/trigger.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ enum class TriggerMetric {
2323
struct Trigger {
2424
TriggerMetric metric; //!< The type of uncertainty (e.g. std dev) measured
2525
double threshold; //!< Uncertainty value below which trigger is satisfied
26+
bool ignore_zeros; //!< Whether to allow zero tally bins to be ignored
2627
int score_index; //!< Index of the relevant score in the tally's arrays
2728
};
2829

‎openmc/trigger.py‎

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ class Trigger(EqualityMixin):
1717
relative error of scores.
1818
threshold : float
1919
The threshold for the trigger type.
20+
ignore_zeros : bool
21+
Whether to allow zero tally bins to be ignored. Note that this option
22+
can cause the trigger to fire prematurely if there are zero scores in
23+
any bin at the first evaluation.
24+
25+
.. versionadded:: 0.14.1
2026
2127
Attributes
2228
----------
@@ -27,18 +33,22 @@ class Trigger(EqualityMixin):
2733
The threshold for the trigger type.
2834
scores : list of str
2935
Scores which should be checked against the trigger
36+
ignore_zeros : bool
37+
Whether to allow zero tally bins to be ignored.
3038
3139
"""
3240

33-
def __init__(self, trigger_type: str, threshold: float):
41+
def __init__(self, trigger_type: str, threshold: float, ignore_zeros: bool = False):
3442
self.trigger_type = trigger_type
3543
self.threshold = threshold
44+
self.ignore_zeros = ignore_zeros
3645
self._scores = []
3746

3847
def __repr__(self):
3948
string = 'Trigger\n'
4049
string += '{: <16}=\t{}\n'.format('\tType', self._trigger_type)
4150
string += '{: <16}=\t{}\n'.format('\tThreshold', self._threshold)
51+
string += '{: <16}=\t{}\n'.format('\tIgnore Zeros', self._ignore_zeros)
4252
string += '{: <16}=\t{}\n'.format('\tScores', self._scores)
4353
return string
4454

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

74+
@property
75+
def ignore_zeros(self):
76+
return self._ignore_zeros
77+
78+
@ignore_zeros.setter
79+
def ignore_zeros(self, ignore_zeros):
80+
cv.check_type('tally trigger ignores zeros', ignore_zeros, bool)
81+
self._ignore_zeros = ignore_zeros
82+
6483
@property
6584
def scores(self):
6685
return self._scores
@@ -88,6 +107,8 @@ def to_xml_element(self):
88107
element = ET.Element("trigger")
89108
element.set("type", self._trigger_type)
90109
element.set("threshold", str(self._threshold))
110+
if self._ignore_zeros:
111+
element.set("ignore_zeros", "true")
91112
if len(self._scores) != 0:
92113
element.set("scores", ' '.join(self._scores))
93114
return element
@@ -110,7 +131,10 @@ def from_xml_element(cls, elem: ET.Element):
110131
# Generate trigger object
111132
trigger_type = elem.get("type")
112133
threshold = float(elem.get("threshold"))
113-
trigger = cls(trigger_type, threshold)
134+
ignore_zeros = str(elem.get("ignore_zeros", "false")).lower()
135+
# Try to convert to bool. Let Trigger error out on instantiation.
136+
ignore_zeros = ignore_zeros in ('true', '1')
137+
trigger = cls(trigger_type, threshold, ignore_zeros)
114138

115139
# Add scores if present
116140
scores = elem.get("scores")

‎src/tallies/tally.cpp‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,12 @@ void Tally::init_triggers(pugi::xml_node node)
690690
"Must specify trigger threshold for tally {} in tally XML file", id_));
691691
}
692692

693+
// Read whether to allow zero-tally bins to be ignored.
694+
bool ignore_zeros = false;
695+
if (check_for_node(trigger_node, "ignore_zeros")) {
696+
ignore_zeros = get_node_value_bool(trigger_node, "ignore_zeros");
697+
}
698+
693699
// Read the trigger scores.
694700
vector<std::string> trigger_scores;
695701
if (check_for_node(trigger_node, "scores")) {
@@ -703,7 +709,7 @@ void Tally::init_triggers(pugi::xml_node node)
703709
if (score_str == "all") {
704710
triggers_.reserve(triggers_.size() + this->scores_.size());
705711
for (auto i_score = 0; i_score < this->scores_.size(); ++i_score) {
706-
triggers_.push_back({metric, threshold, i_score});
712+
triggers_.push_back({metric, threshold, ignore_zeros, i_score});
707713
}
708714
} else {
709715
int i_score = 0;
@@ -717,7 +723,7 @@ void Tally::init_triggers(pugi::xml_node node)
717723
"{} but it was listed in a trigger on that tally",
718724
score_str, id_));
719725
}
720-
triggers_.push_back({metric, threshold, i_score});
726+
triggers_.push_back({metric, threshold, ignore_zeros, i_score});
721727
}
722728
}
723729
}

‎src/tallies/trigger.cpp‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ void check_tally_triggers(double& ratio, int& tally_id, int& score)
7777
auto uncert_pair =
7878
get_tally_uncertainty(i_tally, trigger.score_index, filter_index);
7979

80-
// if there is a score without contributions, set ratio to inf and
81-
// exit early
82-
if (uncert_pair.first == -1) {
80+
// If there is a score without contributions, set ratio to inf and
81+
// exit early, unless zero scores are ignored for this trigger.
82+
if (uncert_pair.first == -1 && !trigger.ignore_zeros) {
8383
ratio = INFINITY;
8484
score = t.scores_[trigger.score_index];
8585
tally_id = t.id_;

‎tests/unit_tests/test_triggers.py‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,43 @@ def test_tally_trigger_null_score(run_in_tmpdir):
7373
total_batches = sp.n_realizations + sp.n_inactive
7474
assert total_batches == pincell.settings.trigger_max_batches
7575

76+
77+
def test_tally_trigger_zero_ignored(run_in_tmpdir):
78+
pincell = openmc.examples.pwr_pin_cell()
79+
80+
# create an energy filter below and around the O-16(n,p) threshold (1.02e7 eV)
81+
e_filter = openmc.EnergyFilter([0.0, 1e7, 2e7])
82+
83+
# create a tally with triggers applied
84+
tally = openmc.Tally()
85+
tally.filters = [e_filter]
86+
tally.scores = ['(n,p)']
87+
tally.nuclides = ["O16"]
88+
89+
# 100% relative error: should be immediately satisfied in nonzero bin
90+
trigger = openmc.Trigger('rel_err', 1.0)
91+
trigger.scores = ['(n,p)']
92+
trigger.ignore_zeros = True
93+
94+
tally.triggers = [trigger]
95+
96+
pincell.tallies = [tally]
97+
98+
pincell.settings.particles = 1000 # we need a few more particles for this
99+
pincell.settings.trigger_active = True
100+
pincell.settings.trigger_max_batches = 50
101+
pincell.settings.trigger_batch_interval = 20
102+
103+
sp_file = pincell.run()
104+
105+
with openmc.StatePoint(sp_file) as sp:
106+
# verify that the first bin is zero and the second is nonzero
107+
tally_out = sp.get_tally(id=tally.id)
108+
below, above = tally_out.mean.squeeze()
109+
assert below == 0.0, "Tally events observed below expected threshold"
110+
assert above > 0, "No tally events observed. Test with more particles."
111+
112+
# we expect that the trigger fires before max batches are hit
113+
total_batches = sp.n_realizations + sp.n_inactive
114+
assert total_batches < pincell.settings.trigger_max_batches
115+

0 commit comments

Comments
 (0)