• 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

77.56
/src/nuclide.cpp
1
#include "openmc/nuclide.h"
2

3
#include "openmc/capi.h"
4
#include "openmc/container_util.h"
5
#include "openmc/cross_sections.h"
6
#include "openmc/endf.h"
7
#include "openmc/error.h"
8
#include "openmc/hdf5_interface.h"
9
#include "openmc/message_passing.h"
10
#include "openmc/photon.h"
11
#include "openmc/random_lcg.h"
12
#include "openmc/search.h"
13
#include "openmc/settings.h"
14
#include "openmc/simulation.h"
15
#include "openmc/string_utils.h"
16
#include "openmc/thermal.h"
17

18
#include <fmt/core.h>
19

20
#include "xtensor/xbuilder.hpp"
21
#include "xtensor/xview.hpp"
22

23
#include <algorithm> // for sort, min_element
24
#include <cassert>
25
#include <string> // for to_string, stoi
26

27
namespace openmc {
28

29
//==============================================================================
30
// Global variables
31
//==============================================================================
32

33
namespace data {
34
array<double, 2> energy_min {0.0, 0.0};
35
array<double, 2> energy_max {INFTY, INFTY};
36
double temperature_min {INFTY};
37
double temperature_max {0.0};
38
std::unordered_map<std::string, int> nuclide_map;
39
vector<unique_ptr<Nuclide>> nuclides;
40
} // namespace data
41

42
//==============================================================================
43
// Nuclide implementation
44
//==============================================================================
45

46
int Nuclide::XS_TOTAL {0};
47
int Nuclide::XS_ABSORPTION {1};
48
int Nuclide::XS_FISSION {2};
49
int Nuclide::XS_NU_FISSION {3};
50
int Nuclide::XS_PHOTON_PROD {4};
51

52
Nuclide::Nuclide(hid_t group, const vector<double>& temperature)
16,593✔
53
{
54
  // Set index of nuclide in global vector
55
  index_ = data::nuclides.size();
16,593✔
56

57
  // Get name of nuclide from group, removing leading '/'
58
  name_ = object_name(group).substr(1);
16,593✔
59
  data::nuclide_map[name_] = index_;
16,593✔
60

61
  read_attribute(group, "Z", Z_);
16,593✔
62
  read_attribute(group, "A", A_);
16,593✔
63
  read_attribute(group, "metastable", metastable_);
16,593✔
64
  read_attribute(group, "atomic_weight_ratio", awr_);
16,593✔
65

66
  if (settings::run_mode == RunMode::VOLUME) {
16,593✔
67
    // Determine whether nuclide is fissionable and then exit
68
    int mt;
69
    hid_t rxs_group = open_group(group, "reactions");
470✔
70
    for (auto name : group_names(rxs_group)) {
17,272✔
71
      if (starts_with(name, "reaction_")) {
16,955✔
72
        hid_t rx_group = open_group(rxs_group, name.c_str());
16,955✔
73
        read_attribute(rx_group, "mt", mt);
16,955✔
74
        if (is_fission(mt)) {
16,955✔
75
          fissionable_ = true;
153✔
76
          break;
153✔
77
        }
78
      }
79
    }
17,272✔
80
    return;
470✔
81
  }
82

83
  // Determine temperatures available
84
  hid_t kT_group = open_group(group, "kTs");
16,123✔
85
  auto dset_names = dataset_names(kT_group);
16,123✔
86
  vector<double> temps_available;
16,123✔
87
  for (const auto& name : dset_names) {
32,576✔
88
    double T;
89
    read_dataset(kT_group, name.c_str(), T);
16,453✔
90
    temps_available.push_back(std::round(T / K_BOLTZMANN));
16,453✔
91
  }
92
  std::sort(temps_available.begin(), temps_available.end());
16,123✔
93

94
  // If only one temperature is available, revert to nearest temperature
95
  if (temps_available.size() == 1 &&
32,081✔
96
      settings::temperature_method == TemperatureMethod::INTERPOLATION) {
15,958✔
97
    if (mpi::master) {
×
98
      warning("Cross sections for " + name_ +
×
99
              " are only available at one "
100
              "temperature. Reverting to nearest temperature method.");
101
    }
102
    settings::temperature_method = TemperatureMethod::NEAREST;
×
103
  }
104

105
  // Determine actual temperatures to read -- start by checking whether a
106
  // temperature range was given (indicated by T_max > 0), in which case all
107
  // temperatures in the range are loaded irrespective of what temperatures
108
  // actually appear in the model
109
  vector<int> temps_to_read;
16,123✔
110
  int n = temperature.size();
16,123✔
111
  double T_min = n > 0 ? settings::temperature_range[0] : 0.0;
16,123✔
112
  double T_max = n > 0 ? settings::temperature_range[1] : INFTY;
16,123✔
113
  if (T_max > 0.0) {
16,123✔
114
    // Determine first available temperature below or equal to T_min
115
    auto T_min_it =
116
      std::upper_bound(temps_available.begin(), temps_available.end(), T_min);
341✔
117
    if (T_min_it != temps_available.begin())
341✔
118
      --T_min_it;
×
119

120
    // Determine first available temperature above or equal to T_max
121
    auto T_max_it =
122
      std::lower_bound(temps_available.begin(), temps_available.end(), T_max);
341✔
123
    if (T_max_it != temps_available.end())
341✔
124
      ++T_max_it;
×
125

126
    // Add corresponding temperatures to vector
127
    for (auto it = T_min_it; it != T_max_it; ++it) {
682✔
128
      temps_to_read.push_back(std::round(*it));
341✔
129
    }
130
  }
131

132
  switch (settings::temperature_method) {
16,123✔
133
  case TemperatureMethod::NEAREST:
16,002✔
134
    // Find nearest temperatures
135
    for (double T_desired : temperature) {
31,735✔
136

137
      // Determine closest temperature
138
      double min_delta_T = INFTY;
15,733✔
139
      double T_actual = 0.0;
15,733✔
140
      for (auto T : temps_available) {
31,554✔
141
        double delta_T = std::abs(T - T_desired);
15,821✔
142
        if (delta_T < min_delta_T) {
15,821✔
143
          T_actual = T;
15,766✔
144
          min_delta_T = delta_T;
15,766✔
145
        }
146
      }
147

148
      if (std::abs(T_actual - T_desired) < settings::temperature_tolerance) {
15,733✔
149
        if (!contains(temps_to_read, std::round(T_actual))) {
15,733✔
150
          temps_to_read.push_back(std::round(T_actual));
15,661✔
151

152
          // Write warning for resonance scattering data if 0K is not available
153
          if (std::abs(T_actual - T_desired) > 0 && T_desired == 0 &&
15,661✔
154
              mpi::master) {
155
            warning(name_ +
×
156
                    " does not contain 0K data needed for resonance "
157
                    "scattering options selected. Using data at " +
×
158
                    std::to_string(T_actual) + " K instead.");
×
159
          }
160
        }
161
      } else {
162
        fatal_error(fmt::format(
×
163
          "Nuclear data library does not contain cross sections "
164
          "for {}  at or near {} K. Available temperatures "
165
          "are {} K. Consider making use of openmc.Settings.temperature "
166
          "to specify how intermediate temperatures are treated.",
167
          name_, std::to_string(T_desired), concatenate(temps_available)));
×
168
      }
169
    }
170
    break;
16,002✔
171

172
  case TemperatureMethod::INTERPOLATION:
121✔
173
    // If temperature interpolation or multipole is selected, get a list of
174
    // bounding temperatures for each actual temperature present in the model
175
    for (double T_desired : temperature) {
253✔
176
      bool found_pair = false;
132✔
177
      for (int j = 0; j < temps_available.size() - 1; ++j) {
396✔
178
        if (temps_available[j] <= T_desired &&
429✔
179
            T_desired < temps_available[j + 1]) {
165✔
180
          int T_j = temps_available[j];
77✔
181
          int T_j1 = temps_available[j + 1];
77✔
182
          if (!contains(temps_to_read, T_j)) {
77✔
183
            temps_to_read.push_back(T_j);
77✔
184
          }
185
          if (!contains(temps_to_read, T_j1)) {
77✔
186
            temps_to_read.push_back(T_j1);
66✔
187
          }
188
          found_pair = true;
77✔
189
        }
190
      }
191

192
      if (!found_pair) {
132✔
193
        // If no pairs found, check if the desired temperature falls just
194
        // outside of data
195
        if (std::abs(T_desired - temps_available.front()) <=
55✔
196
            settings::temperature_tolerance) {
197
          if (!contains(temps_to_read, temps_available.front())) {
33✔
198
            temps_to_read.push_back(temps_available.front());
33✔
199
          }
200
          continue;
33✔
201
        }
202
        if (std::abs(T_desired - temps_available.back()) <=
22✔
203
            settings::temperature_tolerance) {
204
          if (!contains(temps_to_read, temps_available.back())) {
22✔
205
            temps_to_read.push_back(temps_available.back());
22✔
206
          }
207
          continue;
22✔
208
        }
209
        fatal_error(
×
210
          "Nuclear data library does not contain cross sections for " + name_ +
×
211
          " at temperatures that bound " + std::to_string(T_desired) + " K.");
×
212
      }
213
    }
214
    break;
121✔
215
  }
216

217
  // Sort temperatures to read
218
  std::sort(temps_to_read.begin(), temps_to_read.end());
16,123✔
219

220
  data::temperature_min =
16,123✔
221
    std::min(data::temperature_min, static_cast<double>(temps_to_read.front()));
16,123✔
222
  data::temperature_max =
16,123✔
223
    std::max(data::temperature_max, static_cast<double>(temps_to_read.back()));
16,123✔
224

225
  hid_t energy_group = open_group(group, "energy");
16,123✔
226
  for (const auto& T : temps_to_read) {
32,323✔
227
    std::string dset {std::to_string(T) + "K"};
16,200✔
228

229
    // Determine exact kT values
230
    double kT;
231
    read_dataset(kT_group, dset.c_str(), kT);
16,200✔
232
    kTs_.push_back(kT);
16,200✔
233

234
    // Read energy grid
235
    grid_.emplace_back();
16,200✔
236
    read_dataset(energy_group, dset.c_str(), grid_.back().energy);
16,200✔
237
  }
16,200✔
238
  close_group(kT_group);
16,123✔
239

240
  // Check for 0K energy grid
241
  if (object_exists(energy_group, "0K")) {
16,123✔
242
    read_dataset(energy_group, "0K", energy_0K_);
3,376✔
243
  }
244
  close_group(energy_group);
16,123✔
245

246
  // Read reactions
247
  hid_t rxs_group = open_group(group, "reactions");
16,123✔
248
  for (auto name : group_names(rxs_group)) {
775,584✔
249
    if (starts_with(name, "reaction_")) {
759,461✔
250
      hid_t rx_group = open_group(rxs_group, name.c_str());
759,461✔
251
      reactions_.push_back(
759,461✔
252
        make_unique<Reaction>(rx_group, temps_to_read, name_));
1,518,922✔
253

254
      // Check for 0K elastic scattering
255
      const auto& rx = reactions_.back();
759,461✔
256
      if (rx->mt_ == ELASTIC) {
759,461✔
257
        if (object_exists(rx_group, "0K")) {
16,123✔
258
          hid_t temp_group = open_group(rx_group, "0K");
3,376✔
259
          read_dataset(temp_group, "xs", elastic_0K_);
3,376✔
260
          close_group(temp_group);
3,376✔
261
        }
262
      }
263
      close_group(rx_group);
759,461✔
264

265
      // Determine reaction indices for inelastic scattering reactions
266
      if (is_inelastic_scatter(rx->mt_) && !rx->redundant_) {
759,461✔
267
        index_inelastic_scatter_.push_back(reactions_.size() - 1);
376,450✔
268
      }
269
    }
270
  }
775,584✔
271
  close_group(rxs_group);
16,123✔
272

273
  // Read unresolved resonance probability tables if present
274
  if (object_exists(group, "urr")) {
16,123✔
275
    urr_present_ = true;
7,653✔
276
    urr_data_.reserve(temps_to_read.size());
7,653✔
277

278
    for (int i = 0; i < temps_to_read.size(); i++) {
15,306✔
279
      // Get temperature as a string
280
      std::string temp_str {std::to_string(temps_to_read[i]) + "K"};
7,653✔
281

282
      // Read probability tables for i-th temperature
283
      hid_t urr_group = open_group(group, ("urr/" + temp_str).c_str());
7,653✔
284
      urr_data_.emplace_back(urr_group);
7,653✔
285
      close_group(urr_group);
7,653✔
286

287
      // Check for negative values
288
      if (urr_data_[i].has_negative() && mpi::master) {
7,653✔
289
        warning("Negative value(s) found on probability table for nuclide " +
×
290
                name_ + " at " + temp_str);
×
291
      }
292
    }
7,653✔
293

294
    // If the inelastic competition flag indicates that the inelastic cross
295
    // section should be determined from a normal reaction cross section, we
296
    // need to get the index of the reaction.
297
    if (temps_to_read.size() > 0) {
7,653✔
298
      // Make sure inelastic flags are consistent for different temperatures
299
      for (int i = 0; i < urr_data_.size() - 1; ++i) {
7,653✔
300
        if (urr_data_[i].inelastic_flag_ != urr_data_[i + 1].inelastic_flag_) {
×
301
          fatal_error(fmt::format(
×
302
            "URR inelastic flag is not consistent for "
303
            "multiple temperatures in nuclide {}. This most likely indicates "
304
            "a problem in how the data was processed.",
305
            name_));
×
306
        }
307
      }
308

309
      if (urr_data_[0].inelastic_flag_ > 0) {
7,653✔
310
        for (int i = 0; i < reactions_.size(); i++) {
247,924✔
311
          if (reactions_[i]->mt_ == urr_data_[0].inelastic_flag_) {
243,367✔
312
            urr_inelastic_ = i;
4,557✔
313
          }
314
        }
315

316
        // Abort if no corresponding inelastic reaction was found
317
        if (urr_inelastic_ == C_NONE) {
4,557✔
318
          fatal_error("Could no find inelastic reaction specified on "
×
319
                      "unresolved resonance probability table.");
320
        }
321
      }
322
    }
323
  }
324

325
  // Check for total nu data
326
  if (object_exists(group, "total_nu")) {
16,123✔
327
    // Read total nu data
328
    hid_t nu_group = open_group(group, "total_nu");
4,551✔
329
    total_nu_ = read_function(nu_group, "yield");
4,551✔
330
    close_group(nu_group);
4,551✔
331
  }
332

333
  // Read fission energy release data if present
334
  if (object_exists(group, "fission_energy_release")) {
16,123✔
335
    hid_t fer_group = open_group(group, "fission_energy_release");
4,551✔
336
    fission_q_prompt_ = read_function(fer_group, "q_prompt");
4,551✔
337
    fission_q_recov_ = read_function(fer_group, "q_recoverable");
4,551✔
338

339
    // Read fission fragment and delayed beta energy release. This is needed for
340
    // energy normalization in k-eigenvalue calculations
341
    fragments_ = read_function(fer_group, "fragments");
4,551✔
342
    betas_ = read_function(fer_group, "betas");
4,551✔
343

344
    // We need prompt/delayed photon energy release for scaling fission photon
345
    // production
346
    prompt_photons_ = read_function(fer_group, "prompt_photons");
4,551✔
347
    delayed_photons_ = read_function(fer_group, "delayed_photons");
4,551✔
348
    close_group(fer_group);
4,551✔
349
  }
350

351
  this->create_derived(prompt_photons_.get(), delayed_photons_.get());
16,123✔
352
}
16,123✔
353

354
Nuclide::~Nuclide()
16,593✔
355
{
356
  data::nuclide_map.erase(name_);
16,593✔
357
}
16,593✔
358

359
void Nuclide::create_derived(
16,123✔
360
  const Function1D* prompt_photons, const Function1D* delayed_photons)
361
{
362
  for (const auto& grid : grid_) {
32,323✔
363
    // Allocate and initialize cross section
364
    array<size_t, 2> shape {grid.energy.size(), 5};
16,200✔
365
    xs_.emplace_back(shape, 0.0);
16,200✔
366
  }
367

368
  reaction_index_.fill(C_NONE);
16,123✔
369
  for (int i = 0; i < reactions_.size(); ++i) {
775,584✔
370
    const auto& rx {reactions_[i]};
759,461✔
371

372
    // Set entry in direct address table for reaction
373
    reaction_index_[rx->mt_] = i;
759,461✔
374

375
    for (int t = 0; t < kTs_.size(); ++t) {
1,519,153✔
376
      int j = rx->xs_[t].threshold;
759,692✔
377
      int n = rx->xs_[t].value.size();
759,692✔
378
      auto xs = xt::adapt(rx->xs_[t].value);
759,692✔
379
      auto pprod = xt::view(xs_[t], xt::range(j, j + n), XS_PHOTON_PROD);
759,692✔
380

381
      for (const auto& p : rx->products_) {
2,672,012✔
382
        if (p.particle_ == ParticleType::photon) {
1,912,320✔
383
          for (int k = 0; k < n; ++k) {
2,147,483,647✔
384
            double E = grid_[t].energy[k + j];
2,147,483,647✔
385

386
            // For fission, artificially increase the photon yield to
387
            // account for delayed photons
388
            double f = 1.0;
2,147,483,647✔
389
            if (settings::delayed_photon_scaling) {
2,147,483,647✔
390
              if (is_fission(rx->mt_)) {
2,147,483,647✔
391
                if (prompt_photons && delayed_photons) {
353,750,652✔
392
                  double energy_prompt = (*prompt_photons)(E);
353,750,652✔
393
                  double energy_delayed = (*delayed_photons)(E);
353,750,652✔
394
                  f = (energy_prompt + energy_delayed) / (energy_prompt);
353,750,652✔
395
                }
396
              }
397
            }
398

399
            pprod[k] += f * xs[k] * (*p.yield_)(E);
2,147,483,647✔
400
          }
401
        }
402
      }
403

404
      // Skip redundant reactions
405
      if (rx->redundant_)
759,692✔
406
        continue;
101,249✔
407

408
      // Add contribution to total cross section
409
      auto total = xt::view(xs_[t], xt::range(j, j + n), XS_TOTAL);
658,443✔
410
      total += xs;
658,443✔
411

412
      // Add contribution to absorption cross section
413
      auto absorption = xt::view(xs_[t], xt::range(j, j + n), XS_ABSORPTION);
658,443✔
414
      if (is_disappearance(rx->mt_)) {
658,443✔
415
        absorption += xs;
257,988✔
416
      }
417

418
      if (is_fission(rx->mt_)) {
658,443✔
419
        fissionable_ = true;
7,805✔
420
        auto fission = xt::view(xs_[t], xt::range(j, j + n), XS_FISSION);
7,805✔
421
        fission += xs;
7,805✔
422
        absorption += xs;
7,805✔
423

424
        // Keep track of fission reactions
425
        if (t == 0) {
7,805✔
426
          fission_rx_.push_back(rx.get());
7,728✔
427
          if (rx->mt_ == N_F)
7,728✔
428
            has_partial_fission_ = true;
1,004✔
429
        }
430
      }
7,805✔
431
    }
860,941✔
432
  }
433

434
  // Determine number of delayed neutron precursors
435
  if (fissionable_) {
16,123✔
436
    for (const auto& product : fission_rx_[0]->products_) {
40,045✔
437
      if (product.emission_mode_ == EmissionMode::delayed) {
35,329✔
438
        ++n_precursor_;
27,114✔
439
      }
440
    }
441
  }
442

443
  // Calculate nu-fission cross section
444
  for (int t = 0; t < kTs_.size(); ++t) {
32,323✔
445
    if (fissionable_) {
16,200✔
446
      int n = grid_[t].energy.size();
4,793✔
447
      for (int i = 0; i < n; ++i) {
354,653,442✔
448
        double E = grid_[t].energy[i];
354,648,649✔
449
        xs_[t](i, XS_NU_FISSION) =
354,648,649✔
450
          nu(E, EmissionMode::total) * xs_[t](i, XS_FISSION);
354,648,649✔
451
      }
452
    }
453
  }
454

455
  if (settings::res_scat_on) {
16,123✔
456
    // Determine if this nuclide should be treated as a resonant scatterer
457
    if (!settings::res_scat_nuclides.empty()) {
64✔
458
      // If resonant nuclides were specified, check the list explicitly
459
      for (const auto& name : settings::res_scat_nuclides) {
160✔
460
        if (name_ == name) {
144✔
461
          resonant_ = true;
48✔
462

463
          // Make sure nuclide has 0K data
464
          if (energy_0K_.empty()) {
48✔
465
            fatal_error("Cannot treat " + name_ +
×
466
                        " as a resonant scatterer "
467
                        "because 0 K elastic scattering data is not present.");
468
          }
469
          break;
48✔
470
        }
471
      }
472
    } else {
473
      // Otherwise, assume that any that have 0 K elastic scattering data
474
      // are resonant
475
      resonant_ = !energy_0K_.empty();
×
476
    }
477

478
    if (resonant_) {
64✔
479
      // Build CDF for 0K elastic scattering
480
      double xs_cdf_sum = 0.0;
48✔
481
      xs_cdf_.resize(energy_0K_.size());
48✔
482
      xs_cdf_[0] = 0.0;
48✔
483

484
      const auto& E = energy_0K_;
48✔
485
      auto& xs = elastic_0K_;
48✔
486
      for (int i = 0; i < E.size() - 1; ++i) {
8,912,576✔
487
        // Negative cross sections result in a CDF that is not monotonically
488
        // increasing. Set all negative xs values to zero.
489
        if (xs[i] < 0.0)
8,912,528✔
490
          xs[i] = 0.0;
×
491

492
        // build xs cdf
493
        xs_cdf_sum +=
8,912,528✔
494
          (std::sqrt(E[i]) * xs[i] + std::sqrt(E[i + 1]) * xs[i + 1]) / 2.0 *
8,912,528✔
495
          (E[i + 1] - E[i]);
8,912,528✔
496
        xs_cdf_[i + 1] = xs_cdf_sum;
8,912,528✔
497
      }
498
    }
499
  }
500
}
16,123✔
501

502
void Nuclide::init_grid()
16,089✔
503
{
504
  int neutron = static_cast<int>(ParticleType::neutron);
16,089✔
505
  double E_min = data::energy_min[neutron];
16,089✔
506
  double E_max = data::energy_max[neutron];
16,089✔
507
  int M = settings::n_log_bins;
16,089✔
508

509
  // Determine equal-logarithmic energy spacing
510
  double spacing = std::log(E_max / E_min) / M;
16,089✔
511

512
  // Create equally log-spaced energy grid
513
  auto umesh = xt::linspace(0.0, M * spacing, M + 1);
16,089✔
514

515
  for (auto& grid : grid_) {
32,255✔
516
    // Resize array for storing grid indices
517
    grid.grid_index.resize(M + 1);
16,166✔
518

519
    // Determine corresponding indices in nuclide grid to energies on
520
    // equal-logarithmic grid
521
    int j = 0;
16,166✔
522
    for (int k = 0; k <= M; ++k) {
129,936,332✔
523
      while (std::log(grid.energy[j + 1] / E_min) <= umesh(k)) {
566,051,299✔
524
        // Ensure that for isotopes where maxval(grid.energy) << E_max that
525
        // there are no out-of-bounds issues.
526
        if (j + 2 == grid.energy.size())
436,141,304✔
527
          break;
10,171✔
528
        ++j;
436,131,133✔
529
      }
530
      grid.grid_index[k] = j;
129,920,166✔
531
    }
532
  }
533
}
16,089✔
534

535
double Nuclide::nu(double E, EmissionMode mode, int group) const
563,344,341✔
536
{
537
  if (!fissionable_)
563,344,341✔
538
    return 0.0;
29,165,488✔
539

540
  switch (mode) {
534,178,853✔
541
  case EmissionMode::prompt:
7,514,848✔
542
    return (*fission_rx_[0]->products_[0].yield_)(E);
7,514,848✔
543
  case EmissionMode::delayed:
127,356,804✔
544
    if (n_precursor_ > 0 && settings::create_delayed_neutrons) {
127,356,804✔
545
      auto rx = fission_rx_[0];
125,493,008✔
546
      if (group >= 1 && group < rx->products_.size()) {
125,493,008✔
547
        // If delayed group specified, determine yield immediately
548
        return (*rx->products_[group].yield_)(E);
107,360,880✔
549
      } else {
550
        double nu {0.0};
18,132,128✔
551

552
        for (int i = 1; i < rx->products_.size(); ++i) {
143,864,997✔
553
          // Skip any non-neutron products
554
          const auto& product = rx->products_[i];
125,732,869✔
555
          if (product.particle_ != ParticleType::neutron)
125,732,869✔
556
            continue;
16,940,101✔
557

558
          // Evaluate yield
559
          if (product.emission_mode_ == EmissionMode::delayed) {
108,792,768✔
560
            nu += (*product.yield_)(E);
108,792,768✔
561
          }
562
        }
563
        return nu;
18,132,128✔
564
      }
565
    } else {
566
      return 0.0;
1,863,796✔
567
    }
568
  case EmissionMode::total:
399,307,201✔
569
    if (total_nu_ && settings::create_delayed_neutrons) {
399,307,201✔
570
      return (*total_nu_)(E);
397,573,645✔
571
    } else {
572
      return (*fission_rx_[0]->products_[0].yield_)(E);
1,733,556✔
573
    }
574
  }
575
  UNREACHABLE();
×
576
}
577

578
void Nuclide::calculate_elastic_xs(Particle& p) const
484,333,920✔
579
{
580
  // Get temperature index, grid index, and interpolation factor
581
  auto& micro {p.neutron_xs(index_)};
484,333,920✔
582
  int i_temp = micro.index_temp;
484,333,920✔
583
  int i_grid = micro.index_grid;
484,333,920✔
584
  double f = micro.interp_factor;
484,333,920✔
585

586
  if (i_temp >= 0) {
484,333,920✔
587
    const auto& xs = reactions_[0]->xs_[i_temp].value;
481,114,594✔
588
    micro.elastic = (1.0 - f) * xs[i_grid] + f * xs[i_grid + 1];
481,114,594✔
589
  }
590
}
484,333,920✔
591

592
double Nuclide::elastic_xs_0K(double E) const
×
593
{
594
  // Determine index on nuclide energy grid
595
  int i_grid;
596
  if (E < energy_0K_.front()) {
×
597
    i_grid = 0;
×
598
  } else if (E > energy_0K_.back()) {
×
599
    i_grid = energy_0K_.size() - 2;
×
600
  } else {
601
    i_grid = lower_bound_index(energy_0K_.begin(), energy_0K_.end(), E);
×
602
  }
603

604
  // check for rare case where two energy points are the same
605
  if (energy_0K_[i_grid] == energy_0K_[i_grid + 1])
×
606
    ++i_grid;
×
607

608
  // calculate interpolation factor
609
  double f =
610
    (E - energy_0K_[i_grid]) / (energy_0K_[i_grid + 1] - energy_0K_[i_grid]);
×
611

612
  // Calculate microscopic nuclide elastic cross section
613
  return (1.0 - f) * elastic_0K_[i_grid] + f * elastic_0K_[i_grid + 1];
×
614
}
615

616
void Nuclide::calculate_xs(
1,929,586,393✔
617
  int i_sab, int i_log_union, double sab_frac, Particle& p)
618
{
619
  auto& micro {p.neutron_xs(index_)};
1,929,586,393✔
620

621
  // Initialize cached cross sections to zero
622
  micro.elastic = CACHE_INVALID;
1,929,586,393✔
623
  micro.thermal = 0.0;
1,929,586,393✔
624
  micro.thermal_elastic = 0.0;
1,929,586,393✔
625

626
  // Check to see if there is multipole data present at this energy
627
  bool use_mp = false;
1,929,586,393✔
628
  if (multipole_) {
1,929,586,393✔
629
    use_mp = (p.E() >= multipole_->E_min_ && p.E() <= multipole_->E_max_);
9,119,440✔
630
  }
631

632
  // Evaluate multipole or interpolate
633
  if (use_mp) {
1,929,586,393✔
634
    // Call multipole kernel
635
    double sig_s, sig_a, sig_f;
636
    std::tie(sig_s, sig_a, sig_f) = multipole_->evaluate(p.E(), p.sqrtkT());
8,781,542✔
637

638
    micro.total = sig_s + sig_a;
8,781,542✔
639
    micro.elastic = sig_s;
8,781,542✔
640
    micro.absorption = sig_a;
8,781,542✔
641
    micro.fission = sig_f;
8,781,542✔
642
    micro.nu_fission =
8,781,542✔
643
      fissionable_ ? sig_f * this->nu(p.E(), EmissionMode::total) : 0.0;
8,781,542✔
644

645
    if (simulation::need_depletion_rx) {
8,781,542✔
646
      // Only non-zero reaction is (n,gamma)
647
      micro.reaction[0] = sig_a - sig_f;
×
648

649
      // Set all other reaction cross sections to zero
650
      for (int i = 1; i < DEPLETION_RX.size(); ++i) {
×
651
        micro.reaction[i] = 0.0;
×
652
      }
653
    }
654

655
    /*
656
     * index_temp, index_grid, and interp_factor are used only in the
657
     * following places:
658
     *   1. physics.cpp - scatter - For inelastic scatter.
659
     *   2. physics.cpp - sample_fission - For partial fissions.
660
     *   3. tallies/tally_scoring.cpp - score_general -
661
     *        For tallying on MTxxx reactions.
662
     *   4. nuclide.cpp - calculate_urr_xs - For unresolved purposes.
663
     * It is worth noting that none of these occur in the resolved resonance
664
     * range, so the value here does not matter.  index_temp is set to -1 to
665
     * force a segfault in case a developer messes up and tries to use it with
666
     * multipole.
667
     *
668
     * However, a segfault is not necessarily guaranteed with an out-of-bounds
669
     * access, so this technique should be replaced by something more robust
670
     * in the future.
671
     */
672
    micro.index_temp = -1;
8,781,542✔
673
    micro.index_grid = -1;
8,781,542✔
674
    micro.interp_factor = 0.0;
8,781,542✔
675

676
  } else {
677
    // Find the appropriate temperature index.
678
    double kT = p.sqrtkT() * p.sqrtkT();
1,920,804,851✔
679
    double f;
680
    int i_temp = -1;
1,920,804,851✔
681
    switch (settings::temperature_method) {
1,920,804,851✔
682
    case TemperatureMethod::NEAREST: {
1,919,287,896✔
683
      double max_diff = INFTY;
1,919,287,896✔
684
      for (int t = 0; t < kTs_.size(); ++t) {
2,147,483,647✔
685
        double diff = std::abs(kTs_[t] - kT);
1,919,287,896✔
686
        if (diff < max_diff) {
1,919,287,896✔
687
          i_temp = t;
1,919,287,896✔
688
          max_diff = diff;
1,919,287,896✔
689
        }
690
      }
691
    } break;
1,919,287,896✔
692

693
    case TemperatureMethod::INTERPOLATION:
1,516,955✔
694
      // If current kT outside of the bounds of available, snap to the bound
695
      if (kT < kTs_.front()) {
1,516,955✔
696
        i_temp = 0;
413,226✔
697
        break;
413,226✔
698
      }
699
      if (kT > kTs_.back()) {
1,103,729✔
700
        i_temp = kTs_.size() - 1;
217,228✔
701
        break;
217,228✔
702
      }
703

704
      // Find temperatures that bound the actual temperature
705
      for (i_temp = 0; i_temp < kTs_.size() - 1; ++i_temp) {
886,501✔
706
        if (kTs_[i_temp] <= kT && kT < kTs_[i_temp + 1])
886,501✔
707
          break;
886,501✔
708
      }
709

710
      // Randomly sample between temperature i and i+1
711
      f = (kT - kTs_[i_temp]) / (kTs_[i_temp + 1] - kTs_[i_temp]);
886,501✔
712
      if (f > prn(p.current_seed()))
886,501✔
713
        ++i_temp;
411,873✔
714
      break;
886,501✔
715
    }
716

717
    // Determine the energy grid index using a logarithmic mapping to
718
    // reduce the energy range over which a binary search needs to be
719
    // performed
720

721
    const auto& grid {grid_[i_temp]};
1,920,804,851✔
722
    const auto& xs {xs_[i_temp]};
1,920,804,851✔
723

724
    int i_grid;
725
    if (p.E() < grid.energy.front()) {
1,920,804,851✔
726
      i_grid = 0;
593✔
727
    } else if (p.E() > grid.energy.back()) {
1,920,804,258✔
728
      i_grid = grid.energy.size() - 2;
×
729
    } else {
730
      // Determine bounding indices based on which equal log-spaced
731
      // interval the energy is in
732
      int i_low = grid.grid_index[i_log_union];
1,920,804,258✔
733
      int i_high = grid.grid_index[i_log_union + 1] + 1;
1,920,804,258✔
734

735
      // Perform binary search over reduced range
736
      i_grid = i_low + lower_bound_index(
1,920,804,258✔
737
                         &grid.energy[i_low], &grid.energy[i_high], p.E());
1,920,804,258✔
738
    }
739

740
    // check for rare case where two energy points are the same
741
    if (grid.energy[i_grid] == grid.energy[i_grid + 1])
1,920,804,851✔
742
      ++i_grid;
×
743

744
    // calculate interpolation factor
745
    f = (p.E() - grid.energy[i_grid]) /
1,920,804,851✔
746
        (grid.energy[i_grid + 1] - grid.energy[i_grid]);
1,920,804,851✔
747

748
    micro.index_temp = i_temp;
1,920,804,851✔
749
    micro.index_grid = i_grid;
1,920,804,851✔
750
    micro.interp_factor = f;
1,920,804,851✔
751

752
    // Calculate microscopic nuclide total cross section
753
    micro.total =
1,920,804,851✔
754
      (1.0 - f) * xs(i_grid, XS_TOTAL) + f * xs(i_grid + 1, XS_TOTAL);
1,920,804,851✔
755

756
    // Calculate microscopic nuclide absorption cross section
757
    micro.absorption =
1,920,804,851✔
758
      (1.0 - f) * xs(i_grid, XS_ABSORPTION) + f * xs(i_grid + 1, XS_ABSORPTION);
1,920,804,851✔
759

760
    if (fissionable_) {
1,920,804,851✔
761
      // Calculate microscopic nuclide total cross section
762
      micro.fission =
191,492,776✔
763
        (1.0 - f) * xs(i_grid, XS_FISSION) + f * xs(i_grid + 1, XS_FISSION);
191,492,776✔
764

765
      // Calculate microscopic nuclide nu-fission cross section
766
      micro.nu_fission = (1.0 - f) * xs(i_grid, XS_NU_FISSION) +
191,492,776✔
767
                         f * xs(i_grid + 1, XS_NU_FISSION);
191,492,776✔
768
    } else {
769
      micro.fission = 0.0;
1,729,312,075✔
770
      micro.nu_fission = 0.0;
1,729,312,075✔
771
    }
772

773
    // Calculate microscopic nuclide photon production cross section
774
    micro.photon_prod = (1.0 - f) * xs(i_grid, XS_PHOTON_PROD) +
1,920,804,851✔
775
                        f * xs(i_grid + 1, XS_PHOTON_PROD);
1,920,804,851✔
776

777
    // Depletion-related reactions
778
    if (simulation::need_depletion_rx) {
1,920,804,851✔
779
      // Initialize all reaction cross sections to zero
780
      for (double& xs_i : micro.reaction) {
×
781
        xs_i = 0.0;
×
782
      }
783

784
      for (int j = 0; j < DEPLETION_RX.size(); ++j) {
×
785
        // If reaction is present and energy is greater than threshold, set
786
        // the reaction xs appropriately
787
        int i_rx = reaction_index_[DEPLETION_RX[j]];
×
788
        if (i_rx >= 0) {
×
789
          const auto& rx = reactions_[i_rx];
×
790
          const auto& rx_xs = rx->xs_[i_temp].value;
×
791

792
          // Physics says that (n,gamma) is not a threshold reaction, so we
793
          // don't need to specifically check its threshold index
794
          if (j == 0) {
×
795
            micro.reaction[0] =
×
796
              (1.0 - f) * rx_xs[i_grid] + f * rx_xs[i_grid + 1];
×
797
            continue;
×
798
          }
799

800
          int threshold = rx->xs_[i_temp].threshold;
×
801
          if (i_grid >= threshold) {
×
802
            micro.reaction[j] = (1.0 - f) * rx_xs[i_grid - threshold] +
×
803
                                f * rx_xs[i_grid - threshold + 1];
×
804
          } else if (j >= 3) {
×
805
            // One can show that the the threshold for (n,(x+1)n) is always
806
            // higher than the threshold for (n,xn). Thus, if we are below
807
            // the threshold for, e.g., (n,2n), there is no reason to check
808
            // the threshold for (n,3n) and (n,4n).
809
            break;
×
810
          }
811
        }
812
      }
813
    }
814
  }
815

816
  // Initialize sab treatment to false
817
  micro.index_sab = C_NONE;
1,929,586,393✔
818
  micro.sab_frac = 0.0;
1,929,586,393✔
819

820
  // Initialize URR probability table treatment to false
821
  micro.use_ptable = false;
1,929,586,393✔
822

823
  // If there is S(a,b) data for this nuclide, we need to set the
824
  // sab_scatter and sab_elastic cross sections and correct the total and
825
  // elastic cross sections.
826

827
  if (i_sab >= 0)
1,929,586,393✔
828
    this->calculate_sab_xs(i_sab, sab_frac, p);
39,037,688✔
829

830
  // If the particle is in the unresolved resonance range and there are
831
  // probability tables, we need to determine cross sections from the table
832
  if (settings::urr_ptables_on && urr_present_ && !use_mp) {
1,929,586,393✔
833
    if (urr_data_[micro.index_temp].energy_in_bounds(p.E()))
369,518,014✔
834
      this->calculate_urr_xs(micro.index_temp, p);
41,269,221✔
835
  }
836

837
  micro.last_E = p.E();
1,929,586,393✔
838
  micro.last_sqrtkT = p.sqrtkT();
1,929,586,393✔
839
}
1,929,586,393✔
840

841
void Nuclide::calculate_sab_xs(int i_sab, double sab_frac, Particle& p)
39,037,688✔
842
{
843
  auto& micro {p.neutron_xs(index_)};
39,037,688✔
844

845
  // Set flag that S(a,b) treatment should be used for scattering
846
  micro.index_sab = i_sab;
39,037,688✔
847

848
  // Calculate the S(a,b) cross section
849
  int i_temp;
850
  double elastic;
851
  double inelastic;
852
  data::thermal_scatt[i_sab]->calculate_xs(
78,075,376✔
853
    p.E(), p.sqrtkT(), &i_temp, &elastic, &inelastic, p.current_seed());
39,037,688✔
854

855
  // Store the S(a,b) cross sections.
856
  micro.thermal = sab_frac * (elastic + inelastic);
39,037,688✔
857
  micro.thermal_elastic = sab_frac * elastic;
39,037,688✔
858

859
  // Calculate free atom elastic cross section
860
  this->calculate_elastic_xs(p);
39,037,688✔
861

862
  // Correct total and elastic cross sections
863
  micro.total = micro.total + micro.thermal - sab_frac * micro.elastic;
39,037,688✔
864
  micro.elastic = micro.thermal + (1.0 - sab_frac) * micro.elastic;
39,037,688✔
865

866
  // Save temperature index and thermal fraction
867
  micro.index_temp_sab = i_temp;
39,037,688✔
868
  micro.sab_frac = sab_frac;
39,037,688✔
869
}
39,037,688✔
870

871
void Nuclide::calculate_urr_xs(int i_temp, Particle& p) const
41,269,221✔
872
{
873
  auto& micro = p.neutron_xs(index_);
41,269,221✔
874
  micro.use_ptable = true;
41,269,221✔
875

876
  // Create a shorthand for the URR data
877
  const auto& urr = urr_data_[i_temp];
41,269,221✔
878

879
  // Determine the energy table
880
  int i_energy =
881
    lower_bound_index(urr.energy_.begin(), urr.energy_.end(), p.E());
41,269,221✔
882

883
  // Sample the probability table using the cumulative distribution
884

885
  // Random numbers for the xs calculation are sampled from a separate stream.
886
  // This guarantees the randomness and, at the same time, makes sure we
887
  // reuse random numbers for the same nuclide at different temperatures,
888
  // therefore preserving correlation of temperature in probability tables.
889
  double r =
890
    future_prn(static_cast<int64_t>(index_), p.seeds(STREAM_URR_PTABLE));
41,269,221✔
891

892
  // Warning: this assumes row-major order of cdf_values_
893
  int i_low = upper_bound_index(&urr.cdf_values_(i_energy, 0),
41,269,221✔
894
                &urr.cdf_values_(i_energy, 0) + urr.n_cdf(), r) +
41,269,221✔
895
              1;
41,269,221✔
896
  int i_up = upper_bound_index(&urr.cdf_values_(i_energy + 1, 0),
41,269,221✔
897
               &urr.cdf_values_(i_energy + 1, 0) + urr.n_cdf(), r) +
41,269,221✔
898
             1;
41,269,221✔
899

900
  // Determine elastic, fission, and capture cross sections from the
901
  // probability table
902
  double elastic = 0.;
41,269,221✔
903
  double fission = 0.;
41,269,221✔
904
  double capture = 0.;
41,269,221✔
905
  double f;
906
  if (urr.interp_ == Interpolation::lin_lin) {
41,269,221✔
907
    // Determine the interpolation factor on the table
908
    f = (p.E() - urr.energy_[i_energy]) /
26,222,559✔
909
        (urr.energy_[i_energy + 1] - urr.energy_[i_energy]);
26,222,559✔
910

911
    elastic = (1. - f) * urr.xs_values_(i_energy, i_low).elastic +
26,222,559✔
912
              f * urr.xs_values_(i_energy + 1, i_up).elastic;
26,222,559✔
913
    fission = (1. - f) * urr.xs_values_(i_energy, i_low).fission +
26,222,559✔
914
              f * urr.xs_values_(i_energy + 1, i_up).fission;
26,222,559✔
915
    capture = (1. - f) * urr.xs_values_(i_energy, i_low).n_gamma +
26,222,559✔
916
              f * urr.xs_values_(i_energy + 1, i_up).n_gamma;
26,222,559✔
917
  } else if (urr.interp_ == Interpolation::log_log) {
15,046,662✔
918
    // Determine interpolation factor on the table
919
    f = std::log(p.E() / urr.energy_[i_energy]) /
15,046,662✔
920
        std::log(urr.energy_[i_energy + 1] / urr.energy_[i_energy]);
15,046,662✔
921

922
    // Calculate the elastic cross section/factor
923
    if ((urr.xs_values_(i_energy, i_low).elastic > 0.) &&
30,093,324✔
924
        (urr.xs_values_(i_energy + 1, i_up).elastic > 0.)) {
15,046,662✔
925
      elastic =
926
        std::exp((1. - f) * std::log(urr.xs_values_(i_energy, i_low).elastic) +
15,046,662✔
927
                 f * std::log(urr.xs_values_(i_energy + 1, i_up).elastic));
15,046,662✔
928
    } else {
929
      elastic = 0.;
×
930
    }
931

932
    // Calculate the fission cross section/factor
933
    if ((urr.xs_values_(i_energy, i_low).fission > 0.) &&
27,137,089✔
934
        (urr.xs_values_(i_energy + 1, i_up).fission > 0.)) {
12,090,427✔
935
      fission =
936
        std::exp((1. - f) * std::log(urr.xs_values_(i_energy, i_low).fission) +
12,090,427✔
937
                 f * std::log(urr.xs_values_(i_energy + 1, i_up).fission));
12,090,427✔
938
    } else {
939
      fission = 0.;
2,956,235✔
940
    }
941

942
    // Calculate the capture cross section/factor
943
    if ((urr.xs_values_(i_energy, i_low).n_gamma > 0.) &&
30,093,324✔
944
        (urr.xs_values_(i_energy + 1, i_up).n_gamma > 0.)) {
15,046,662✔
945
      capture =
946
        std::exp((1. - f) * std::log(urr.xs_values_(i_energy, i_low).n_gamma) +
15,046,662✔
947
                 f * std::log(urr.xs_values_(i_energy + 1, i_up).n_gamma));
15,046,662✔
948
    } else {
949
      capture = 0.;
×
950
    }
951
  }
952

953
  // Determine the treatment of inelastic scattering
954
  double inelastic = 0.;
41,269,221✔
955
  if (urr.inelastic_flag_ != C_NONE) {
41,269,221✔
956
    // get interpolation factor
957
    f = micro.interp_factor;
29,210,424✔
958

959
    // Determine inelastic scattering cross section
960
    Reaction* rx = reactions_[urr_inelastic_].get();
29,210,424✔
961
    int xs_index = micro.index_grid - rx->xs_[i_temp].threshold;
29,210,424✔
962
    if (xs_index >= 0) {
29,210,424✔
963
      inelastic = (1. - f) * rx->xs_[i_temp].value[xs_index] +
15,171,163✔
964
                  f * rx->xs_[i_temp].value[xs_index + 1];
15,171,163✔
965
    }
966
  }
967

968
  // Multiply by smooth cross-section if needed
969
  if (urr.multiply_smooth_) {
41,269,221✔
970
    calculate_elastic_xs(p);
15,099,438✔
971
    elastic *= micro.elastic;
15,099,438✔
972
    capture *= (micro.absorption - micro.fission);
15,099,438✔
973
    fission *= micro.fission;
15,099,438✔
974
  }
975

976
  // Check for negative values
977
  if (elastic < 0.) {
41,269,221✔
978
    elastic = 0.;
×
979
  }
980
  if (fission < 0.) {
41,269,221✔
981
    fission = 0.;
×
982
  }
983
  if (capture < 0.) {
41,269,221✔
984
    capture = 0.;
×
985
  }
986

987
  // Set elastic, absorption, fission, total, and capture x/s. Note that the
988
  // total x/s is calculated as a sum of partials instead of the
989
  // table-provided value
990
  micro.elastic = elastic;
41,269,221✔
991
  micro.absorption = capture + fission;
41,269,221✔
992
  micro.fission = fission;
41,269,221✔
993
  micro.total = elastic + inelastic + capture + fission;
41,269,221✔
994
  if (simulation::need_depletion_rx) {
41,269,221✔
995
    micro.reaction[0] = capture;
×
996
  }
997

998
  // Determine nu-fission cross-section
999
  if (fissionable_) {
41,269,221✔
1000
    micro.nu_fission = nu(p.E(), EmissionMode::total) * micro.fission;
28,946,908✔
1001
  }
1002
}
41,269,221✔
1003

1004
std::pair<int64_t, double> Nuclide::find_temperature(double T) const
×
1005
{
1006
  assert(T >= 0.0);
1007

1008
  // Determine temperature index
1009
  int64_t i_temp = 0;
×
1010
  double f = 0.0;
×
1011
  double kT = K_BOLTZMANN * T;
×
1012
  int64_t n = kTs_.size();
×
1013
  switch (settings::temperature_method) {
×
1014
  case TemperatureMethod::NEAREST: {
×
1015
    double max_diff = INFTY;
×
1016
    for (int64_t t = 0; t < n; ++t) {
×
1017
      double diff = std::abs(kTs_[t] - kT);
×
1018
      if (diff < max_diff) {
×
1019
        i_temp = t;
×
1020
        max_diff = diff;
×
1021
      }
1022
    }
1023
  } break;
×
1024

1025
  case TemperatureMethod::INTERPOLATION:
×
1026
    // If current kT outside of the bounds of available, snap to the bound
1027
    if (kT < kTs_.front()) {
×
1028
      i_temp = 0;
×
1029
      break;
×
1030
    }
1031
    if (kT > kTs_.back()) {
×
1032
      i_temp = kTs_.size() - 1;
×
1033
      break;
×
1034
    }
1035
    // Find temperatures that bound the actual temperature
1036
    while (kTs_[i_temp + 1] < kT && i_temp + 1 < n - 1)
×
1037
      ++i_temp;
×
1038

1039
    // Determine interpolation factor
1040
    f = (kT - kTs_[i_temp]) / (kTs_[i_temp + 1] - kTs_[i_temp]);
×
1041
  }
1042

1043
  assert(i_temp >= 0 && i_temp < n);
1044

1045
  return {i_temp, f};
×
1046
}
1047

1048
double Nuclide::collapse_rate(int MT, double temperature,
×
1049
  span<const double> energy, span<const double> flux) const
1050
{
1051
  assert(MT > 0);
1052
  assert(energy.size() > 0);
1053
  assert(energy.size() == flux.size() + 1);
1054

1055
  int i_rx = reaction_index_[MT];
×
1056
  if (i_rx < 0)
×
1057
    return 0.0;
×
1058
  const auto& rx = reactions_[i_rx];
×
1059

1060
  // Determine temperature index
1061
  int64_t i_temp;
1062
  double f;
1063
  std::tie(i_temp, f) = this->find_temperature(temperature);
×
1064

1065
  // Get reaction rate at lower temperature
1066
  const auto& grid_low = grid_[i_temp].energy;
×
1067
  double rr_low = rx->collapse_rate(i_temp, energy, flux, grid_low);
×
1068

1069
  if (f > 0.0) {
×
1070
    // Interpolate between reaction rate at lower and higher temperature
1071
    const auto& grid_high = grid_[i_temp + 1].energy;
×
1072
    double rr_high = rx->collapse_rate(i_temp + 1, energy, flux, grid_high);
×
1073
    return rr_low + f * (rr_high - rr_low);
×
1074
  } else {
1075
    // If interpolation factor is zero, return reaction rate at lower
1076
    // temperature
1077
    return rr_low;
×
1078
  }
1079
}
1080

1081
//==============================================================================
1082
// Non-member functions
1083
//==============================================================================
1084

1085
void check_data_version(hid_t file_id)
17,965✔
1086
{
1087
  if (attribute_exists(file_id, "version")) {
17,965✔
1088
    vector<int> version;
17,965✔
1089
    read_attribute(file_id, "version", version);
17,965✔
1090
    if (version[0] != HDF5_VERSION[0]) {
17,965✔
1091
      fatal_error("HDF5 data format uses version " +
×
1092
                  std::to_string(version[0]) + "." +
×
1093
                  std::to_string(version[1]) +
×
1094
                  " whereas your installation of "
1095
                  "OpenMC expects version " +
×
1096
                  std::to_string(HDF5_VERSION[0]) + ".x data.");
×
1097
    }
1098
  } else {
17,965✔
1099
    fatal_error("HDF5 data does not indicate a version. Your installation of "
×
1100
                "OpenMC expects version " +
×
1101
                std::to_string(HDF5_VERSION[0]) + ".x data.");
×
1102
  }
1103
}
17,965✔
1104

1105
extern "C" size_t nuclides_size()
×
1106
{
1107
  return data::nuclides.size();
×
1108
}
1109

1110
//==============================================================================
1111
// C API
1112
//==============================================================================
1113

1114
extern "C" int openmc_load_nuclide(const char* name, const double* temps, int n)
16,593✔
1115
{
1116
  if (data::nuclide_map.find(name) == data::nuclide_map.end() ||
49,636✔
1117
      data::nuclide_map.at(name) >= data::elements.size()) {
33,043✔
1118
    LibraryKey key {Library::Type::neutron, name};
16,593✔
1119
    const auto& it = data::library_map.find(key);
16,593✔
1120
    if (it == data::library_map.end()) {
16,593✔
1121
      set_errmsg(
×
1122
        "Nuclide '" + std::string {name} + "' is not present in library.");
×
1123
      return OPENMC_E_DATA;
×
1124
    }
1125

1126
    // Get filename for library containing nuclide
1127
    int idx = it->second;
16,593✔
1128
    const auto& filename = data::libraries[idx].path_;
16,593✔
1129
    write_message(6, "Reading {} from {}", name, filename);
16,593✔
1130

1131
    // Open file and make sure version is sufficient
1132
    hid_t file_id = file_open(filename, 'r');
16,593✔
1133
    check_data_version(file_id);
16,593✔
1134

1135
    // Read nuclide data from HDF5
1136
    hid_t group = open_group(file_id, name);
16,593✔
1137
    vector<double> temperature {temps, temps + n};
16,593✔
1138
    data::nuclides.push_back(make_unique<Nuclide>(group, temperature));
16,593✔
1139

1140
    close_group(group);
16,593✔
1141
    file_close(file_id);
16,593✔
1142

1143
    // Read multipole file into the appropriate entry on the nuclides array
1144
    int i_nuclide = data::nuclide_map.at(name);
16,593✔
1145
    if (settings::temperature_multipole)
16,593✔
1146
      read_multipole_data(i_nuclide);
416✔
1147

1148
    // Read elemental data, if necessary
1149
    if (settings::photon_transport) {
16,593✔
1150
      auto element = to_element(name);
2,156✔
1151
      if (data::element_map.find(element) == data::element_map.end() ||
2,156✔
1152
          data::element_map.at(element) >= data::elements.size()) {
1,078✔
1153
        // Read photon interaction data from HDF5 photon library
1154
        LibraryKey key {Library::Type::photon, element};
535✔
1155
        const auto& it = data::library_map.find(key);
535✔
1156
        if (it == data::library_map.end()) {
535✔
1157
          set_errmsg("Element '" + std::string {element} +
×
1158
                     "' is not present in library.");
1159
          return OPENMC_E_DATA;
×
1160
        }
1161

1162
        int idx = it->second;
535✔
1163
        const auto& filename = data::libraries[idx].path_;
535✔
1164
        write_message(6, "Reading {} from {} ", element, filename);
535✔
1165

1166
        // Open file and make sure version is sufficient
1167
        hid_t file_id = file_open(filename, 'r');
535✔
1168
        check_data_version(file_id);
535✔
1169

1170
        // Read element data from HDF5
1171
        hid_t group = open_group(file_id, element.c_str());
535✔
1172
        data::elements.push_back(make_unique<PhotonInteraction>(group));
535✔
1173

1174
        close_group(group);
535✔
1175
        file_close(file_id);
535✔
1176
      }
535✔
1177
    }
1,078✔
1178
  }
16,593✔
1179
  return 0;
16,593✔
1180
}
1181

1182
extern "C" int openmc_get_nuclide_index(const char* name, int* index)
×
1183
{
1184
  auto it = data::nuclide_map.find(name);
×
1185
  if (it == data::nuclide_map.end()) {
×
1186
    set_errmsg(
×
1187
      "No nuclide named '" + std::string {name} + "' has been loaded.");
×
1188
    return OPENMC_E_DATA;
×
1189
  }
1190
  *index = it->second;
×
1191
  return 0;
×
1192
}
1193

1194
extern "C" int openmc_nuclide_name(int index, const char** name)
×
1195
{
1196
  if (index >= 0 && index < data::nuclides.size()) {
×
1197
    *name = data::nuclides[index]->name_.data();
×
1198
    return 0;
×
1199
  } else {
1200
    set_errmsg("Index in nuclides vector is out of bounds.");
×
1201
    return OPENMC_E_OUT_OF_BOUNDS;
×
1202
  }
1203
}
1204

1205
extern "C" int openmc_nuclide_collapse_rate(int index, int MT,
×
1206
  double temperature, const double* energy, const double* flux, int n,
1207
  double* xs)
1208
{
1209
  if (index < 0 || index >= data::nuclides.size()) {
×
1210
    set_errmsg("Index in nuclides vector is out of bounds.");
×
1211
    return OPENMC_E_OUT_OF_BOUNDS;
×
1212
  }
1213

1214
  try {
1215
    *xs = data::nuclides[index]->collapse_rate(
×
1216
      MT, temperature, {energy, energy + n + 1}, {flux, flux + n});
×
1217
  } catch (const std::out_of_range& e) {
×
1218
    fmt::print("Caught error\n");
×
1219
    set_errmsg(e.what());
×
1220
    return OPENMC_E_OUT_OF_BOUNDS;
×
1221
  }
×
1222
  return 0;
×
1223
}
1224

1225
void nuclides_clear()
5,405✔
1226
{
1227
  data::nuclides.clear();
5,405✔
1228
  data::nuclide_map.clear();
5,405✔
1229
}
5,405✔
1230

1231
bool multipole_in_range(const Nuclide& nuc, double E)
976,525✔
1232
{
1233
  return E >= nuc.multipole_->E_min_ && E <= nuc.multipole_->E_max_;
976,525✔
1234
}
1235

1236
} // 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