1 #include "openmc/tallies/trigger.h"
2 
3 #include <cmath>
4 #include <utility> // for std::pair
5 
6 #include <fmt/core.h>
7 
8 #include "openmc/capi.h"
9 #include "openmc/constants.h"
10 #include "openmc/error.h"
11 #include "openmc/reaction.h"
12 #include "openmc/settings.h"
13 #include "openmc/simulation.h"
14 #include "openmc/tallies/tally.h"
15 
16 namespace openmc {
17 
18 //==============================================================================
19 // Global variable definitions
20 //==============================================================================
21 
22 namespace settings {
23   KTrigger keff_trigger;
24 }
25 
26 //==============================================================================
27 // Non-member functions
28 //==============================================================================
29 
30 std::pair<double, double>
get_tally_uncertainty(int i_tally,int score_index,int filter_index)31 get_tally_uncertainty(int i_tally, int score_index, int filter_index)
32 {
33   const auto& tally {model::tallies[i_tally]};
34 
35   auto sum = tally->results_(filter_index, score_index, TallyResult::SUM);
36   auto sum_sq = tally->results_(filter_index, score_index, TallyResult::SUM_SQ);
37 
38   int n = tally->n_realizations_;
39   auto mean = sum / n;
40   double std_dev = std::sqrt((sum_sq/n - mean*mean) / (n-1));
41   double rel_err = (mean != 0.) ? std_dev / std::abs(mean) : 0.;
42 
43   return {std_dev, rel_err};
44 }
45 
46 //! Find the limiting limiting tally trigger.
47 //
48 //! param[out] ratio The uncertainty/threshold ratio for the most limiting
49 //!   tally trigger
50 //! param[out] tally_id The ID number of the most limiting tally
51 //! param[out] score The most limiting tally score bin
52 
53 void
check_tally_triggers(double & ratio,int & tally_id,int & score)54 check_tally_triggers(double& ratio, int& tally_id, int& score)
55 {
56   ratio = 0.;
57   for (auto i_tally = 0; i_tally < model::tallies.size(); ++i_tally) {
58     const Tally& t {*model::tallies[i_tally]};
59 
60     // Ignore tallies with less than two realizations.
61     if (t.n_realizations_ < 2) continue;
62 
63     for (const auto& trigger : t.triggers_) {
64       // Skip trigger if it is not active
65       if (trigger.metric == TriggerMetric::not_active) continue;
66 
67       const auto& results = t.results_;
68       for (auto filter_index = 0; filter_index < results.shape()[0];
69            ++filter_index) {
70         for (auto score_index = 0; score_index < results.shape()[1];
71              ++score_index) {
72           // Compute the tally uncertainty metrics.
73           auto uncert_pair = get_tally_uncertainty(i_tally, score_index,
74             filter_index);
75           double std_dev = uncert_pair.first;
76           double rel_err = uncert_pair.second;
77 
78           // Pick out the relevant uncertainty metric for this trigger.
79           double uncertainty;
80           switch (trigger.metric) {
81             case TriggerMetric::variance:
82               uncertainty = std_dev * std_dev;
83               break;
84             case TriggerMetric::standard_deviation:
85               uncertainty = std_dev;
86               break;
87             case TriggerMetric::relative_error:
88               uncertainty = rel_err;
89               break;
90             case TriggerMetric::not_active:
91               UNREACHABLE();
92           }
93 
94           // Compute the uncertainty / threshold ratio.
95           double this_ratio = uncertainty / trigger.threshold;
96           if (trigger.metric == TriggerMetric::variance) {
97             this_ratio = std::sqrt(ratio);
98           }
99 
100           // If this is the most uncertain value, set the output variables.
101           if (this_ratio > ratio) {
102             ratio = this_ratio;
103             score = t.scores_[trigger.score_index];
104             tally_id = t.id_;
105           }
106         }
107       }
108     }
109   }
110 }
111 
112 //! Compute the uncertainty/threshold ratio for the eigenvalue trigger.
113 
114 double
check_keff_trigger()115 check_keff_trigger()
116 {
117   if (settings::run_mode != RunMode::EIGENVALUE) return 0.0;
118 
119   double k_combined[2];
120   openmc_get_keff(k_combined);
121 
122   double uncertainty = 0.;
123   switch (settings::keff_trigger.metric) {
124   case TriggerMetric::variance:
125     uncertainty = k_combined[1] * k_combined[1];
126     break;
127   case TriggerMetric::standard_deviation:
128     uncertainty = k_combined[1];
129     break;
130   case TriggerMetric::relative_error:
131     uncertainty = k_combined[1] / k_combined[0];
132     break;
133   default:
134     // If it's an unrecognized TriggerMetric or no keff trigger is on,
135     // return 0 to stop division by zero where "ratio" is calculated.
136     return 0.0;
137   }
138 
139   double ratio = uncertainty / settings::keff_trigger.threshold;
140   if (settings::keff_trigger.metric == TriggerMetric::variance)
141     ratio = std::sqrt(ratio);
142   return ratio;
143 }
144 
145 //! See if tally and eigenvalue uncertainties are under trigger thresholds.
146 
147 void
check_triggers()148 check_triggers()
149 {
150   // Make some aliases.
151   const auto current_batch {simulation::current_batch};
152   const auto n_batches {settings::n_batches};
153   const auto interval {settings::trigger_batch_interval};
154 
155   // See if the current batch is one for which the triggers must be checked.
156   if (!settings::trigger_on) return;
157   if (current_batch < n_batches) return;
158   if (((current_batch - n_batches) % interval) != 0) return;
159 
160   // Check the eigenvalue and tally triggers.
161   double keff_ratio = check_keff_trigger();
162   double tally_ratio;
163   int tally_id, score;
164   check_tally_triggers(tally_ratio, tally_id, score);
165 
166   // If all the triggers are satisfied, alert the user and return.
167   if (std::max(keff_ratio, tally_ratio) <= 1.) {
168     simulation::satisfy_triggers = true;
169     write_message(7, "Triggers satisfied for batch {}", current_batch);
170     return;
171   }
172 
173   // At least one trigger is unsatisfied.  Let the user know which one.
174   simulation::satisfy_triggers = false;
175   std::string msg;
176   if (keff_ratio >= tally_ratio) {
177     msg = fmt::format("Triggers unsatisfied, max unc./thresh. is {} for "
178       "eigenvalue", keff_ratio);
179   } else {
180     msg = fmt::format(
181       "Triggers unsatisfied, max unc./thresh. is {} for {} in tally {}",
182       tally_ratio, reaction_name(score), tally_id);
183   }
184   write_message(msg, 7);
185 
186   // Estimate batches til triggers are satisfied.
187   if (settings::trigger_predict) {
188     // This calculation assumes tally variance is proportional to 1/N where N is
189     // the number of batches.
190     auto max_ratio = std::max(keff_ratio, tally_ratio);
191     auto n_active = current_batch - settings::n_inactive;
192     auto n_pred_batches = static_cast<int>(n_active * max_ratio * max_ratio)
193       + settings::n_inactive + 1;
194 
195     std::string msg = fmt::format("The estimated number of batches is {}",
196       n_pred_batches);
197     if (n_pred_batches > settings::n_max_batches) {
198       msg.append(" --- greater than max batches");
199       warning(msg);
200     } else {
201       write_message(msg, 7);
202     }
203   }
204 }
205 
206 } // namespace openmc
207