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

openmc-dev / openmc / 10424279536

16 Aug 2024 06:08PM UTC coverage: 84.718% (-0.2%) from 84.9%
10424279536

Pull #3112

github

web-flow
Merge 6ef06d3b8 into 4ef1faf76
Pull Request #3112: Revamp CI with dependency and Python caching for efficient installs

49450 of 58370 relevant lines covered (84.72%)

45353796.23 hits per line

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

86.2
/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,
6,222✔
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;
6,222✔
34
  awr = in_awr;
6,222✔
35
  // TODO: Remove adapt when in_KTs is an xtensor
36
  kTs = xt::adapt(in_kTs);
6,222✔
37
  fissionable = in_fissionable;
6,222✔
38
  scatter_format = in_scatter_format;
6,222✔
39
  xs.resize(in_kTs.size());
6,222✔
40
  is_isotropic = in_is_isotropic;
6,222✔
41
  n_pol = in_polar.size();
6,222✔
42
  n_azi = in_azimuthal.size();
6,222✔
43
  polar = in_polar;
6,222✔
44
  azimuthal = in_azimuthal;
6,222✔
45
}
6,222✔
46

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

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

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

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

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

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

88
  // If only one temperature is available, lets just use nearest temperature
89
  // interpolation
90
  if ((num_temps == 1) &&
3,502✔
91
      (settings::temperature_method == TemperatureMethod::INTERPOLATION)) {
2,448✔
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,502✔
99
  case TemperatureMethod::NEAREST:
3,094✔
100
    // Determine actual temperatures to read
101
    for (const auto& T : temperature) {
6,222✔
102
      auto i_closest = xt::argmin(xt::abs(temps_available - T))[0];
3,128✔
103
      double temp_actual = temps_available[i_closest];
3,128✔
104
      if (std::fabs(temp_actual - T) < settings::temperature_tolerance) {
3,128✔
105
        if (std::find(temps_to_read.begin(), temps_to_read.end(),
3,128✔
106
              std::round(temp_actual)) == temps_to_read.end()) {
6,256✔
107
          temps_to_read.push_back(std::round(temp_actual));
3,128✔
108
        }
109
      } else {
110
        fatal_error(fmt::format(
×
111
          "MGXS library does not contain cross sections "
112
          "for {} at or near {} K. Available temperatures "
113
          "are {} K. Consider making use of openmc.Settings.temperature "
114
          "to specify how intermediate temperatures are treated.",
115
          in_name, std::round(T), concatenate(temps_available)));
×
116
      }
117
    }
118
    break;
3,094✔
119

120
  case TemperatureMethod::INTERPOLATION:
408✔
121
    for (int i = 0; i < temperature.size(); i++) {
816✔
122
      for (int j = 0; j < num_temps; j++) {
612✔
123
        if (j == (num_temps - 1)) {
612✔
124
          fatal_error("MGXS Library does not contain cross sections for " +
×
125
                      in_name + " at temperatures that bound " +
×
126
                      std::to_string(std::round(temperature[i])));
×
127
        }
128
        if ((temps_available[j] <= temperature[i]) &&
1,224✔
129
            (temperature[i] < temps_available[j + 1])) {
612✔
130
          if (std::find(temps_to_read.begin(), temps_to_read.end(),
408✔
131
                temps_available[j]) == temps_to_read.end()) {
816✔
132
            temps_to_read.push_back(temps_available[j]);
408✔
133
          }
134

135
          if (std::find(temps_to_read.begin(), temps_to_read.end(),
408✔
136
                temps_available[j + 1]) == temps_to_read.end()) {
816✔
137
            temps_to_read.push_back(temps_available[j + 1]);
408✔
138
          }
139
          break;
408✔
140
        }
141
      }
142
    }
143
  }
144
  std::sort(temps_to_read.begin(), temps_to_read.end());
3,502✔
145

146
  // Get the library's temperatures
147
  int n_temperature = temps_to_read.size();
3,502✔
148
  vector<double> in_kTs(n_temperature);
7,004✔
149
  for (int i = 0; i < n_temperature; i++) {
7,446✔
150
    std::string temp_str(std::to_string(temps_to_read[i]) + "K");
3,944✔
151

152
    // read exact temperature value
153
    read_double(kT_group, temp_str.c_str(), &in_kTs[i], true);
3,944✔
154
  }
3,944✔
155
  close_group(kT_group);
3,502✔
156

157
  // Load the remaining metadata
158
  AngleDistributionType in_scatter_format;
159
  if (attribute_exists(xs_id, "scatter_format")) {
3,502✔
160
    std::string temp_str;
3,502✔
161
    read_attribute(xs_id, "scatter_format", temp_str);
3,502✔
162
    to_lower(strtrim(temp_str));
3,502✔
163
    if (temp_str.compare(0, 8, "legendre") == 0) {
3,502✔
164
      in_scatter_format = AngleDistributionType::LEGENDRE;
3,230✔
165
    } else if (temp_str.compare(0, 9, "histogram") == 0) {
272✔
166
      in_scatter_format = AngleDistributionType::HISTOGRAM;
136✔
167
    } else if (temp_str.compare(0, 7, "tabular") == 0) {
136✔
168
      in_scatter_format = AngleDistributionType::TABULAR;
136✔
169
    } else {
170
      fatal_error("Invalid scatter_format option!");
×
171
    }
172
  } else {
3,502✔
173
    in_scatter_format = AngleDistributionType::LEGENDRE;
×
174
  }
175

176
  if (attribute_exists(xs_id, "scatter_shape")) {
3,502✔
177
    std::string temp_str;
3,502✔
178
    read_attribute(xs_id, "scatter_shape", temp_str);
3,502✔
179
    to_lower(strtrim(temp_str));
3,502✔
180
    if (temp_str.compare(0, 14, "[g][g\'][order]") != 0) {
3,502✔
181
      fatal_error("Invalid scatter_shape option!");
×
182
    }
183
  }
3,502✔
184

185
  bool in_fissionable = false;
3,502✔
186
  if (attribute_exists(xs_id, "fissionable")) {
3,502✔
187
    int int_fiss;
188
    read_attr_int(xs_id, "fissionable", &int_fiss);
3,502✔
189
    in_fissionable = int_fiss;
3,502✔
190
  } else {
191
    fatal_error("Fissionable element must be set!");
×
192
  }
193

194
  // Get the library's value for the order
195
  if (attribute_exists(xs_id, "order")) {
3,502✔
196
    read_attr_int(xs_id, "order", &order_dim);
3,502✔
197
  } else {
198
    fatal_error("Order must be provided!");
×
199
  }
200

201
  // Store the dimensionality of the data in order_dim.
202
  // For Legendre data, we usually refer to it as Pn where n is the order.
203
  // However Pn has n+1 sets of points (since you need to count the P0
204
  // moment). Adjust for that. Histogram and Tabular formats dont need this
205
  // adjustment.
206
  if (in_scatter_format == AngleDistributionType::LEGENDRE) {
3,502✔
207
    ++order_dim;
3,230✔
208
  }
209

210
  // Get the angular information
211
  int in_n_pol;
212
  int in_n_azi;
213
  bool in_is_isotropic = true;
3,502✔
214
  if (attribute_exists(xs_id, "representation")) {
3,502✔
215
    std::string temp_str;
3,502✔
216
    read_attribute(xs_id, "representation", temp_str);
3,502✔
217
    to_lower(strtrim(temp_str));
3,502✔
218
    if (temp_str.compare(0, 5, "angle") == 0) {
3,502✔
219
      in_is_isotropic = false;
68✔
220
    } else if (temp_str.compare(0, 9, "isotropic") != 0) {
3,434✔
221
      fatal_error("Invalid Data Representation!");
×
222
    }
223
  }
3,502✔
224

225
  if (!in_is_isotropic) {
3,502✔
226
    if (attribute_exists(xs_id, "num_polar")) {
68✔
227
      read_attr_int(xs_id, "num_polar", &in_n_pol);
68✔
228
    } else {
229
      fatal_error("num_polar must be provided!");
×
230
    }
231
    if (attribute_exists(xs_id, "num_azimuthal")) {
68✔
232
      read_attr_int(xs_id, "num_azimuthal", &in_n_azi);
68✔
233
    } else {
234
      fatal_error("num_azimuthal must be provided!");
×
235
    }
236
  } else {
237
    in_n_pol = 1;
3,434✔
238
    in_n_azi = 1;
3,434✔
239
  }
240

241
  // Set the angular bins to use equally-spaced bins
242
  vector<double> in_polar(in_n_pol);
7,004✔
243
  double dangle = PI / in_n_pol;
3,502✔
244
  for (int p = 0; p < in_n_pol; p++) {
7,072✔
245
    in_polar[p] = (p + 0.5) * dangle;
3,570✔
246
  }
247
  vector<double> in_azimuthal(in_n_azi);
7,004✔
248
  dangle = 2. * PI / in_n_azi;
3,502✔
249
  for (int a = 0; a < in_n_azi; a++) {
7,072✔
250
    in_azimuthal[a] = (a + 0.5) * dangle - PI;
3,570✔
251
  }
252

253
  // Finally use this data to initialize the MGXS Object
254
  init(in_name, in_awr, in_kTs, in_fissionable, in_scatter_format,
3,502✔
255
    in_is_isotropic, in_polar, in_azimuthal);
256
}
3,502✔
257

258
//==============================================================================
259

260
Mgxs::Mgxs(
3,502✔
261
  hid_t xs_id, const vector<double>& temperature, int num_group, int num_delay)
3,502✔
262
  : num_groups(num_group), num_delayed_groups(num_delay)
3,502✔
263
{
264
  // Call generic data gathering routine (will populate the metadata)
265
  int order_data;
266
  vector<int> temps_to_read;
3,502✔
267
  metadata_from_hdf5(xs_id, temperature, temps_to_read, order_data);
3,502✔
268

269
  // Set number of energy and delayed groups
270
  AngleDistributionType final_scatter_format = scatter_format;
3,502✔
271
  if (settings::legendre_to_tabular) {
3,502✔
272
    if (scatter_format == AngleDistributionType::LEGENDRE)
2,958✔
273
      final_scatter_format = AngleDistributionType::TABULAR;
2,754✔
274
  }
275

276
  // Load the more specific XsData information
277
  for (int t = 0; t < temps_to_read.size(); t++) {
7,446✔
278
    xs[t] = XsData(fissionable, final_scatter_format, n_pol, n_azi, num_groups,
7,888✔
279
      num_delayed_groups);
7,888✔
280
    // Get the temperature as a string and then open the HDF5 group
281
    std::string temp_str = std::to_string(temps_to_read[t]) + "K";
3,944✔
282
    hid_t xsdata_grp = open_group(xs_id, temp_str.c_str());
3,944✔
283

284
    xs[t].from_hdf5(xsdata_grp, fissionable, scatter_format,
3,944✔
285
      final_scatter_format, order_data, is_isotropic, n_pol, n_azi);
3,944✔
286
    close_group(xsdata_grp);
3,944✔
287

288
  } // end temperature loop
3,944✔
289

290
  // Make sure the scattering format is updated to the final case
291
  scatter_format = final_scatter_format;
3,502✔
292
}
3,502✔
293

294
//==============================================================================
295

296
Mgxs::Mgxs(const std::string& in_name, const vector<double>& mat_kTs,
2,720✔
297
  const vector<Mgxs*>& micros, const vector<double>& atom_densities,
298
  int num_group, int num_delay)
2,720✔
299
  : num_groups(num_group), num_delayed_groups(num_delay)
2,720✔
300
{
301
  // Get the minimum data needed to initialize:
302
  // Dont need awr, but lets just initialize it anyways
303
  double in_awr = -1.;
2,720✔
304
  // start with the assumption it is not fissionable and set
305
  // the fissionable status if we learn differently
306
  bool in_fissionable = false;
2,720✔
307
  for (int m = 0; m < micros.size(); m++) {
6,256✔
308
    if (micros[m]->fissionable)
3,536✔
309
      in_fissionable = true;
1,870✔
310
  }
311
  // Force all of the following data to be the same; these will be verified
312
  // to be true later
313
  AngleDistributionType in_scatter_format = micros[0]->scatter_format;
2,720✔
314
  bool in_is_isotropic = micros[0]->is_isotropic;
2,720✔
315
  vector<double> in_polar = micros[0]->polar;
2,720✔
316
  vector<double> in_azimuthal = micros[0]->azimuthal;
2,720✔
317

318
  init(in_name, in_awr, mat_kTs, in_fissionable, in_scatter_format,
2,720✔
319
    in_is_isotropic, in_polar, in_azimuthal);
320

321
  // Create the xs data for each temperature
322
  for (int t = 0; t < mat_kTs.size(); t++) {
5,474✔
323
    xs[t] = XsData(in_fissionable, in_scatter_format, in_polar.size(),
5,508✔
324
      in_azimuthal.size(), num_groups, num_delayed_groups);
5,508✔
325

326
    // Find the right temperature index to use
327
    double temp_desired = mat_kTs[t];
2,754✔
328

329
    // Create the list of temperature indices and interpolation factors for
330
    // each microscopic data at the material temperature
331
    vector<int> micro_t(micros.size(), 0);
2,754✔
332
    vector<double> micro_t_interp(micros.size(), 0.);
2,754✔
333
    for (int m = 0; m < micros.size(); m++) {
6,324✔
334
      switch (settings::temperature_method) {
3,570✔
335
      case TemperatureMethod::NEAREST: {
3,162✔
336
        micro_t[m] = xt::argmin(xt::abs(micros[m]->kTs - temp_desired))[0];
3,162✔
337
        auto temp_actual = micros[m]->kTs[micro_t[m]];
3,162✔
338

339
        if (std::abs(temp_actual - temp_desired) >=
3,162✔
340
            K_BOLTZMANN * settings::temperature_tolerance) {
3,162✔
341
          fatal_error(fmt::format("MGXS Library does not contain cross section "
×
342
                                  "for {} at or near {} K.",
343
            name, std::round(temp_desired / K_BOLTZMANN)));
×
344
        }
345
      } break;
3,162✔
346
      case TemperatureMethod::INTERPOLATION:
408✔
347
        // Get a list of bounding temperatures for each actual temperature
348
        // present in the model
349
        for (int k = 0; k < micros[m]->kTs.shape()[0] - 1; k++) {
816✔
350
          if ((micros[m]->kTs[k] <= temp_desired) &&
816✔
351
              (temp_desired < micros[m]->kTs[k + 1])) {
408✔
352
            micro_t[m] = k;
408✔
353
            if (k == 0) {
408✔
354
              micro_t_interp[m] = (temp_desired - micros[m]->kTs[k]) /
816✔
355
                                  (micros[m]->kTs[k + 1] - micros[m]->kTs[k]);
408✔
356
            } else {
357
              micro_t_interp[m] = 1.;
×
358
            }
359
          }
360
        }
361
      } // end switch
362
    }   // end microscopic temperature loop
363

364
    // Now combine the microscopic data at each relevant temperature
365
    // We will do this by treating the multiple temperatures of a nuclide as
366
    // a different nuclide. Mathematically this just means the temperature
367
    // interpolant is included in the number density.
368
    // These interpolants are contained within interpolant.
369
    vector<double> interpolant;    // the interpolant for the Mgxs
5,508✔
370
    vector<int> temp_indices;      // the temperature index for each Mgxs
5,508✔
371
    vector<Mgxs*> mgxs_to_combine; // The Mgxs to combine
5,508✔
372
    // Now go through and build the above vectors so that we can use them to
373
    // combine the data. We will step through each microscopic data and
374
    // add in its lower and upper temperature points
375
    for (int m = 0; m < micros.size(); m++) {
6,324✔
376
      if (settings::temperature_method == TemperatureMethod::NEAREST) {
3,570✔
377
        // Nearest interpolation only has one temperature point per isotope
378
        // and so we dont need to include a temperature interpolant in
379
        // the interpolant vector
380
        interpolant.push_back(atom_densities[m]);
3,162✔
381
        temp_indices.push_back(micro_t[m]);
3,162✔
382
        mgxs_to_combine.push_back(micros[m]);
3,162✔
383
      } else {
384
        // This will be an interpolation between two points so get both these
385
        // points
386
        // Start with the low point
387
        interpolant.push_back((1. - micro_t_interp[m]) * atom_densities[m]);
408✔
388
        temp_indices.push_back(micro_t[m]);
408✔
389
        mgxs_to_combine.push_back(micros[m]);
408✔
390
        // The higher point
391
        interpolant.push_back((micro_t_interp[m]) * atom_densities[m]);
408✔
392
        temp_indices.push_back(micro_t[m] + 1);
408✔
393
        mgxs_to_combine.push_back(micros[m]);
408✔
394
      }
395
    }
396

397
    // And finally, combine the data
398
    combine(mgxs_to_combine, interpolant, temp_indices, t);
2,754✔
399
  } // end temperature (t) loop
2,754✔
400
}
2,720✔
401

402
//==============================================================================
403

404
void Mgxs::combine(const vector<Mgxs*>& micros, const vector<double>& scalars,
2,754✔
405
  const vector<int>& micro_ts, int this_t)
406
{
407
  // Build the vector of pointers to the xs objects within micros
408
  vector<XsData*> those_xs(micros.size());
2,754✔
409
  for (int i = 0; i < micros.size(); i++) {
6,732✔
410
    those_xs[i] = &(micros[i]->xs[micro_ts[i]]);
3,978✔
411
  }
412

413
  xs[this_t].combine(those_xs, scalars);
2,754✔
414
}
2,754✔
415

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

418
double Mgxs::get_xs(MgxsType xstype, int gin, const int* gout, const double* mu,
2,147,483,647✔
419
  const int* dg, int t, int a)
420
{
421
  XsData* xs_t = &xs[t];
2,147,483,647✔
422
  double val;
423
  switch (xstype) {
2,147,483,647✔
424
  case MgxsType::TOTAL:
1,382,229,324✔
425
    val = xs_t->total(a, gin);
1,382,229,324✔
426
    break;
1,382,229,324✔
427
  case MgxsType::NU_FISSION:
189,094,528✔
428
    val = fissionable ? xs_t->nu_fission(a, gin) : 0.;
189,094,528✔
429
    break;
189,094,528✔
430
  case MgxsType::ABSORPTION:
12,697,944✔
431
    val = xs_t->absorption(a, gin);
12,697,944✔
432
    ;
433
    break;
12,697,944✔
434
  case MgxsType::FISSION:
27,875,520✔
435
    val = fissionable ? xs_t->fission(a, gin) : 0.;
27,875,520✔
436
    break;
27,875,520✔
437
  case MgxsType::KAPPA_FISSION:
22,746,288✔
438
    val = fissionable ? xs_t->kappa_fission(a, gin) : 0.;
22,746,288✔
439
    break;
22,746,288✔
440
  case MgxsType::NU_SCATTER:
201,446,624✔
441
  case MgxsType::SCATTER:
442
  case MgxsType::NU_SCATTER_FMU:
443
  case MgxsType::SCATTER_FMU:
444
    val = xs_t->scatter[a]->get_xs(xstype, gin, gout, mu);
201,446,624✔
445
    break;
201,446,624✔
446
  case MgxsType::PROMPT_NU_FISSION:
22,216,368✔
447
    val = fissionable ? xs_t->prompt_nu_fission(a, gin) : 0.;
22,216,368✔
448
    break;
22,216,368✔
449
  case MgxsType::DELAYED_NU_FISSION:
155,514,576✔
450
    if (fissionable) {
155,514,576✔
451
      if (dg != nullptr) {
155,514,576✔
452
        val = xs_t->delayed_nu_fission(a, *dg, gin);
133,298,208✔
453
      } else {
454
        val = 0.;
22,216,368✔
455
        for (int d = 0; d < xs_t->delayed_nu_fission.shape()[1]; d++) {
155,514,576✔
456
          val += xs_t->delayed_nu_fission(a, d, gin);
133,298,208✔
457
        }
458
      }
459
    } else {
460
      val = 0.;
×
461
    }
462
    break;
155,514,576✔
463
  case MgxsType::CHI_PROMPT:
161,423,840✔
464
    if (fissionable) {
161,423,840✔
465
      if (gout != nullptr) {
13,768,640✔
466
        val = xs_t->chi_prompt(a, gin, *gout);
13,768,640✔
467
      } else {
468
        // provide an outgoing group-wise sum
469
        val = 0.;
×
470
        for (int g = 0; g < xs_t->chi_prompt.shape()[2]; g++) {
×
471
          val += xs_t->chi_prompt(a, gin, g);
×
472
        }
473
      }
474
    } else {
475
      val = 0.;
147,655,200✔
476
    }
477
    break;
161,423,840✔
478
  case MgxsType::CHI_DELAYED:
×
479
    if (fissionable) {
×
480
      if (gout != nullptr) {
×
481
        if (dg != nullptr) {
×
482
          val = xs_t->chi_delayed(a, *dg, gin, *gout);
×
483
        } else {
484
          val = xs_t->chi_delayed(a, 0, gin, *gout);
×
485
        }
486
      } else {
487
        if (dg != nullptr) {
×
488
          val = 0.;
×
489
          for (int g = 0; g < xs_t->delayed_nu_fission.shape()[2]; g++) {
×
490
            val += xs_t->delayed_nu_fission(a, *dg, gin, g);
×
491
          }
492
        } else {
493
          val = 0.;
×
494
          for (int g = 0; g < xs_t->delayed_nu_fission.shape()[2]; g++) {
×
495
            for (int d = 0; d < xs_t->delayed_nu_fission.shape()[3]; d++) {
×
496
              val += xs_t->delayed_nu_fission(a, d, gin, g);
×
497
            }
498
          }
499
        }
500
      }
501
    } else {
502
      val = 0.;
×
503
    }
504
    break;
×
505
  case MgxsType::INVERSE_VELOCITY:
33,917,808✔
506
    val = xs_t->inverse_velocity(a, gin);
33,917,808✔
507
    break;
33,917,808✔
508
  case MgxsType::DECAY_RATE:
133,302,048✔
509
    if (dg != nullptr) {
133,302,048✔
510
      val = xs_t->decay_rate(a, *dg);
133,302,048✔
511
    } else {
512
      val = xs_t->decay_rate(a, 0);
×
513
    }
514
    break;
133,302,048✔
515
  default:
×
516
    val = 0.;
×
517
  }
518
  return val;
2,147,483,647✔
519
}
520

