• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openmc-dev / openmc / 14515233533

17 Apr 2025 12:06PM UTC coverage: 83.16% (-2.3%) from 85.414%
14515233533

Pull #3087

github

web-flow
Merge 6ed397e9d into 47ca2916a
Pull Request #3087: wheel building with scikit build core

140769 of 169274 relevant lines covered (83.16%)

11830168.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

63.64
/src/reaction.cpp
1
#include "openmc/reaction.h"
2

3
#include <algorithm> // for remove_if
4
#include <string>
5
#include <unordered_map>
6
#include <utility> // for move
7

8
#include <fmt/core.h>
9

10
#include "openmc/chain.h"
11
#include "openmc/constants.h"
12
#include "openmc/endf.h"
13
#include "openmc/hdf5_interface.h"
14
#include "openmc/random_lcg.h"
15
#include "openmc/search.h"
16
#include "openmc/secondary_uncorrelated.h"
17
#include "openmc/settings.h"
18

19
namespace openmc {
20

21
//==============================================================================
22
// Reaction implementation
23
//==============================================================================
24

25
Reaction::Reaction(
759,461✔
26
  hid_t group, const vector<int>& temperatures, std::string name)
759,461✔
27
{
28
  read_attribute(group, "Q_value", q_value_);
759,461✔
29
  read_attribute(group, "mt", mt_);
759,461✔
30
  int tmp;
31
  read_attribute(group, "center_of_mass", tmp);
759,461✔
32
  scatter_in_cm_ = (tmp == 1);
759,461✔
33

34
  // Checks if redudant attribute exists before loading
35
  // (for compatibiltiy with legacy .h5 libraries)
36
  if (attribute_exists(group, "redundant")) {
759,461✔
37
    read_attribute(group, "redundant", tmp);
759,461✔
38
    redundant_ = (tmp == 1);
759,461✔
39
  } else {
40
    redundant_ = false;
×
41
  }
42

43
  // Read cross section and threshold_idx data
44
  for (auto t : temperatures) {
1,519,153✔
45
    // Get group corresponding to temperature
46
    hid_t temp_group = open_group(group, fmt::format("{}K", t).c_str());
759,692✔
47
    hid_t dset = open_dataset(temp_group, "xs");
759,692✔
48

49
    // Get threshold index
50
    TemperatureXS xs;
759,692✔
51
    read_attribute(dset, "threshold_idx", xs.threshold);
759,692✔
52

53
    // Read cross section values
54
    read_dataset(dset, xs.value);
759,692✔
55
    close_dataset(dset);
759,692✔
56
    close_group(temp_group);
759,692✔
57

58
    // create new entry in xs vector
59
    xs_.push_back(std::move(xs));
759,692✔
60
  }
759,692✔
61

62
  // Read products
63
  for (const auto& name : group_names(group)) {
3,443,506✔
64
    if (name.rfind("product_", 0) == 0) {
2,684,045✔
65
      hid_t pgroup = open_group(group, name.c_str());
1,920,218✔
66
      products_.emplace_back(pgroup);
1,920,218✔
67
      close_group(pgroup);
1,920,218✔
68
    }
69
  }
759,461✔
70

71
  if (settings::use_decay_photons) {
759,461✔
72
    // Remove photon products for D1S method
73
    products_.erase(
2,486✔
74
      std::remove_if(products_.begin(), products_.end(),
1,243✔
75
        [](const auto& p) { return p.particle_ == ParticleType::photon; }),
8,866✔
76
      products_.end());
1,243✔
77

78
    // Determine product for D1S method
79
    auto nuclide_it = data::chain_nuclide_map.find(name);
1,243✔
80
    if (nuclide_it != data::chain_nuclide_map.end()) {
1,243✔
81
      const auto& chain_nuc = data::chain_nuclides[nuclide_it->second];
1,243✔
82
      const auto& rx_products = chain_nuc->reaction_products();
1,243✔
83
      auto product_it = rx_products.find(mt_);
1,243✔
84
      if (product_it != rx_products.end()) {
1,243✔
85
        auto decay_products = product_it->second;
220✔
86
        for (const auto& decay_product : decay_products) {
440✔
87
          auto product_it = data::chain_nuclide_map.find(decay_product.name);
220✔
88
          if (product_it != data::chain_nuclide_map.end()) {
220✔
89
            const auto& product_nuc = data::chain_nuclides[product_it->second];
220✔
90
            if (product_nuc->photon_energy()) {
220✔
91
              products_.emplace_back(decay_product);
132✔
92
            }
93
          }
94
        }
95
      }
220✔
96
    }
97
  }
98
}
759,461✔
99

100
double Reaction::xs(int64_t i_temp, int64_t i_grid, double interp_factor) const
545,986,877✔
101
{
102
  // If energy is below threshold, return 0. Otherwise interpolate between
103
  // nearest grid points
104
  const auto& x = xs_[i_temp];
545,986,877✔
105
  return (i_grid < x.threshold)
545,986,877✔
106
           ? 0.0
545,986,877✔
107
           : (1.0 - interp_factor) * x.value[i_grid - x.threshold] +
331,918,918✔
108
               interp_factor * x.value[i_grid - x.threshold + 1];
545,986,877✔
109
}
110

111
double Reaction::xs(const NuclideMicroXS& micro) const
545,986,877✔
112
{
113
  return this->xs(micro.index_temp, micro.index_grid, micro.interp_factor);
545,986,877✔
114
}
115

116
double Reaction::collapse_rate(int64_t i_temp, span<const double> energy,
×
117
  span<const double> flux, const vector<double>& grid) const
118
{
119
  // Find index corresponding to first energy
120
  const auto& xs = xs_[i_temp].value;
×
121
  int i_low = lower_bound_index(grid.cbegin(), grid.cend(), energy.front());
×
122

123
  // Check for threshold and adjust starting point if necessary
124
  int j_start = 0;
×
125
  int i_threshold = xs_[i_temp].threshold;
×
126
  if (i_low < i_threshold) {
×
127
    i_low = i_threshold;
×
128
    while (energy[j_start + 1] < grid[i_low]) {
×
129
      ++j_start;
×
130
      if (j_start + 1 == energy.size())
×
131
        return 0.0;
×
132
    }
133
  }
134

135
  double xs_flux_sum = 0.0;
×
136

137
  for (int j = j_start; j < flux.size(); ++j) {
×
138
    double E_group_low = energy[j];
×
139
    double E_group_high = energy[j + 1];
×
140
    double flux_per_eV = flux[j] / (E_group_high - E_group_low);
×
141

142
    // Determine energy grid index corresponding to group high
143
    int i_high = i_low;
×
144
    while (grid[i_high + 1] < E_group_high && i_high + 1 < grid.size() - 1)
×
145
      ++i_high;
×
146

147
    // Loop over energy grid points within [E_group_low, E_group_high]
148
    for (; i_low <= i_high; ++i_low) {
×
149
      // Determine bounding grid energies and cross sections
150
      double E_l = grid[i_low];
×
151
      double E_r = grid[i_low + 1];
×
152
      if (E_l == E_r)
×
153
        continue;
×
154

155
      double xs_l = xs[i_low - i_threshold];
×
156
      double xs_r = xs[i_low + 1 - i_threshold];
×
157

158
      // Determine actual energies
159
      double E_low = std::max(E_group_low, E_l);
×
160
      double E_high = std::min(E_group_high, E_r);
×
161

162
      // Determine average cross section across segment
163
      double m = (xs_r - xs_l) / (E_r - E_l);
×
164
      double xs_low = xs_l + m * (E_low - E_l);
×
165
      double xs_high = xs_l + m * (E_high - E_l);
×
166
      double xs_avg = 0.5 * (xs_low + xs_high);
×
167

168
      // Add contribution from segment
169
      double dE = (E_high - E_low);
×
170
      xs_flux_sum += flux_per_eV * xs_avg * dE;
×
171
    }
172

173
    i_low = i_high;
×
174

175
    // Check for end of energy grid
176
    if (i_low + 1 == grid.size())
×
177
      break;
×
178
  }
179

180
  return xs_flux_sum;
×
181
}
182

183
//==============================================================================
184
// Non-member functions
185
//==============================================================================
186

187
std::unordered_map<int, std::string> REACTION_NAME_MAP {
188
  {SCORE_FLUX, "flux"},
189
  {SCORE_TOTAL, "total"},
190
  {SCORE_SCATTER, "scatter"},
191
  {SCORE_NU_SCATTER, "nu-scatter"},
192
  {SCORE_ABSORPTION, "absorption"},
193
  {SCORE_FISSION, "fission"},
194
  {SCORE_NU_FISSION, "nu-fission"},
195
  {SCORE_DECAY_RATE, "decay-rate"},
196
  {SCORE_DELAYED_NU_FISSION, "delayed-nu-fission"},
197
  {SCORE_PROMPT_NU_FISSION, "prompt-nu-fission"},
198
  {SCORE_KAPPA_FISSION, "kappa-fission"},
199
  {SCORE_CURRENT, "current"},
200
  {SCORE_EVENTS, "events"},
201
  {SCORE_INVERSE_VELOCITY, "inverse-velocity"},
202
  {SCORE_FISS_Q_PROMPT, "fission-q-prompt"},
203
  {SCORE_FISS_Q_RECOV, "fission-q-recoverable"},
204
  {SCORE_PULSE_HEIGHT, "pulse-height"},
205
  {SCORE_IFP_TIME_NUM, "ifp-time-numerator"},
206
  {SCORE_IFP_BETA_NUM, "ifp-beta-numerator"},
207
  {SCORE_IFP_DENOM, "ifp-denominator"},
208
  // Normal ENDF-based reactions
209
  {TOTAL_XS, "(n,total)"},
210
  {ELASTIC, "(n,elastic)"},
211
  {N_NONELASTIC, "(n,nonelastic)"},
212
  {N_LEVEL, "(n,level)"},
213
  {N_2ND, "(n,2nd)"},
214
  {N_2N, "(n,2n)"},
215
  {N_3N, "(n,3n)"},
216
  {N_FISSION, "(n,fission)"},
217
  {N_F, "(n,f)"},
218
  {N_NF, "(n,nf)"},
219
  {N_2NF, "(n,2nf)"},
220
  {N_NA, "(n,na)"},
221
  {N_N3A, "(n,n3a)"},
222
  {N_2NA, "(n,2na)"},
223
  {N_3NA, "(n,3na)"},
224
  {N_NP, "(n,np)"},
225
  {N_N2A, "(n,n2a)"},
226
  {N_2N2A, "(n,2n2a)"},
227
  {N_ND, "(n,nd)"},
228
  {N_NT, "(n,nt)"},
229
  {N_N3HE, "(n,n3He)"},
230
  {N_ND2A, "(n,nd2a)"},
231
  {N_NT2A, "(n,nt2a)"},
232
  {N_4N, "(n,4n)"},
233
  {N_3NF, "(n,3nf)"},
234
  {N_2NP, "(n,2np)"},
235
  {N_3NP, "(n,3np)"},
236
  {N_N2P, "(n,n2p)"},
237
  {N_NPA, "(n,npa)"},
238
  {N_NC, "(n,nc)"},
239
  {N_DISAPPEAR, "(n,disappear)"},
240
  {N_GAMMA, "(n,gamma)"},
241
  {N_P, "(n,p)"},
242
  {N_D, "(n,d)"},
243
  {N_T, "(n,t)"},
244
  {N_3HE, "(n,3He)"},
245
  {N_A, "(n,a)"},
246
  {N_2A, "(n,2a)"},
247
  {N_3A, "(n,3a)"},
248
  {N_2P, "(n,2p)"},
249
  {N_PA, "(n,pa)"},
250
  {N_T2A, "(n,t2a)"},
251
  {N_D2A, "(n,d2a)"},
252
  {N_PD, "(n,pd)"},
253
  {N_PT, "(n,pt)"},
254
  {N_DA, "(n,da)"},
255
  {N_5N, "(n,5n)"},
256
  {N_6N, "(n,6n)"},
257
  {N_2NT, "(n,2nt)"},
258
  {N_TA, "(n,ta)"},
259
  {N_4NP, "(n,4np)"},
260
  {N_3ND, "(n,3nd)"},
261
  {N_NDA, "(n,nda)"},
262
  {N_2NPA, "(n,2npa)"},
263
  {N_7N, "(n,7n)"},
264
  {N_8N, "(n,8n)"},
265
  {N_5NP, "(n,5np)"},
266
  {N_6NP, "(n,6np)"},
267
  {N_7NP, "(n,7np)"},
268
  {N_4NA, "(n,4na)"},
269
  {N_5NA, "(n,5na)"},
270
  {N_6NA, "(n,6na)"},
271
  {N_7NA, "(n,7na)"},
272
  {N_4ND, "(n,4nd)"},
273
  {N_5ND, "(n,5nd)"},
274
  {N_6ND, "(n,6nd)"},
275
  {N_3NT, "(n,3nt)"},
276
  {N_4NT, "(n,4nt)"},
277
  {N_5NT, "(n,5nt)"},
278
  {N_6NT, "(n,6nt)"},
279
  {N_2N3HE, "(n,2n3He)"},
280
  {N_3N3HE, "(n,3n3He)"},
281
  {N_4N3HE, "(n,4n3He)"},
282
  {N_3N2P, "(n,3n2p)"},
283
  {N_3N2A, "(n,3n2a)"},
284
  {N_3NPA, "(n,3npa)"},
285
  {N_DT, "(n,dt)"},
286
  {N_NPD, "(n,npd)"},
287
  {N_NPT, "(n,npt)"},
288
  {N_NDT, "(n,ndt)"},
289
  {N_NP3HE, "(n,np3He)"},
290
  {N_ND3HE, "(n,nd3He)"},
291
  {N_NT3HE, "(n,nt3He)"},
292
  {N_NTA, "(n,nta)"},
293
  {N_2N2P, "(n,2n2p)"},
294
  {N_P3HE, "(n,p3He)"},
295
  {N_D3HE, "(n,d3He)"},
296
  {N_3HEA, "(n,3Hea)"},
297
  {N_4N2P, "(n,4n2p)"},
298
  {N_4N2A, "(n,4n2a)"},
299
  {N_4NPA, "(n,4npa)"},
300
  {N_3P, "(n,3p)"},
301
  {N_N3P, "(n,n3p)"},
302
  {N_3N2PA, "(n,3n2pa)"},
303
  {N_5N2P, "(n,5n2p)"},
304
  {201, "(n,Xn)"},
305
  {202, "(n,Xgamma)"},
306
  {N_XP, "(n,Xp)"},
307
  {N_XD, "(n,Xd)"},
308
  {N_XT, "(n,Xt)"},
309
  {N_X3HE, "(n,X3He)"},
310
  {N_XA, "(n,Xa)"},
311
  {HEATING, "heating"},
312
  {DAMAGE_ENERGY, "damage-energy"},
313
  {COHERENT, "coherent-scatter"},
314
  {INCOHERENT, "incoherent-scatter"},
315
  {PAIR_PROD_ELEC, "pair-production-electron"},
316
  {PAIR_PROD, "pair-production"},
317
  {PAIR_PROD_NUC, "pair-production-nuclear"},
318
  {PHOTOELECTRIC, "photoelectric"},
319
  {N_PC, "(n,pc)"},
320
  {N_DC, "(n,dc)"},
321
  {N_TC, "(n,tc)"},
322
  {N_3HEC, "(n,3Hec)"},
323
  {N_AC, "(n,ac)"},
324
  {N_2NC, "(n,2nc)"},
325
  {HEATING_LOCAL, "heating-local"},
326
};
327

328
std::unordered_map<std::string, int> REACTION_TYPE_MAP;
329

330
void initialize_maps()
3,777✔
331
{
332
  // Add level reactions to name map
333
  for (int level = 0; level <= 48; ++level) {
188,850✔
334
    if (level >= 1 && level <= 40) {
185,073✔
335
      REACTION_NAME_MAP[50 + level] = fmt::format("(n,n{})", level);
151,080✔
336
    }
337
    REACTION_NAME_MAP[600 + level] = fmt::format("(n,p{})", level);
185,073✔
338
    REACTION_NAME_MAP[650 + level] = fmt::format("(n,d{})", level);
185,073✔
339
    REACTION_NAME_MAP[700 + level] = fmt::format("(n,t{})", level);
185,073✔
340
    REACTION_NAME_MAP[750 + level] = fmt::format("(n,3He{})", level);
185,073✔
341
    REACTION_NAME_MAP[800 + level] = fmt::format("(n,a{})", level);
185,073✔
342
    if (level <= 15) {
185,073✔
343
      REACTION_NAME_MAP[875 + level] = fmt::format("(n,2n{})", level);
60,432✔
344
    }
345
  }
346

347
  // Create photoelectric subshells
348
  for (int mt = 534; mt <= 572; ++mt) {
151,080✔
349
    REACTION_NAME_MAP[mt] =
174,291✔
350
      fmt::format("photoelectric, {} subshell", SUBSHELLS[mt - 534]);
414,921✔
351
  }
352

353
  // Invert name map to create type map
354
  for (const auto& kv : REACTION_NAME_MAP) {
1,805,406✔
355
    REACTION_TYPE_MAP[kv.second] = kv.first;
1,801,629✔
356
  }
357
}
3,777✔
358

359
std::string reaction_name(int mt)
52,964✔
360
{
361
  // Initialize remainder of name map and all of type map
362
  if (REACTION_TYPE_MAP.empty())
52,964✔
363
    initialize_maps();
33✔
364

365
  // Get reaction name from map
366
  auto it = REACTION_NAME_MAP.find(mt);
52,964✔
367
  if (it != REACTION_NAME_MAP.end()) {
52,964✔
368
    return it->second;
52,964✔
369
  } else {
370
    return fmt::format("MT={}", mt);
×
371
  }
372
}
373

374
int reaction_type(std::string name)
39,785✔
375
{
376
  // Initialize remainder of name map and all of type map
377
  if (REACTION_TYPE_MAP.empty())
39,785✔
378
    initialize_maps();
3,744✔
379

380
  // (n,total) exists in REACTION_TYPE_MAP for MT=1, but we need this to return
381
  // the special SCORE_TOTAL score
382
  if (name == "(n,total)")
39,785✔
383
    return SCORE_TOTAL;
×
384

385
  // Check if type map has an entry for this reaction name
386
  auto it = REACTION_TYPE_MAP.find(name);
39,785✔
387
  if (it != REACTION_TYPE_MAP.end()) {
39,785✔
388
    return it->second;
39,549✔
389
  }
390

391
  // Alternate names for several reactions
392
  if (name == "elastic") {
236✔
393
    return ELASTIC;
134✔
394
  } else if (name == "n2n") {
102✔
395
    return N_2N;
×
396
  } else if (name == "n3n") {
102✔
397
    return N_3N;
×
398
  } else if (name == "n4n") {
102✔
399
    return N_4N;
×
400
  } else if (name == "H1-production") {
102✔
401
    return N_XP;
16✔
402
  } else if (name == "H2-production") {
86✔
403
    return N_XD;
16✔
404
  } else if (name == "H3-production") {
70✔
405
    return N_XT;
16✔
406
  } else if (name == "He3-production") {
54✔
407
    return N_X3HE;
38✔
408
  } else if (name == "He4-production") {
16✔
409
    return N_XA;
16✔
410
  }
411

412
  // Assume the given string is a reaction MT number.  Make sure it's a natural
413
  // number then return.
414
  int MT = 0;
×
415
  try {
416
    MT = std::stoi(name);
×
417
  } catch (const std::invalid_argument& ex) {
×
418
    throw std::invalid_argument(
×
419
      "Invalid tally score \"" + name +
×
420
      "\". See the docs "
421
      "for details: "
422
      "https://docs.openmc.org/en/stable/usersguide/tallies.html#scores");
×
423
  }
×
424
  if (MT < 1)
×
425
    throw std::invalid_argument(
×
426
      "Invalid tally score \"" + name +
×
427
      "\". See the docs "
428
      "for details: "
429
      "https://docs.openmc.org/en/stable/usersguide/tallies.html#scores");
×
430
  return MT;
×
431
}
432

433
} // namespace openmc
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc