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

openmc-dev / openmc / 21077426970

16 Jan 2026 06:56PM UTC coverage: 82.029% (-0.04%) from 82.069%
21077426970

Pull #3740

github

web-flow
Merge ea498539e into 51ea89ccc
Pull Request #3740: Add `chi` cross sections to MGXS machinery

17267 of 23987 branches covered (71.98%)

Branch coverage included in aggregate %.

34 of 44 new or added lines in 3 files covered. (77.27%)

7 existing lines in 2 files now uncovered.

55664 of 64922 relevant lines covered (85.74%)

43515974.52 hits per line

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

76.25
/src/mgxs.cpp
1
#include "openmc/mgxs.h"
2

3
#include <algorithm>
4
#include <cmath>
5
#include <cstdlib>
6
#include <sstream>
7

8
#include "xtensor/xadapt.hpp"
9
#include "xtensor/xmath.hpp"
10
#include "xtensor/xsort.hpp"
11
#include "xtensor/xview.hpp"
12
#include <fmt/core.h>
13

14
#include "openmc/error.h"
15
#include "openmc/math_functions.h"
16
#include "openmc/mgxs_interface.h"
17
#include "openmc/random_lcg.h"
18
#include "openmc/settings.h"
19
#include "openmc/string_utils.h"
20

21
namespace openmc {
22

23
//==============================================================================
24
// Mgxs base-class methods
25
//==============================================================================
26

27
void Mgxs::init(const std::string& in_name, double in_awr,
5,654✔
28
  const vector<double>& in_kTs, bool in_fissionable,
29
  AngleDistributionType in_scatter_format, bool in_is_isotropic,
30
  const vector<double>& in_polar, const vector<double>& in_azimuthal)
31
{
32
  // Set the metadata
33
  name = in_name;
5,654✔
34
  awr = in_awr;
5,654✔
35
  // TODO: Remove adapt when in_KTs is an xtensor
36
  kTs = xt::adapt(in_kTs);
5,654✔
37
  fissionable = in_fissionable;
5,654✔
38
  scatter_format = in_scatter_format;
5,654✔
39
  xs.resize(in_kTs.size());
5,654✔
40
  is_isotropic = in_is_isotropic;
5,654✔
41
  n_pol = in_polar.size();
5,654✔
42
  n_azi = in_azimuthal.size();
5,654✔
43
  polar = in_polar;
5,654✔
44
  azimuthal = in_azimuthal;
5,654✔
45
}
5,654✔
46

47
//==============================================================================
48

49
void Mgxs::metadata_from_hdf5(hid_t xs_id, const vector<double>& temperature,
3,043✔
50
  vector<int>& temps_to_read, int& order_dim)
51
{
52
  // get name
53
  std::string in_name;
3,043✔
54
  get_name(xs_id, in_name);
3,043✔
55
  // remove the leading '/'
56
  in_name = in_name.substr(1);
3,043✔
57

58
  // Get the AWR
59
  double in_awr;
60
  if (attribute_exists(xs_id, "atomic_weight_ratio")) {
3,043✔
61
    read_attr_double(xs_id, "atomic_weight_ratio", &in_awr);
128✔
62
  } else {
63
    in_awr = MACROSCOPIC_AWR;
2,915✔
64
  }
65

66
  // Determine the available temperatures
67
  hid_t kT_group = open_group(xs_id, "kTs");
3,043✔
68
  size_t num_temps = get_num_datasets(kT_group);
3,043✔
69
  char** dset_names = new char*[num_temps];
3,043!
70
  for (int i = 0; i < num_temps; i++) {
7,062✔
71
    dset_names[i] = new char[151];
4,019✔
72
  }
73
  get_datasets(kT_group, dset_names);
3,043✔
74
  vector<size_t> shape = {num_temps};
3,043✔
75
  xt::xarray<double> temps_available(shape);
3,043✔
76
  for (int i = 0; i < num_temps; i++) {
7,062✔
77
    read_double(kT_group, dset_names[i], &temps_available[i], true);
4,019✔
78

79
    // convert eV to Kelvin
80
    temps_available[i] = std::round(temps_available[i] / K_BOLTZMANN);
4,019✔
81

82
    // Done with dset_names, so delete it
83
    delete[] dset_names[i];
4,019!
84
  }
85
  delete[] dset_names;
3,043!
86
  std::sort(temps_available.begin(), temps_available.end());
3,043✔
87

88
  // If only one temperature is available, lets just use nearest temperature
89
  // interpolation
90
  if ((num_temps == 1) &&
3,043✔
91
      (settings::temperature_method == TemperatureMethod::INTERPOLATION)) {
2,547!
92
    warning("Cross sections for " + strtrim(name) + " are only available " +
×
93
            "at one temperature.  Reverting to the nearest temperature " +
×
94
            "method.");
95
    settings::temperature_method = TemperatureMethod::NEAREST;
×
96
  }
97

98
  switch (settings::temperature_method) {
3,043!
99
  case TemperatureMethod::NEAREST:
2,851✔
100
    // Determine actual temperatures to read
101
    for (const auto& T : temperature) {
5,654✔
102
      // Determine the closest temperature value
103
      // NOTE: the below block could be replaced with the following line,
104
      // though this gives a runtime error if using LLVM 20 or newer,
105
      // likely due to a bug in xtensor.
106
      // auto i_closest = xt::argmin(xt::abs(temps_available - T))[0];
107
      double closest = std::numeric_limits<double>::max();
2,803✔
108
      int i_closest = 0;
2,803✔
109
      for (int i = 0; i < temps_available.size(); i++) {
6,214✔
110
        double diff = std::abs(temps_available[i] - T);
3,411✔
111
        if (diff < closest) {
3,411✔
112
          closest = diff;
3,107✔
113
          i_closest = i;
3,107✔
114
        }
115
      }
116

117
      double temp_actual = temps_available[i_closest];
2,803✔
118
      if (std::fabs(temp_actual - T) < settings::temperature_tolerance) {
2,803!
119
        if (std::find(temps_to_read.begin(), temps_to_read.end(),
2,803✔
120
              std::round(temp_actual)) == temps_to_read.end()) {
5,606!
121
          temps_to_read.push_back(std::round(temp_actual));
2,803✔
122
        }
123
      } else {
124
        fatal_error(fmt::format(
×
125
          "MGXS library does not contain cross sections "
126
          "for {} at or near {} K. Available temperatures "
127
          "are {} K. Consider making use of openmc.Settings.temperature "
128
          "to specify how intermediate temperatures are treated.",
129
          in_name, std::round(T), concatenate(temps_available)));
×
130
      }
131
    }
132
    break;
2,851✔
133

134
  case TemperatureMethod::INTERPOLATION:
192✔
135
    for (int i = 0; i < temperature.size(); i++) {
384✔
136
      for (int j = 0; j < num_temps; j++) {
288!
137
        if (j == (num_temps - 1)) {
288!
138
          fatal_error("MGXS Library does not contain cross sections for " +
×
139
                      in_name + " at temperatures that bound " +
×
140
                      std::to_string(std::round(temperature[i])));
×
141
        }
142
        if ((temps_available[j] <= temperature[i]) &&
576!
143
            (temperature[i] < temps_available[j + 1])) {
288✔
144
          if (std::find(temps_to_read.begin(), temps_to_read.end(),
192✔
145
                temps_available[j]) == temps_to_read.end()) {
384!
146
            temps_to_read.push_back(temps_available[j]);
192✔
147
          }
148

149
          if (std::find(temps_to_read.begin(), temps_to_read.end(),
192✔
150
                temps_available[j + 1]) == temps_to_read.end()) {
384!
151
            temps_to_read.push_back(temps_available[j + 1]);
192✔
152
          }
153
          break;
192✔
154
        }
155
      }
156
    }
157
  }
158
  std::sort(temps_to_read.begin(), temps_to_read.end());
3,043✔
159

160
  // Get the library's temperatures
161
  int n_temperature = temps_to_read.size();
3,043✔
162
  vector<double> in_kTs(n_temperature);
6,086✔
163
  for (int i = 0; i < n_temperature; i++) {
6,230✔
164
    std::string temp_str(std::to_string(temps_to_read[i]) + "K");
3,187✔
165

166
    // read exact temperature value
167
    read_double(kT_group, temp_str.c_str(), &in_kTs[i], true);
3,187✔
168
  }
3,187✔
169
  close_group(kT_group);
3,043✔
170

171
  // Load the remaining metadata
172
  AngleDistributionType in_scatter_format;
173
  if (attribute_exists(xs_id, "scatter_format")) {
3,043!
174
    std::string temp_str;
3,043✔
175
    read_attribute(xs_id, "scatter_format", temp_str);
3,043✔
176
    to_lower(strtrim(temp_str));
3,043✔
177
    if (temp_str.compare(0, 8, "legendre") == 0) {
3,043✔
178
      in_scatter_format = AngleDistributionType::LEGENDRE;
2,915✔
179
    } else if (temp_str.compare(0, 9, "histogram") == 0) {
128✔
180
      in_scatter_format = AngleDistributionType::HISTOGRAM;
64✔
181
    } else if (temp_str.compare(0, 7, "tabular") == 0) {
64!
182
      in_scatter_format = AngleDistributionType::TABULAR;
64✔
183
    } else {
184
      fatal_error("Invalid scatter_format option!");
×
185
    }
186
  } else {
3,043✔
187
    in_scatter_format = AngleDistributionType::LEGENDRE;
×
188
  }
189

190
  if (attribute_exists(xs_id, "scatter_shape")) {
3,043!
191
    std::string temp_str;
3,043✔
192
    read_attribute(xs_id, "scatter_shape", temp_str);
3,043✔
193
    to_lower(strtrim(temp_str));
3,043✔
194
    if (temp_str.compare(0, 14, "[g][g\'][order]") != 0) {
3,043!
195
      fatal_error("Invalid scatter_shape option!");
×
196
    }
197
  }
3,043✔
198

199
  bool in_fissionable = false;
3,043✔
200
  if (attribute_exists(xs_id, "fissionable")) {
3,043!
201
    int int_fiss;
202
    read_attr_int(xs_id, "fissionable", &int_fiss);
3,043✔
203
    in_fissionable = int_fiss;
3,043✔
204
  } else {
205
    fatal_error("Fissionable element must be set!");
×
206
  }
207

208
  // Get the library's value for the order
209
  if (attribute_exists(xs_id, "order")) {
3,043!
210
    read_attr_int(xs_id, "order", &order_dim);
3,043✔
211
  } else {
212
    fatal_error("Order must be provided!");
×
213
  }
214

215
  // Store the dimensionality of the data in order_dim.
216
  // For Legendre data, we usually refer to it as Pn where n is the order.
217
  // However Pn has n+1 sets of points (since you need to count the P0
218
  // moment). Adjust for that. Histogram and Tabular formats dont need this
219
  // adjustment.
220
  if (in_scatter_format == AngleDistributionType::LEGENDRE) {
3,043✔
221
    ++order_dim;
2,915✔
222
  }
223

224
  // Get the angular information
225
  int in_n_pol;
226
  int in_n_azi;
227
  bool in_is_isotropic = true;
3,043✔
228
  if (attribute_exists(xs_id, "representation")) {
3,043!
229
    std::string temp_str;
3,043✔
230
    read_attribute(xs_id, "representation", temp_str);
3,043✔
231
    to_lower(strtrim(temp_str));
3,043✔
232
    if (temp_str.compare(0, 5, "angle") == 0) {
3,043✔
233
      in_is_isotropic = false;
32✔
234
    } else if (temp_str.compare(0, 9, "isotropic") != 0) {
3,011!
235
      fatal_error("Invalid Data Representation!");
×
236
    }
237
  }
3,043✔
238

239
  if (!in_is_isotropic) {
3,043✔
240
    if (attribute_exists(xs_id, "num_polar")) {
32!
241
      read_attr_int(xs_id, "num_polar", &in_n_pol);
32✔
242
    } else {
243
      fatal_error("num_polar must be provided!");
×
244
    }
245
    if (attribute_exists(xs_id, "num_azimuthal")) {
32!
246
      read_attr_int(xs_id, "num_azimuthal", &in_n_azi);
32✔
247
    } else {
248
      fatal_error("num_azimuthal must be provided!");
×
249
    }
250
  } else {
251
    in_n_pol = 1;
3,011✔
252
    in_n_azi = 1;
3,011✔
253
  }
254

255
  // Set the angular bins to use equally-spaced bins
256
  vector<double> in_polar(in_n_pol);
6,086✔
257
  double dangle = PI / in_n_pol;
3,043✔
258
  for (int p = 0; p < in_n_pol; p++) {
6,118✔
259
    in_polar[p] = (p + 0.5) * dangle;
3,075✔
260
  }
261
  vector<double> in_azimuthal(in_n_azi);
6,086✔
262
  dangle = 2. * PI / in_n_azi;
3,043✔
263
  for (int a = 0; a < in_n_azi; a++) {
6,118✔
264
    in_azimuthal[a] = (a + 0.5) * dangle - PI;
3,075✔
265
  }
266

267
  // Finally use this data to initialize the MGXS Object
268
  init(in_name, in_awr, in_kTs, in_fissionable, in_scatter_format,
3,043✔
269
    in_is_isotropic, in_polar, in_azimuthal);
270
}
3,043✔
271

272
//==============================================================================
273

274
Mgxs::Mgxs(
3,043✔
275
  hid_t xs_id, const vector<double>& temperature, int num_group, int num_delay)
3,043✔
276
  : num_groups(num_group), num_delayed_groups(num_delay)
3,043✔
277
{
278
  // Call generic data gathering routine (will populate the metadata)
279
  int order_data;
280
  vector<int> temps_to_read;
3,043✔
281
  metadata_from_hdf5(xs_id, temperature, temps_to_read, order_data);
3,043✔
282

283
  // Set number of energy and delayed groups
284
  AngleDistributionType final_scatter_format = scatter_format;
3,043✔
285
  if (settings::legendre_to_tabular) {
3,043✔
286
    if (scatter_format == AngleDistributionType::LEGENDRE)
2,787✔
287
      final_scatter_format = AngleDistributionType::TABULAR;
2,691✔
288
  }
289

290
  // Load the more specific XsData information
291
  for (int t = 0; t < temps_to_read.size(); t++) {
6,230✔
292
    xs[t] = XsData(fissionable, final_scatter_format, n_pol, n_azi, num_groups,
6,374✔
293
      num_delayed_groups);
6,374✔
294
    // Get the temperature as a string and then open the HDF5 group
295
    std::string temp_str = std::to_string(temps_to_read[t]) + "K";
3,187✔
296
    hid_t xsdata_grp = open_group(xs_id, temp_str.c_str());
3,187✔
297

298
    xs[t].from_hdf5(xsdata_grp, fissionable, scatter_format,
3,187✔
299
      final_scatter_format, order_data, is_isotropic, n_pol, n_azi);
3,187✔
300
    close_group(xsdata_grp);
3,187✔
301

302
  } // end temperature loop
3,187✔
303

304
  // Make sure the scattering format is updated to the final case
305
  scatter_format = final_scatter_format;
3,043✔
306
}
3,043✔
307

308
//==============================================================================
309

310
Mgxs::Mgxs(const std::string& in_name, const vector<double>& mat_kTs,
2,611✔
311
  const vector<Mgxs*>& micros, const vector<double>& atom_densities,
312
  int num_group, int num_delay)
2,611✔
313
  : num_groups(num_group), num_delayed_groups(num_delay)
2,611✔
314
{
315
  // Get the minimum data needed to initialize:
316
  // Dont need awr, but lets just initialize it anyways
317
  double in_awr = -1.;
2,611✔
318
  // start with the assumption it is not fissionable and set
319
  // the fissionable status if we learn differently
320
  bool in_fissionable = false;
2,611✔
321
  for (int m = 0; m < micros.size(); m++) {
5,606✔
322
    if (micros[m]->fissionable)
2,995✔
323
      in_fissionable = true;
1,072✔
324
  }
325
  // Force all of the following data to be the same; these will be verified
326
  // to be true later
327
  AngleDistributionType in_scatter_format = micros[0]->scatter_format;
2,611✔
328
  bool in_is_isotropic = micros[0]->is_isotropic;
2,611✔
329
  vector<double> in_polar = micros[0]->polar;
2,611✔
330
  vector<double> in_azimuthal = micros[0]->azimuthal;
2,611✔
331

332
  init(in_name, in_awr, mat_kTs, in_fissionable, in_scatter_format,
2,611✔
333
    in_is_isotropic, in_polar, in_azimuthal);
334

335
  // Create the xs data for each temperature
336
  for (int t = 0; t < mat_kTs.size(); t++) {
5,238✔
337
    xs[t] = XsData(in_fissionable, in_scatter_format, in_polar.size(),
5,254✔
338
      in_azimuthal.size(), num_groups, num_delayed_groups);
5,254✔
339

340
    // Find the right temperature index to use
341
    double temp_desired = mat_kTs[t];
2,627✔
342

343
    // Create the list of temperature indices and interpolation factors for
344
    // each microscopic data at the material temperature
345
    vector<int> micro_t(micros.size(), 0);
2,627✔
346
    vector<double> micro_t_interp(micros.size(), 0.);
2,627✔
347
    for (int m = 0; m < micros.size(); m++) {
5,638✔
348
      switch (settings::temperature_method) {
3,011!
349
      case TemperatureMethod::NEAREST: {
2,819✔
350
        micro_t[m] = xt::argmin(xt::abs(micros[m]->kTs - temp_desired))[0];
2,819✔
351
        auto temp_actual = micros[m]->kTs[micro_t[m]];
2,819✔
352

353
        if (std::abs(temp_actual - temp_desired) >=
2,819✔
354
            K_BOLTZMANN * settings::temperature_tolerance) {
2,819!
355
          fatal_error(fmt::format("MGXS Library does not contain cross section "
×
356
                                  "for {} at or near {} K.",
357
            name, std::round(temp_desired / K_BOLTZMANN)));
×
358
        }
359
      } break;
2,819✔
360
      case TemperatureMethod::INTERPOLATION:
192✔
361
        // Get a list of bounding temperatures for each actual temperature
362
        // present in the model
363
        for (int k = 0; k < micros[m]->kTs.shape()[0] - 1; k++) {
384✔
364
          if ((micros[m]->kTs[k] <= temp_desired) &&
384!
365
              (temp_desired < micros[m]->kTs[k + 1])) {
192!
366
            micro_t[m] = k;
192✔
367
            if (k == 0) {
192!
368
              micro_t_interp[m] = (temp_desired - micros[m]->kTs[k]) /
384✔
369
                                  (micros[m]->kTs[k + 1] - micros[m]->kTs[k]);
192✔
370
            } else {
371
              micro_t_interp[m] = 1.;
×
372
            }
373
          }
374
        }
375
      } // end switch
376
    }   // end microscopic temperature loop
377

378
    // Now combine the microscopic data at each relevant temperature
379
    // We will do this by treating the multiple temperatures of a nuclide as
380
    // a different nuclide. Mathematically this just means the temperature
381
    // interpolant is included in the number density.
382
    // These interpolants are contained within interpolant.
383
    vector<double> interpolant;    // the interpolant for the Mgxs
5,254✔
384
    vector<int> temp_indices;      // the temperature index for each Mgxs
5,254✔
385
    vector<Mgxs*> mgxs_to_combine; // The Mgxs to combine
5,254✔
386
    // Now go through and build the above vectors so that we can use them to
387
    // combine the data. We will step through each microscopic data and
388
    // add in its lower and upper temperature points
389
    for (int m = 0; m < micros.size(); m++) {
5,638✔
390
      if (settings::temperature_method == TemperatureMethod::NEAREST) {
3,011✔
391
        // Nearest interpolation only has one temperature point per isotope
392
        // and so we dont need to include a temperature interpolant in
393
        // the interpolant vector
394
        interpolant.push_back(atom_densities[m]);
2,819✔
395
        temp_indices.push_back(micro_t[m]);
2,819✔
396
        mgxs_to_combine.push_back(micros[m]);
2,819✔
397
      } else {
398
        // This will be an interpolation between two points so get both these
399
        // points
400
        // Start with the low point
401
        interpolant.push_back((1. - micro_t_interp[m]) * atom_densities[m]);
192✔
402
        temp_indices.push_back(micro_t[m]);
192✔
403
        mgxs_to_combine.push_back(micros[m]);
192✔
404
        // The higher point
405
        interpolant.push_back((micro_t_interp[m]) * atom_densities[m]);
192✔
406
        temp_indices.push_back(micro_t[m] + 1);
192✔
407
        mgxs_to_combine.push_back(micros[m]);
192✔
408
      }
409
    }
410

411
    // And finally, combine the data
412
    combine(mgxs_to_combine, interpolant, temp_indices, t);
2,627✔
413
  } // end temperature (t) loop
2,627✔
414
}
2,611✔
415

416
//==============================================================================
417

418
void Mgxs::combine(const vector<Mgxs*>& micros, const vector<double>& scalars,
2,627✔
419
  const vector<int>& micro_ts, int this_t)
420
{
421
  // Build the vector of pointers to the xs objects within micros
422
  vector<XsData*> those_xs(micros.size());
2,627✔
423
  for (int i = 0; i < micros.size(); i++) {
5,830✔
424
    those_xs[i] = &(micros[i]->xs[micro_ts[i]]);
3,203✔
425
  }
426

427
  xs[this_t].combine(those_xs, scalars);
2,627✔
428
}
2,627✔
429

430
//==============================================================================
431

432
double Mgxs::get_xs(MgxsType xstype, int gin, const int* gout, const double* mu,
2,147,483,647✔
433
  const int* dg, int t, int a)
434
{
435
  XsData* xs_t = &xs[t];
2,147,483,647✔
436
  double val;
437
  std::string mgxs_type;
2,147,483,647✔
438
  switch (xstype) {
2,147,483,647!
439
  case MgxsType::TOTAL:
20,631,735✔
440
    val = xs_t->total(a, gin);
20,631,735✔
441
    mgxs_type = "total";
20,631,735✔
442
    break;
20,631,735✔
443
  case MgxsType::NU_FISSION:
10,196,902✔
444
    val = fissionable ? xs_t->nu_fission(a, gin) : 0.;
10,196,902✔
445
    mgxs_type = "nu-fission";
10,196,902✔
446
    break;
10,196,902✔
447
  case MgxsType::ABSORPTION:
5,825,710✔
448
    val = xs_t->absorption(a, gin);
5,825,710✔
449
    mgxs_type = "absorption";
5,825,710✔
450
    ;
451
    break;
5,825,710✔
452
  case MgxsType::FISSION:
11,169,742✔
453
    val = fissionable ? xs_t->fission(a, gin) : 0.;
11,169,742✔
454
    mgxs_type = "fission";
11,169,742✔
455
    break;
11,169,742✔
456
  case MgxsType::KAPPA_FISSION:
10,432,400✔
457
    val = fissionable ? xs_t->kappa_fission(a, gin) : 0.;
10,432,400!
458
    mgxs_type = "kappa-fission";
10,432,400✔
459
    break;
10,432,400✔
460
  case MgxsType::NU_SCATTER:
18,716,708✔
461
  case MgxsType::SCATTER:
462
  case MgxsType::NU_SCATTER_FMU:
463
  case MgxsType::SCATTER_FMU:
464
    val = xs_t->scatter[a]->get_xs(xstype, gin, gout, mu);
18,716,708✔
465
    mgxs_type = "scatter_data";
18,716,708✔
466
    break;
18,716,708✔
467
  case MgxsType::PROMPT_NU_FISSION:
10,188,596✔
468
    val = fissionable ? xs_t->prompt_nu_fission(a, gin) : 0.;
10,188,596!
469
    mgxs_type = "prompt-nu-fission";
10,188,596✔
470
    break;
10,188,596✔
471
  case MgxsType::DELAYED_NU_FISSION:
71,320,172✔
472
    if (fissionable) {
71,320,172!
473
      if (dg != nullptr) {
71,320,172✔
474
        val = xs_t->delayed_nu_fission(a, *dg, gin);
61,131,576✔
475
      } else {
476
        val = 0.;
10,188,596✔
477
        for (int d = 0; d < xs_t->delayed_nu_fission.shape()[1]; d++) {
71,320,172✔
478
          val += xs_t->delayed_nu_fission(a, d, gin);
61,131,576✔
479
        }
480
      }
481
    } else {
482
      val = 0.;
×
483
    }
484
    mgxs_type = "delayed-nu-fission";
71,320,172✔
485
    break;
71,320,172✔
486
  case MgxsType::CHI:
8,306✔
487
    if (fissionable) {
8,306✔
488
      if (gout != nullptr) {
2,752!
489
        val = xs_t->chi(a, gin, *gout);
2,752✔
490
      } else {
491
        // provide an outgoing group-wise sum
NEW
492
        val = 0.;
×
NEW
493
        for (int g = 0; g < xs_t->chi.shape()[2]; g++) {
×
NEW
494
          val += xs_t->chi(a, gin, g);
×
495
        }
496
      }
497
    } else {
498
      val = 0.;
5,554✔
499
    }
500
    mgxs_type = "chi";
8,306✔
501
    break;
8,306✔
UNCOV
502
  case MgxsType::CHI_PROMPT:
×
UNCOV
503
    if (fissionable) {
×
UNCOV
504
      if (gout != nullptr) {
×
UNCOV
505
        val = xs_t->chi_prompt(a, gin, *gout);
×
506
      } else {
507
        // provide an outgoing group-wise sum
508
        val = 0.;
×
509
        for (int g = 0; g < xs_t->chi_prompt.shape()[2]; g++) {
×
510
          val += xs_t->chi_prompt(a, gin, g);
×
511
        }
512
      }
513
    } else {
UNCOV
514
      val = 0.;
×
515
    }
NEW
516
    mgxs_type = "chi-prompt";
×
517
    break;
×
518
  case MgxsType::CHI_DELAYED:
×
519
    if (fissionable) {
×
520
      if (gout != nullptr) {
×
521
        if (dg != nullptr) {
×
522
          val = xs_t->chi_delayed(a, *dg, gin, *gout);
×
523
        } else {
524
          val = xs_t->chi_delayed(a, 0, gin, *gout);
×
525
        }
526
      } else {
527
        if (dg != nullptr) {
×
528
          val = 0.;
×
529
          for (int g = 0; g < xs_t->delayed_nu_fission.shape()[2]; g++) {
×
530
            val += xs_t->delayed_nu_fission(a, *dg, gin, g);
×
531
          }
532
        } else {
533
          val = 0.;
×
534
          for (int g = 0; g < xs_t->delayed_nu_fission.shape()[2]; g++) {
×
535
            for (int d = 0; d < xs_t->delayed_nu_fission.shape()[3]; d++) {
×
536
              val += xs_t->delayed_nu_fission(a, d, gin, g);
×
537
            }
538
          }
539
        }
540
      }
541
    } else {
542
      val = 0.;
×
543
    }
NEW
544
    mgxs_type = "chi-delayed";
×
UNCOV
545
    break;
×
546
  case MgxsType::INVERSE_VELOCITY:
2,079,471,196✔
547
    val = xs_t->inverse_velocity(a, gin);
2,079,471,196✔
548
    mgxs_type = "inverse-velocity";
2,079,471,196✔
549
    break;
2,079,471,196✔
550
  case MgxsType::DECAY_RATE:
61,134,799✔
551
    if (dg != nullptr) {
61,134,799!
552
      val = xs_t->decay_rate(a, *dg);
61,134,799✔
553
    } else {
554
      val = xs_t->decay_rate(a, 0);
×
555
    }
556
    mgxs_type = "decay-rate";
61,134,799✔
557
    break;
61,134,799✔
558
  default:
×
559
    val = 0.;
×
560
  }
561
  // TODO: need to add more robust handling of zero-values cross sections that
562
  // produce NANs on normalization
563
  if (val != val) {
2,147,483,647✔
564
    warning(
576✔
565
      fmt::format("The {} cross section for this material has a nan present "
1,044✔
566
                  "(possibly due to a division by zero). Setting to zero...",
567
        mgxs_type));
568
    val = 0;
576✔
569
  }
570
  return val;
2,147,483,647✔
571
}
2,147,483,647✔
572

573
//==============================================================================
574

575
void Mgxs::sample_fission_energy(
111,089,935✔
576
  int gin, int& dg, int& gout, uint64_t* seed, int t, int a)
577
{
578
  XsData* xs_t = &xs[t];
111,089,935✔
579
  double nu_fission = xs_t->nu_fission(a, gin);
111,089,935✔
580

581
  // Find the probability of having a prompt neutron
582
  double prob_prompt = xs_t->prompt_nu_fission(a, gin);
111,089,935✔
583

584
  // sample random numbers
585
  double xi_pd = prn(seed) * nu_fission;
111,089,935✔
586
  double xi_gout = prn(seed);
111,089,935✔
587

588
  // Select whether the neutron is prompt or delayed
589
  if (xi_pd <= prob_prompt) {
111,089,935✔
590
    // the neutron is prompt
591

592
    // set the delayed group for the particle to be -1, indicating prompt
593
    dg = -1;
111,088,428✔
594

595
    // sample the outgoing energy group
596
    double prob_gout = 0.;
111,088,428✔
597
    for (gout = 0; gout < num_groups; ++gout) {
111,140,150!
598
      prob_gout += xs_t->chi_prompt(a, gin, gout);
111,140,150✔
599
      if (xi_gout < prob_gout)
111,140,150✔
600
        break;
111,088,428✔
601
    }
602

603
  } else {
604
    // the neutron is delayed
605

606
    // get the delayed group
607
    for (dg = 0; dg < num_delayed_groups; ++dg) {
3,795!
608
      prob_prompt += xs_t->delayed_nu_fission(a, dg, gin);
3,795✔
609
      if (xi_pd < prob_prompt)
3,795✔
610
        break;
1,507✔
611
    }
612

613
    // adjust dg in case of round-off error
614
    dg = std::min(dg, num_delayed_groups - 1);
1,507✔
615

616
    // sample the outgoing energy group
617
    double prob_gout = 0.;
1,507✔
618
    for (gout = 0; gout < num_groups; ++gout) {
1,507!
619
      prob_gout += xs_t->chi_delayed(a, dg, gin, gout);
1,507✔
620
      if (xi_gout < prob_gout)
1,507!
621
        break;
1,507✔
622
    }
623
  }
624
}
111,089,935✔
625

626
//==============================================================================
627

628
void Mgxs::sample_scatter(
1,669,415,704✔
629
  int gin, int& gout, double& mu, double& wgt, uint64_t* seed, int t, int a)
630
{
631
  // Sample the data
632
  xs[t].scatter[a]->sample(gin, gout, mu, wgt, seed);
1,669,415,704✔
633
}
1,669,415,704✔
634

635
//==============================================================================
636

637
void Mgxs::calculate_xs(Particle& p)
2,063,937,414✔
638
{
639
  // If the material is different, then we need to do a full lookup
640
  if (p.material() != p.mg_xs_cache().material) {
2,063,937,414✔
641
    set_temperature_index(p);
112,278,623✔
642
    set_angle_index(p);
112,278,623✔
643
    p.mg_xs_cache().material = p.material();
112,278,623✔
644
  } else {
645
    // If material is the same, but temperature is different, need to
646
    // find the new temperature index
647
    if (p.sqrtkT() != p.mg_xs_cache().sqrtkT) {
1,951,658,791✔
648
      set_temperature_index(p);
2,227,056✔
649
    }
650
    // If the material is the same, but angle is different, need to
651
    // find the new angle index
652
    if (p.u_local() != p.mg_xs_cache().u) {
1,951,658,791!
653
      set_angle_index(p);
1,951,658,791✔
654
    }
655
  }
656
  int temperature = p.mg_xs_cache().t;
2,063,937,414✔
657
  int angle = p.mg_xs_cache().a;
2,063,937,414✔
658
  p.macro_xs().total = xs[temperature].total(angle, p.g()) * p.density_mult();
2,063,937,414✔
659
  p.macro_xs().absorption =
2,147,483,647✔
660
    xs[temperature].absorption(angle, p.g()) * p.density_mult();
2,063,937,414✔
661
  p.macro_xs().nu_fission =
2,063,937,414✔
662
    fissionable ? xs[temperature].nu_fission(angle, p.g()) * p.density_mult()
2,063,937,414✔
663
                : 0.;
664
}
2,063,937,414✔
665

666
//==============================================================================
667

668
bool Mgxs::equiv(const Mgxs& that)
×
669
{
670
  return (
671
    (num_delayed_groups == that.num_delayed_groups) &&
×
672
    (num_groups == that.num_groups) && (n_pol == that.n_pol) &&
×
673
    (n_azi == that.n_azi) &&
×
674
    (std::equal(polar.begin(), polar.end(), that.polar.begin())) &&
×
675
    (std::equal(azimuthal.begin(), azimuthal.end(), that.azimuthal.begin())) &&
×
676
    (scatter_format == that.scatter_format));
×
677
}
678

679
//==============================================================================
680

681
int Mgxs::get_temperature_index(double sqrtkT) const
124,689,094✔
682
{
683
  return xt::argmin(xt::abs(kTs - sqrtkT * sqrtkT))[0];
124,689,094✔
684
}
685

686
//==============================================================================
687

688
void Mgxs::set_temperature_index(Particle& p)
114,505,679✔
689
{
690
  p.mg_xs_cache().t = get_temperature_index(p.sqrtkT());
114,505,679✔
691
  p.mg_xs_cache().sqrtkT = p.sqrtkT();
114,505,679✔
692
}
114,505,679✔
693

694
//==============================================================================
695

696
int Mgxs::get_angle_index(const Direction& u) const
2,134,807,873✔
697
{
698
  if (is_isotropic) {
2,134,807,873✔
699
    return 0;
2,134,763,631✔
700
  } else {
701
    // convert direction to polar and azimuthal angles
702
    double my_pol = std::acos(u.z);
44,242✔
703
    double my_azi = std::atan2(u.y, u.x);
44,242✔
704

705
    // Find the location, assuming equal-bin angles
706
    double delta_angle = PI / n_pol;
44,242✔
707
    int p = std::floor(my_pol / delta_angle);
44,242✔
708
    delta_angle = 2. * PI / n_azi;
44,242✔
709
    int a = std::floor((my_azi + PI) / delta_angle);
44,242✔
710

711
    return n_azi * p + a;
44,242✔
712
  }
713
}
714

715
//==============================================================================
716

717
void Mgxs::set_angle_index(Particle& p)
2,063,937,414✔
718
{
719
  // See if we need to find the new index
720
  if (!is_isotropic) {
2,063,937,414✔
721
    p.mg_xs_cache().a = get_angle_index(p.u_local());
22,121✔
722
    p.mg_xs_cache().u = p.u_local();
22,121✔
723
  }
724
}
2,063,937,414✔
725

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

© 2026 Coveralls, Inc