521
//==============================================================================
522

523
void Mgxs::sample_fission_energy(
242,302,152✔
524
  int gin, int& dg, int& gout, uint64_t* seed, int t, int a)
525
{
526
  XsData* xs_t = &xs[t];
242,302,152✔
527
  double nu_fission = xs_t->nu_fission(a, gin);
242,302,152✔
528

529
  // Find the probability of having a prompt neutron
530
  double prob_prompt = xs_t->prompt_nu_fission(a, gin);
242,302,152✔
531

532
  // sample random numbers
533
  double xi_pd = prn(seed) * nu_fission;
242,302,152✔
534
  double xi_gout = prn(seed);
242,302,152✔
535

536
  // Select whether the neutron is prompt or delayed
537
  if (xi_pd <= prob_prompt) {
242,302,152✔
538
    // the neutron is prompt
539

540
    // set the delayed group for the particle to be -1, indicating prompt
541
    dg = -1;
242,298,792✔
542

543
    // sample the outgoing energy group
544
    double prob_gout = 0.;
242,298,792✔
545
    for (gout = 0; gout < num_groups; ++gout) {
242,406,648✔
546
      prob_gout += xs_t->chi_prompt(a, gin, gout);
242,406,648✔
547
      if (xi_gout < prob_gout)
242,406,648✔
548
        break;
242,298,792✔
549
    }
550

551
  } else {
552
    // the neutron is delayed
553

554
    // get the delayed group
555
    for (dg = 0; dg < num_delayed_groups; ++dg) {
8,520✔
556
      prob_prompt += xs_t->delayed_nu_fission(a, dg, gin);
8,520✔
557
      if (xi_pd < prob_prompt)
8,520✔
558
        break;
3,360✔
559
    }
560

561
    // adjust dg in case of round-off error
562
    dg = std::min(dg, num_delayed_groups - 1);
3,360✔
563

564
    // sample the outgoing energy group
565
    double prob_gout = 0.;
3,360✔
566
    for (gout = 0; gout < num_groups; ++gout) {
3,360✔
567
      prob_gout += xs_t->chi_delayed(a, dg, gin, gout);
3,360✔
568
      if (xi_gout < prob_gout)
3,360✔
569
        break;
3,360✔
570
    }
571
  }
572
}
242,302,152✔
573

574
//==============================================================================
575

576
void Mgxs::sample_scatter(
2,147,483,647✔
577
  int gin, int& gout, double& mu, double& wgt, uint64_t* seed, int t, int a)
578
{
579
  // Sample the data
580
  xs[t].scatter[a]->sample(gin, gout, mu, wgt, seed);
2,147,483,647✔
581
}
2,147,483,647✔
582

583
//==============================================================================
584

585
void Mgxs::calculate_xs(Particle& p)
2,147,483,647✔
586
{
587
  // If the material is different, then we need to do a full lookup
588
  if (p.material() != p.mg_xs_cache().material) {
2,147,483,647✔
589
    set_temperature_index(p);
237,980,062✔
590
    set_angle_index(p);
237,980,062✔
591
    p.mg_xs_cache().material = p.material();
237,980,062✔
592
  } else {
593
    // If material is the same, but temperature is different, need to
594
    // find the new temperature index
595
    if (p.sqrtkT() != p.mg_xs_cache().sqrtkT) {
2,147,483,647✔
596
      set_temperature_index(p);
4,898,422✔
597
    }
598
    // If the material is the same, but angle is different, need to
599
    // find the new angle index
600
    if (p.u_local() != p.mg_xs_cache().u) {
2,147,483,647✔
601
      set_angle_index(p);
2,147,483,647✔
602
    }
603
  }
604
  int temperature = p.mg_xs_cache().t;
2,147,483,647✔
605
  int angle = p.mg_xs_cache().a;
2,147,483,647✔
606
  p.macro_xs().total = xs[temperature].total(angle, p.g());
2,147,483,647✔
607
  p.macro_xs().absorption = xs[temperature].absorption(angle, p.g());
2,147,483,647✔
608
  p.macro_xs().nu_fission =
2,147,483,647✔
609
    fissionable ? xs[temperature].nu_fission(angle, p.g()) : 0.;
2,147,483,647✔
610
}
2,147,483,647✔
611

612
//==============================================================================
613

614
bool Mgxs::equiv(const Mgxs& that)
×
615
{
616
  return (
617
    (num_delayed_groups == that.num_delayed_groups) &&
×
618
    (num_groups == that.num_groups) && (n_pol == that.n_pol) &&
×
619
    (n_azi == that.n_azi) &&
×
620
    (std::equal(polar.begin(), polar.end(), that.polar.begin())) &&
×
621
    (std::equal(azimuthal.begin(), azimuthal.end(), that.azimuthal.begin())) &&
×
622
    (scatter_format == that.scatter_format));
×
623
}
624

625
//==============================================================================
626

627
int Mgxs::get_temperature_index(double sqrtkT) const
265,079,036✔
628
{
629
  return xt::argmin(xt::abs(kTs - sqrtkT * sqrtkT))[0];
265,079,036✔
630
}
631

632
//==============================================================================
633

634
void Mgxs::set_temperature_index(Particle& p)
242,878,484✔
635
{
636
  p.mg_xs_cache().t = get_temperature_index(p.sqrtkT());
242,878,484✔
637
  p.mg_xs_cache().sqrtkT = p.sqrtkT();
242,878,484✔
638
}
242,878,484✔
639

640
//==============================================================================
641

642
int Mgxs::get_angle_index(const Direction& u) const
72,749,376✔
643
{
644
  if (is_isotropic) {
72,749,376✔
645
    return 0;
72,700,968✔
646
  } else {
647
    // convert direction to polar and azimuthal angles
648
    double my_pol = std::acos(u.z);
48,408✔
649
    double my_azi = std::atan2(u.y, u.x);
48,408✔
650

651
    // Find the location, assuming equal-bin angles
652
    double delta_angle = PI / n_pol;
48,408✔
653
    int p = std::floor(my_pol / delta_angle);
48,408✔
654
    delta_angle = 2. * PI / n_azi;
48,408✔
655
    int a = std::floor((my_azi + PI) / delta_angle);
48,408✔
656

657
    return n_azi * p + a;
48,408✔
658
  }
659
}
660

661
//==============================================================================
662

663
void Mgxs::set_angle_index(Particle& p)
2,147,483,647✔
664
{
665
  // See if we need to find the new index
666
  if (!is_isotropic) {
2,147,483,647✔
667
    p.mg_xs_cache().a = get_angle_index(p.u_local());
48,408✔
668
    p.mg_xs_cache().u = p.u_local();
48,408✔
669
  }
670
}
2,147,483,647✔
671

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