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

openmc-dev / openmc / 17852533892

19 Sep 2025 08:10AM UTC coverage: 85.197% (-0.004%) from 85.201%
17852533892

push

github

web-flow
Combing for fission site sampling, and delayed neutron emission time (#2992)

Co-authored-by: Gavin Ridley <gavin.keith.ridley@gmail.com>
Co-authored-by: Paul Romano <paul.k.romano@gmail.com>

54 of 58 new or added lines in 7 files covered. (93.1%)

4 existing lines in 3 files now uncovered.

53173 of 62412 relevant lines covered (85.2%)

38753013.29 hits per line

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

91.33
/src/scattdata.cpp
1
#include "openmc/scattdata.h"
2

3
#include <algorithm>
4
#include <cmath>
5
#include <numeric>
6

7
#include "xtensor/xbuilder.hpp"
8
#include "xtensor/xview.hpp"
9

10
#include "openmc/constants.h"
11
#include "openmc/error.h"
12
#include "openmc/math_functions.h"
13
#include "openmc/random_lcg.h"
14
#include "openmc/settings.h"
15

16
namespace openmc {
17

18
//==============================================================================
19
// ScattData base-class methods
20
//==============================================================================
21

22
void ScattData::base_init(int order, const xt::xtensor<int, 1>& in_gmin,
7,929✔
23
  const xt::xtensor<int, 1>& in_gmax, const double_2dvec& in_energy,
24
  const double_2dvec& in_mult)
25
{
26
  size_t groups = in_energy.size();
7,929✔
27

28
  gmin = in_gmin;
7,929✔
29
  gmax = in_gmax;
7,929✔
30
  energy.resize(groups);
7,929✔
31
  mult.resize(groups);
7,929✔
32
  dist.resize(groups);
7,929✔
33

34
  for (int gin = 0; gin < groups; gin++) {
34,725✔
35
    // Store the inputted data
36
    energy[gin] = in_energy[gin];
26,796✔
37
    mult[gin] = in_mult[gin];
26,796✔
38

39
    // Make sure the multiplicity does not have 0s
40
    for (int go = 0; go < mult[gin].size(); go++) {
172,702✔
41
      if (mult[gin][go] == 0.) {
145,906✔
42
        mult[gin][go] = 1.;
12,992✔
43
      }
44
    }
45

46
    // Make sure the energy is normalized
47
    double norm = std::accumulate(energy[gin].begin(), energy[gin].end(), 0.);
26,796✔
48

49
    if (norm != 0.) {
26,796✔
50
      for (auto& n : energy[gin])
172,606✔
51
        n /= norm;
145,858✔
52
    }
53

54
    // Initialize the distribution data
55
    dist[gin].resize(in_gmax[gin] - in_gmin[gin] + 1);
26,796✔
56
    for (auto& v : dist[gin]) {
172,702✔
57
      v.resize(order);
145,906✔
58
    }
59
  }
60
}
7,929✔
61

62
//==============================================================================
63

64
void ScattData::base_combine(size_t max_order, size_t order_dim,
2,403✔
65
  const vector<ScattData*>& those_scatts, const vector<double>& scalars,
66
  xt::xtensor<int, 1>& in_gmin, xt::xtensor<int, 1>& in_gmax,
67
  double_2dvec& sparse_mult, double_3dvec& sparse_scatter)
68
{
69
  size_t groups = those_scatts[0]->energy.size();
2,403✔
70

71
  // Now allocate and zero our storage spaces
72
  xt::xtensor<double, 3> this_nuscatt_matrix({groups, groups, order_dim}, 0.);
2,403✔
73
  xt::xtensor<double, 2> this_nuscatt_P0({groups, groups}, 0.);
2,403✔
74
  xt::xtensor<double, 2> this_scatt_P0({groups, groups}, 0.);
2,403✔
75
  xt::xtensor<double, 2> this_mult({groups, groups}, 1.);
2,403✔
76

77
  // Build the dense scattering and multiplicity matrices
78
  for (int i = 0; i < those_scatts.size(); i++) {
5,382✔
79
    ScattData* that = those_scatts[i];
2,979✔
80

81
    // Build the dense matrix for that object
82
    xt::xtensor<double, 3> that_matrix = that->get_matrix(max_order);
2,979✔
83

84
    // Now add that to this for the nu-scatter matrix
85
    this_nuscatt_matrix += scalars[i] * that_matrix;
2,979✔
86

87
    // Do the same with the P0 matrices
88
    for (int gin = 0; gin < groups; gin++) {
12,567✔
89
      for (int go = 0; go < groups; go++) {
266,730✔
90
        this_nuscatt_P0(gin, go) +=
514,284✔
91
          scalars[i] * that->get_xs(MgxsType::NU_SCATTER, gin, &go, nullptr);
257,142✔
92
        this_scatt_P0(gin, go) +=
257,142✔
93
          scalars[i] * that->get_xs(MgxsType::SCATTER, gin, &go, nullptr);
257,142✔
94
      }
95
    }
96
  }
2,979✔
97

98
  // Now we have the dense nuscatt and scatt, we can easily compute the
99
  // multiplicity matrix by dividing the two and fixing any nans
100
  this_mult = xt::nan_to_num(this_nuscatt_P0 / this_scatt_P0);
2,403✔
101

102
  // We have the data, now we need to convert to a jagged array and then use
103
  // the initialize function to store it on the object.
104
  for (int gin = 0; gin < groups; gin++) {
10,887✔
105
    // Find the minimum and maximum group boundaries
106
    int gmin_;
107
    for (gmin_ = 0; gmin_ < groups; gmin_++) {
125,924✔
108
      bool non_zero = false;
125,908✔
109
      for (int l = 0; l < this_nuscatt_matrix.shape()[2]; l++) {
370,516✔
110
        if (this_nuscatt_matrix(gin, gmin_, l) != 0.) {
253,076✔
111
          non_zero = true;
8,468✔
112
          break;
8,468✔
113
        }
114
      }
115
      if (non_zero)
125,908✔
116
        break;
8,468✔
117
    }
118
    int gmax_;
119
    for (gmax_ = groups - 1; gmax_ >= 0; gmax_--) {
98,116✔
120
      bool non_zero = false;
98,100✔
121
      for (int l = 0; l < this_nuscatt_matrix.shape()[2]; l++) {
280,388✔
122
        if (this_nuscatt_matrix(gin, gmax_, l) != 0.) {
190,756✔
123
          non_zero = true;
8,468✔
124
          break;
8,468✔
125
        }
126
      }
127
      if (non_zero)
98,100✔
128
        break;
8,468✔
129
    }
130

131
    // treat the case of all values being 0
132
    if (gmin_ > gmax_) {
8,484✔
133
      gmin_ = gin;
16✔
134
      gmax_ = gin;
16✔
135
    }
136

137
    // Store the group bounds
138
    in_gmin[gin] = gmin_;
8,484✔
139
    in_gmax[gin] = gmax_;
8,484✔
140

141
    // Store the data in the compressed format
142
    sparse_scatter[gin].resize(gmax_ - gmin_ + 1);
8,484✔
143
    sparse_mult[gin].resize(gmax_ - gmin_ + 1);
8,484✔
144
    int i_gout = 0;
8,484✔
145
    for (int gout = gmin_; gout <= gmax_; gout++) {
56,442✔
146
      sparse_scatter[gin][i_gout].resize(this_nuscatt_matrix.shape()[2]);
47,958✔
147
      for (int l = 0; l < this_nuscatt_matrix.shape()[2]; l++) {
171,330✔
148
        sparse_scatter[gin][i_gout][l] = this_nuscatt_matrix(gin, gout, l);
123,372✔
149
      }
150
      sparse_mult[gin][i_gout] = this_mult(gin, gout);
47,958✔
151
      i_gout++;
47,958✔
152
    }
153
  }
154
}
2,403✔
155

156
//==============================================================================
157

158
void ScattData::sample_energy(int gin, int& gout, int& i_gout, uint64_t* seed)
1,669,415,704✔
159
{
160
  // Sample the outgoing group
161
  double xi = prn(seed);
1,669,415,704✔
162
  double prob = 0.;
1,669,415,704✔
163
  i_gout = 0;
1,669,415,704✔
164
  for (gout = gmin[gin]; gout < gmax[gin]; ++gout) {
2,035,752,224✔
165
    prob += energy[gin][i_gout];
1,606,134,706✔
166
    if (xi < prob)
1,606,134,706✔
167
      break;
1,239,798,186✔
168
    ++i_gout;
366,336,520✔
169
  }
170
}
1,669,415,704✔
171

172
//==============================================================================
173

174
double ScattData::get_xs(
19,218,804✔
175
  MgxsType xstype, int gin, const int* gout, const double* mu)
176
{
177
  // Set the outgoing group offset index as needed
178
  int i_gout = 0;
19,218,804✔
179
  if (gout != nullptr) {
19,218,804✔
180
    // short circuit the function if gout is from a zero portion of the
181
    // scattering matrix
182
    if ((*gout < gmin[gin]) || (*gout > gmax[gin])) { // > gmax?
19,218,804✔
183
      return 0.;
620,832✔
184
    }
185
    i_gout = *gout - gmin[gin];
18,597,972✔
186
  }
187

188
  double val = scattxs[gin];
18,597,972✔
189
  switch (xstype) {
18,597,972✔
190
  case MgxsType::NU_SCATTER:
94,926✔
191
    if (gout != nullptr)
94,926✔
192
      val *= energy[gin][i_gout];
94,926✔
193
    break;
94,926✔
194
  case MgxsType::SCATTER:
49,622✔
195
    if (gout != nullptr) {
49,622✔
196
      val *= energy[gin][i_gout] / mult[gin][i_gout];
49,622✔
197
    } else {
198
      val /= std::inner_product(
×
199
        mult[gin].begin(), mult[gin].end(), energy[gin].begin(), 0.0);
×
200
    }
201
    break;
49,622✔
202
  case MgxsType::NU_SCATTER_FMU:
9,226,712✔
203
    if ((gout != nullptr) && (mu != nullptr)) {
9,226,712✔
204
      val *= energy[gin][i_gout] * calc_f(gin, *gout, *mu);
9,226,712✔
205
    } else {
206
      // This is not an expected path (asking for f_mu without asking for a
207
      // group or mu is not useful
208
      fatal_error("Invalid call to get_xs");
×
209
    }
210
    break;
9,226,712✔
211
  case MgxsType::SCATTER_FMU:
9,226,712✔
212
    if ((gout != nullptr) && (mu != nullptr)) {
9,226,712✔
213
      val *= energy[gin][i_gout] * calc_f(gin, *gout, *mu) / mult[gin][i_gout];
9,226,712✔
214
    } else {
215
      // This is not an expected path (asking for f_mu without asking for a
216
      // group or mu is not useful
217
      fatal_error("Invalid call to get_xs");
×
218
    }
219
    break;
9,226,712✔
220
  default:
×
221
    break;
×
222
  }
223
  return val;
18,597,972✔
224
}
225

226
//==============================================================================
227
// ScattDataLegendre methods
228
//==============================================================================
229

230
void ScattDataLegendre::init(const xt::xtensor<int, 1>& in_gmin,
3,107✔
231
  const xt::xtensor<int, 1>& in_gmax, const double_2dvec& in_mult,
232
  const double_3dvec& coeffs)
233
{
234
  size_t groups = coeffs.size();
3,107✔
235
  size_t order = coeffs[0][0].size();
3,107✔
236

237
  // make a copy of coeffs that we can use to both extract data and normalize
238
  double_3dvec matrix = coeffs;
3,107✔
239

240
  // Get the scattering cross section value by summing the un-normalized P0
241
  // coefficient in the variable matrix over all outgoing groups.
242
  scattxs = xt::zeros<double>({groups});
3,107✔
243
  for (int gin = 0; gin < groups; gin++) {
12,951✔
244
    int num_groups = in_gmax[gin] - in_gmin[gin] + 1;
9,844✔
245
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
59,850✔
246
      scattxs[gin] += matrix[gin][i_gout][0];
50,006✔
247
    }
248
  }
249

250
  // Build the energy transfer matrix from data in the variable matrix while
251
  // also normalizing the variable matrix itself
252
  // (forcing the CDF of f(mu=1) == 1)
253
  double_2dvec in_energy;
3,107✔
254
  in_energy.resize(groups);
3,107✔
255
  for (int gin = 0; gin < groups; gin++) {
12,951✔
256
    int num_groups = in_gmax[gin] - in_gmin[gin] + 1;
9,844✔
257
    in_energy[gin].resize(num_groups);
9,844✔
258
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
59,850✔
259
      double norm = matrix[gin][i_gout][0];
50,006✔
260
      in_energy[gin][i_gout] = norm;
50,006✔
261
      if (norm != 0.) {
50,006✔
262
        for (auto& n : matrix[gin][i_gout])
90,028✔
263
          n /= norm;
46,502✔
264
      }
265
    }
266
  }
267

268
  // Initialize the base class attributes
269
  ScattData::base_init(order, in_gmin, in_gmax, in_energy, in_mult);
3,107✔
270

271
  // Set the distribution (sdata.dist) values and initialize max_val
272
  max_val.resize(groups);
3,107✔
273
  for (int gin = 0; gin < groups; gin++) {
12,951✔
274
    int num_groups = gmax[gin] - gmin[gin] + 1;
9,844✔
275
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
59,850✔
276
      dist[gin][i_gout] = matrix[gin][i_gout];
50,006✔
277
    }
278
    max_val[gin].resize(num_groups);
9,844✔
279
    for (auto& n : max_val[gin])
59,850✔
280
      n = 0.;
50,006✔
281
  }
282

283
  // Now update the maximum value
284
  update_max_val();
3,107✔
285
}
3,107✔
286

287
//==============================================================================
288

289
void ScattDataLegendre::update_max_val()
3,107✔
290
{
291
  size_t groups = max_val.size();
3,107✔
292
  // Step through the polynomial with fixed number of points to identify the
293
  // maximal value
294
  int Nmu = 1001;
3,107✔
295
  double dmu = 2. / (Nmu - 1);
3,107✔
296
  for (int gin = 0; gin < groups; gin++) {
12,951✔
297
    int num_groups = gmax[gin] - gmin[gin] + 1;
9,844✔
298
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
59,850✔
299
      for (int imu = 0; imu < Nmu; imu++) {
50,106,012✔
300
        double mu;
301
        if (imu == 0) {
50,056,006✔
302
          mu = -1.;
50,006✔
303
        } else if (imu == (Nmu - 1)) {
50,006,000✔
304
          mu = 1.;
50,006✔
305
        } else {
306
          mu = -1. + (imu - 1) * dmu;
49,955,994✔
307
        }
308

309
        // Calculate probability
310
        double f = evaluate_legendre(
100,112,012✔
311
          dist[gin][i_gout].size() - 1, dist[gin][i_gout].data(), mu);
50,056,006✔
312

313
        // if this is a new maximum, store it
314
        if (f > max_val[gin][i_gout])
50,056,006✔
315
          max_val[gin][i_gout] = f;
1,396,582✔
316
      } // end imu loop
317

318
      // Since we may not have caught the true max, add 10% margin
319
      max_val[gin][i_gout] *= 1.1;
50,006✔
320
    }
321
  }
322
}
3,107✔
323

324
//==============================================================================
325

326
double ScattDataLegendre::calc_f(int gin, int gout, double mu)
46,160,257✔
327
{
328
  double f;
329
  if ((gout < gmin[gin]) || (gout > gmax[gin])) {
46,160,257✔
330
    f = 0.;
×
331
  } else {
332
    int i_gout = gout - gmin[gin];
46,160,257✔
333
    f = evaluate_legendre(
46,160,257✔
334
      dist[gin][i_gout].size() - 1, dist[gin][i_gout].data(), mu);
46,160,257✔
335
  }
336
  return f;
46,160,257✔
337
}
338

339
//==============================================================================
340

341
void ScattDataLegendre::sample(
15,644,299✔
342
  int gin, int& gout, double& mu, double& wgt, uint64_t* seed)
343
{
344
  // Sample the outgoing energy using the base-class method
345
  int i_gout;
346
  sample_energy(gin, gout, i_gout, seed);
15,644,299✔
347

348
  // Now we can sample mu using the scattering kernel using rejection
349
  // sampling from a rectangular bounding box
350
  double M = max_val[gin][i_gout];
15,644,299✔
351
  int samples;
352
  for (samples = 0; samples < MAX_SAMPLE; ++samples) {
27,706,833✔
353
    mu = 2. * prn(seed) - 1.;
27,706,833✔
354
    double f = calc_f(gin, gout, mu);
27,706,833✔
355
    if (f > 0.) {
27,706,833✔
356
      double u = prn(seed) * M;
27,706,833✔
357
      if (u <= f)
27,706,833✔
358
        break;
15,644,299✔
359
    }
360
  }
361
  if (samples == MAX_SAMPLE) {
15,644,299✔
362
    fatal_error("Maximum number of Legendre expansion samples reached!");
×
363
  }
364

365
  // Update the weight to reflect neutron multiplicity
366
  wgt *= mult[gin][i_gout];
15,644,299✔
367
}
15,644,299✔
368

369
//==============================================================================
370

371
void ScattDataLegendre::combine(
272✔
372
  const vector<ScattData*>& those_scatts, const vector<double>& scalars)
373
{
374
  // Find the max order in the data set and make sure we can combine the sets
375
  size_t max_order = 0;
272✔
376
  for (int i = 0; i < those_scatts.size(); i++) {
560✔
377
    // Lets also make sure these items are combineable
378
    ScattDataLegendre* that = dynamic_cast<ScattDataLegendre*>(those_scatts[i]);
288✔
379
    if (!that) {
288✔
380
      fatal_error("Cannot combine the ScattData objects!");
×
381
    }
382
    size_t that_order = that->get_order();
288✔
383
    if (that_order > max_order)
288✔
384
      max_order = that_order;
272✔
385
  }
386

387
  size_t groups = those_scatts[0]->energy.size();
272✔
388

389
  xt::xtensor<int, 1> in_gmin({groups}, 0);
272✔
390
  xt::xtensor<int, 1> in_gmax({groups}, 0);
272✔
391
  double_3dvec sparse_scatter(groups);
272✔
392
  double_2dvec sparse_mult(groups);
272✔
393

394
  // The rest of the steps do not depend on the type of angular representation
395
  // so we use a base class method to sum up xs and create new energy and mult
396
  // matrices
397
  size_t order_dim = max_order + 1;
272✔
398
  ScattData::base_combine(max_order, order_dim, those_scatts, scalars, in_gmin,
272✔
399
    in_gmax, sparse_mult, sparse_scatter);
400

401
  // Got everything we need, store it.
402
  init(in_gmin, in_gmax, sparse_mult, sparse_scatter);
272✔
403
}
272✔
404

405
//==============================================================================
406

407
xt::xtensor<double, 3> ScattDataLegendre::get_matrix(size_t max_order)
288✔
408
{
409
  // Get the sizes and initialize the data to 0
410
  size_t groups = energy.size();
288✔
411
  size_t order_dim = max_order + 1;
288✔
412
  xt::xtensor<double, 3> matrix({groups, groups, order_dim}, 0.);
288✔
413

414
  for (int gin = 0; gin < groups; gin++) {
864✔
415
    for (int i_gout = 0; i_gout < energy[gin].size(); i_gout++) {
1,440✔
416
      int gout = i_gout + gmin[gin];
864✔
417
      for (int l = 0; l < order_dim; l++) {
2,592✔
418
        matrix(gin, gout, l) =
1,728✔
419
          scattxs[gin] * energy[gin][i_gout] * dist[gin][i_gout][l];
1,728✔
420
      }
421
    }
422
  }
423
  return matrix;
288✔
424
}
×
425

426
//==============================================================================
427
// ScattDataHistogram methods
428
//==============================================================================
429

430
void ScattDataHistogram::init(const xt::xtensor<int, 1>& in_gmin,
128✔
431
  const xt::xtensor<int, 1>& in_gmax, const double_2dvec& in_mult,
432
  const double_3dvec& coeffs)
433
{
434
  size_t groups = coeffs.size();
128✔
435
  size_t order = coeffs[0][0].size();
128✔
436

437
  // make a copy of coeffs that we can use to both extract data and normalize
438
  double_3dvec matrix = coeffs;
128✔
439

440
  // Get the scattering cross section value by summing the distribution
441
  // over all the histogram bins in angle and outgoing energy groups
442
  scattxs = xt::zeros<double>({groups});
128✔
443
  for (int gin = 0; gin < groups; gin++) {
384✔
444
    for (int i_gout = 0; i_gout < matrix[gin].size(); i_gout++) {
640✔
445
      scattxs[gin] += std::accumulate(
768✔
446
        matrix[gin][i_gout].begin(), matrix[gin][i_gout].end(), 0.);
768✔
447
    }
448
  }
449

450
  // Build the energy transfer matrix from data in the variable matrix
451
  double_2dvec in_energy;
128✔
452
  in_energy.resize(groups);
128✔
453
  for (int gin = 0; gin < groups; gin++) {
384✔
454
    int num_groups = in_gmax[gin] - in_gmin[gin] + 1;
256✔
455
    in_energy[gin].resize(num_groups);
256✔
456
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
640✔
457
      double norm = std::accumulate(
768✔
458
        matrix[gin][i_gout].begin(), matrix[gin][i_gout].end(), 0.);
768✔
459
      in_energy[gin][i_gout] = norm;
384✔
460
      if (norm != 0.) {
384✔
461
        for (auto& n : matrix[gin][i_gout])
11,232✔
462
          n /= norm;
10,848✔
463
      }
464
    }
465
  }
466

467
  // Initialize the base class attributes
468
  ScattData::base_init(order, in_gmin, in_gmax, in_energy, in_mult);
128✔
469

470
  // Build the angular distribution mu values
471
  mu = xt::linspace(-1., 1., order + 1);
128✔
472
  dmu = 2. / order;
128✔
473

474
  // Calculate f(mu) and integrate it so we can avoid rejection sampling
475
  fmu.resize(groups);
128✔
476
  for (int gin = 0; gin < groups; gin++) {
384✔
477
    int num_groups = gmax[gin] - gmin[gin] + 1;
256✔
478
    fmu[gin].resize(num_groups);
256✔
479
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
640✔
480
      fmu[gin][i_gout].resize(order);
384✔
481
      // The variable matrix contains f(mu); so directly assign it
482
      fmu[gin][i_gout] = matrix[gin][i_gout];
384✔
483

484
      // Integrate the histogram
485
      dist[gin][i_gout][0] = dmu * matrix[gin][i_gout][0];
384✔
486
      for (int imu = 1; imu < order; imu++) {
10,848✔
487
        dist[gin][i_gout][imu] =
10,464✔
488
          dmu * matrix[gin][i_gout][imu] + dist[gin][i_gout][imu - 1];
10,464✔
489
      }
490

491
      // Now re-normalize for integral to unity
492
      double norm = dist[gin][i_gout][order - 1];
384✔
493
      if (norm > 0.) {
384✔
494
        for (int imu = 0; imu < order; imu++) {
11,232✔
495
          fmu[gin][i_gout][imu] /= norm;
10,848✔
496
          dist[gin][i_gout][imu] /= norm;
10,848✔
497
        }
498
      }
499
    }
500
  }
501
}
128✔
502

503
//==============================================================================
504

505
double ScattDataHistogram::calc_f(int gin, int gout, double mu)
×
506
{
507
  double f;
508
  if ((gout < gmin[gin]) || (gout > gmax[gin])) {
×
509
    f = 0.;
×
510
  } else {
511
    // Find mu bin
512
    int i_gout = gout - gmin[gin];
×
513
    int imu;
514
    if (mu == 1.) {
×
515
      // use size -2 to have the index one before the end
516
      imu = this->mu.shape()[0] - 2;
×
517
    } else {
518
      imu = std::floor((mu + 1.) / dmu + 1.) - 1;
×
519
    }
520

521
    f = fmu[gin][i_gout][imu];
×
522
  }
523
  return f;
×
524
}
525

526
//==============================================================================
527

528
void ScattDataHistogram::sample(
31,768✔
529
  int gin, int& gout, double& mu, double& wgt, uint64_t* seed)
530
{
531
  // Sample the outgoing energy using the base-class method
532
  int i_gout;
533
  sample_energy(gin, gout, i_gout, seed);
31,768✔
534

535
  // Determine the outgoing cosine bin
536
  double xi = prn(seed);
31,768✔
537

538
  int imu;
539
  if (xi < dist[gin][i_gout][0]) {
31,768✔
540
    imu = 0;
1,529✔
541
  } else {
542
    imu =
30,239✔
543
      std::upper_bound(dist[gin][i_gout].begin(), dist[gin][i_gout].end(), xi) -
30,239✔
544
      dist[gin][i_gout].begin();
60,478✔
545
  }
546

547
  // Randomly select mu within the imu bin
548
  mu = prn(seed) * dmu + this->mu[imu];
31,768✔
549

550
  if (mu < -1.) {
31,768✔
551
    mu = -1.;
×
552
  } else if (mu > 1.) {
31,768✔
553
    mu = 1.;
×
554
  }
555

556
  // Update the weight to reflect neutron multiplicity
557
  wgt *= mult[gin][i_gout];
31,768✔
558
}
31,768✔
559

560
//==============================================================================
561

562
xt::xtensor<double, 3> ScattDataHistogram::get_matrix(size_t max_order)
64✔
563
{
564
  // Get the sizes and initialize the data to 0
565
  size_t groups = energy.size();
64✔
566
  // We ignore the requested order for Histogram and Tabular representations
567
  size_t order_dim = get_order();
64✔
568
  xt::xtensor<double, 3> matrix({groups, groups, order_dim}, 0);
64✔
569

570
  for (int gin = 0; gin < groups; gin++) {
192✔
571
    for (int i_gout = 0; i_gout < energy[gin].size(); i_gout++) {
320✔
572
      int gout = i_gout + gmin[gin];
192✔
573
      for (int l = 0; l < order_dim; l++) {
5,616✔
574
        matrix(gin, gout, l) =
5,424✔
575
          scattxs[gin] * energy[gin][i_gout] * fmu[gin][i_gout][l];
5,424✔
576
      }
577
    }
578
  }
579
  return matrix;
64✔
580
}
×
581

582
//==============================================================================
583

584
void ScattDataHistogram::combine(
64✔
585
  const vector<ScattData*>& those_scatts, const vector<double>& scalars)
586
{
587
  // Find the max order in the data set and make sure we can combine the sets
588
  size_t max_order = those_scatts[0]->get_order();
64✔
589
  for (int i = 0; i < those_scatts.size(); i++) {
128✔
590
    // Lets also make sure these items are combineable
591
    ScattDataHistogram* that =
592
      dynamic_cast<ScattDataHistogram*>(those_scatts[i]);
64✔
593
    if (!that) {
64✔
594
      fatal_error("Cannot combine the ScattData objects!");
×
595
    }
596
    if (max_order != that->get_order()) {
64✔
597
      fatal_error("Cannot combine the ScattData objects!");
×
598
    }
599
  }
600

601
  size_t groups = those_scatts[0]->energy.size();
64✔
602

603
  xt::xtensor<int, 1> in_gmin({groups}, 0);
64✔
604
  xt::xtensor<int, 1> in_gmax({groups}, 0);
64✔
605
  double_3dvec sparse_scatter(groups);
64✔
606
  double_2dvec sparse_mult(groups);
64✔
607

608
  // The rest of the steps do not depend on the type of angular representation
609
  // so we use a base class method to sum up xs and create new energy and mult
610
  // matrices
611
  size_t order_dim = max_order;
64✔
612
  ScattData::base_combine(max_order, order_dim, those_scatts, scalars, in_gmin,
64✔
613
    in_gmax, sparse_mult, sparse_scatter);
614

615
  // Got everything we need, store it.
616
  init(in_gmin, in_gmax, sparse_mult, sparse_scatter);
64✔
617
}
64✔
618

619
//==============================================================================
620
// ScattDataTabular methods
621
//==============================================================================
622

623
void ScattDataTabular::init(const xt::xtensor<int, 1>& in_gmin,
2,131✔
624
  const xt::xtensor<int, 1>& in_gmax, const double_2dvec& in_mult,
625
  const double_3dvec& coeffs)
626
{
627
  size_t groups = coeffs.size();
2,131✔
628
  size_t order = coeffs[0][0].size();
2,131✔
629

630
  // make a copy of coeffs that we can use to both extract data and normalize
631
  double_3dvec matrix = coeffs;
2,131✔
632

633
  // Build the angular distribution mu values
634
  mu = xt::linspace(-1., 1., order);
2,131✔
635
  dmu = 2. / (order - 1);
2,131✔
636

637
  // Get the scattering cross section value by integrating the distribution
638
  // over all mu points and then combining over all outgoing groups
639
  scattxs = xt::zeros<double>({groups});
2,131✔
640
  for (int gin = 0; gin < groups; gin++) {
10,071✔
641
    for (int i_gout = 0; i_gout < matrix[gin].size(); i_gout++) {
55,082✔
642
      for (int imu = 1; imu < order; imu++) {
119,772✔
643
        scattxs[gin] +=
72,630✔
644
          0.5 * dmu * (matrix[gin][i_gout][imu - 1] + matrix[gin][i_gout][imu]);
72,630✔
645
      }
646
    }
647
  }
648

649
  // Build the energy transfer matrix from data in the variable matrix
650
  double_2dvec in_energy(groups);
2,131✔
651
  for (int gin = 0; gin < groups; gin++) {
10,071✔
652
    int num_groups = in_gmax[gin] - in_gmin[gin] + 1;
7,940✔
653
    in_energy[gin].resize(num_groups);
7,940✔
654
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
55,082✔
655
      double norm = 0.;
47,142✔
656
      for (int imu = 1; imu < order; imu++) {
119,772✔
657
        norm +=
72,630✔
658
          0.5 * dmu * (matrix[gin][i_gout][imu - 1] + matrix[gin][i_gout][imu]);
72,630✔
659
      }
660
      in_energy[gin][i_gout] = norm;
47,142✔
661
    }
662
  }
663

664
  // Initialize the base class attributes
665
  ScattData::base_init(order, in_gmin, in_gmax, in_energy, in_mult);
2,131✔
666

667
  // Calculate f(mu) and integrate it so we can avoid rejection sampling
668
  fmu.resize(groups);
2,131✔
669
  for (int gin = 0; gin < groups; gin++) {
10,071✔
670
    int num_groups = gmax[gin] - gmin[gin] + 1;
7,940✔
671
    fmu[gin].resize(num_groups);
7,940✔
672
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
55,082✔
673
      fmu[gin][i_gout].resize(order);
47,142✔
674
      // The variable matrix contains f(mu); so directly assign it
675
      fmu[gin][i_gout] = matrix[gin][i_gout];
47,142✔
676

677
      // Ensure positivity
678
      for (auto& val : fmu[gin][i_gout]) {
166,914✔
679
        if (val < 0.)
119,772✔
680
          val = 0.;
1,696✔
681
      }
682

683
      // Now re-normalize for numerical integration issues and to take care of
684
      // the above negative fix-up.  Also accrue the CDF
685
      double norm = 0.;
47,142✔
686
      for (int imu = 1; imu < order; imu++) {
119,772✔
687
        norm += 0.5 * dmu * (fmu[gin][i_gout][imu - 1] + fmu[gin][i_gout][imu]);
72,630✔
688
        // incorporate to the CDF
689
        dist[gin][i_gout][imu] = norm;
72,630✔
690
      }
691

692
      // now do the normalization
693
      if (norm > 0.) {
47,142✔
694
        for (int imu = 0; imu < order; imu++) {
144,434✔
695
          fmu[gin][i_gout][imu] /= norm;
104,620✔
696
          dist[gin][i_gout][imu] /= norm;
104,620✔
697
        }
698
      }
699
    }
700
  }
701
}
2,131✔
702

703
//==============================================================================
704

705
double ScattDataTabular::calc_f(int gin, int gout, double mu)
×
706
{
707
  double f;
708
  if ((gout < gmin[gin]) || (gout > gmax[gin])) {
×
709
    f = 0.;
×
710
  } else {
711
    // Find mu bin
712
    int i_gout = gout - gmin[gin];
×
713
    int imu;
714
    if (mu == 1.) {
×
715
      // use size -2 to have the index one before the end
716
      imu = this->mu.shape()[0] - 2;
×
717
    } else {
718
      imu = std::floor((mu + 1.) / dmu + 1.) - 1;
×
719
    }
720

721
    double r = (mu - this->mu[imu]) / (this->mu[imu + 1] - this->mu[imu]);
×
722
    f = (1. - r) * fmu[gin][i_gout][imu] + r * fmu[gin][i_gout][imu + 1];
×
723
  }
724
  return f;
×
725
}
726

727
//==============================================================================
728

729
void ScattDataTabular::sample(
1,653,739,637✔
730
  int gin, int& gout, double& mu, double& wgt, uint64_t* seed)
731
{
732
  // Sample the outgoing energy using the base-class method
733
  int i_gout;
734
  sample_energy(gin, gout, i_gout, seed);
1,653,739,637✔
735

736
  // Determine the outgoing cosine bin
737
  int NP = this->mu.shape()[0];
1,653,739,637✔
738
  double xi = prn(seed);
1,653,739,637✔
739

740
  double c_k = dist[gin][i_gout][0];
1,653,739,637✔
741
  int k;
742
  for (k = 0; k < NP - 1; k++) {
1,667,467,747✔
743
    double c_k1 = dist[gin][i_gout][k + 1];
1,667,467,747✔
744
    if (xi < c_k1)
1,667,467,747✔
745
      break;
1,653,739,637✔
746
    c_k = c_k1;
13,728,110✔
747
  }
748

749
  // Check to make sure k is <= NP - 1
750
  k = std::min(k, NP - 2);
1,653,739,637✔
751

752
  // Find the pdf values we want
753
  double p0 = fmu[gin][i_gout][k];
1,653,739,637✔
754
  double mu0 = this->mu[k];
1,653,739,637✔
755
  double p1 = fmu[gin][i_gout][k + 1];
1,653,739,637✔
756
  double mu1 = this->mu[k + 1];
1,653,739,637✔
757

758
  if (p0 == p1) {
1,653,739,637✔
759
    mu = mu0 + (xi - c_k) / p0;
1,653,032,667✔
760
  } else {
761
    double frac = (p1 - p0) / (mu1 - mu0);
706,970✔
762
    mu =
706,970✔
763
      mu0 +
706,970✔
764
      (std::sqrt(std::max(0., p0 * p0 + 2. * frac * (xi - c_k))) - p0) / frac;
706,970✔
765
  }
766

767
  if (mu < -1.) {
1,653,739,637✔
768
    mu = -1.;
×
769
  } else if (mu > 1.) {
1,653,739,637✔
UNCOV
770
    mu = 1.;
×
771
  }
772

773
  // Update the weight to reflect neutron multiplicity
774
  wgt *= mult[gin][i_gout];
1,653,739,637✔
775
}
1,653,739,637✔
776

777
//==============================================================================
778

779
xt::xtensor<double, 3> ScattDataTabular::get_matrix(size_t max_order)
2,627✔
780
{
781
  // Get the sizes and initialize the data to 0
782
  size_t groups = energy.size();
2,627✔
783
  // We ignore the requested order for Histogram and Tabular representations
784
  size_t order_dim = get_order();
2,627✔
785
  xt::xtensor<double, 3> matrix({groups, groups, order_dim}, 0.);
2,627✔
786

787
  for (int gin = 0; gin < groups; gin++) {
11,511✔
788
    for (int i_gout = 0; i_gout < energy[gin].size(); i_gout++) {
57,450✔
789
      int gout = i_gout + gmin[gin];
48,566✔
790
      for (int l = 0; l < order_dim; l++) {
168,114✔
791
        matrix(gin, gout, l) =
119,548✔
792
          scattxs[gin] * energy[gin][i_gout] * fmu[gin][i_gout][l];
119,548✔
793
      }
794
    }
795
  }
796
  return matrix;
2,627✔
797
}
×
798

799
//==============================================================================
800

801
void ScattDataTabular::combine(
2,067✔
802
  const vector<ScattData*>& those_scatts, const vector<double>& scalars)
803
{
804
  // Find the max order in the data set and make sure we can combine the sets
805
  size_t max_order = those_scatts[0]->get_order();
2,067✔
806
  for (int i = 0; i < those_scatts.size(); i++) {
4,694✔
807
    // Lets also make sure these items are combineable
808
    ScattDataTabular* that = dynamic_cast<ScattDataTabular*>(those_scatts[i]);
2,627✔
809
    if (!that) {
2,627✔
810
      fatal_error("Cannot combine the ScattData objects!");
×
811
    }
812
    if (max_order != that->get_order()) {
2,627✔
813
      fatal_error("Cannot combine the ScattData objects!");
×
814
    }
815
  }
816

817
  size_t groups = those_scatts[0]->energy.size();
2,067✔
818

819
  xt::xtensor<int, 1> in_gmin({groups}, 0);
2,067✔
820
  xt::xtensor<int, 1> in_gmax({groups}, 0);
2,067✔
821
  double_3dvec sparse_scatter(groups);
2,067✔
822
  double_2dvec sparse_mult(groups);
2,067✔
823

824
  // The rest of the steps do not depend on the type of angular representation
825
  // so we use a base class method to sum up xs and create new energy and mult
826
  // matrices
827
  size_t order_dim = max_order;
2,067✔
828
  ScattData::base_combine(max_order, order_dim, those_scatts, scalars, in_gmin,
2,067✔
829
    in_gmax, sparse_mult, sparse_scatter);
830

831
  // Got everything we need, store it.
832
  init(in_gmin, in_gmax, sparse_mult, sparse_scatter);
2,067✔
833
}
2,067✔
834

835
//==============================================================================
836
// module-level methods
837
//==============================================================================
838

839
void convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab)
2,563✔
840
{
841
  // See if the user wants us to figure out how many points to use
842
  int n_mu = settings::legendre_to_tabular_points;
2,563✔
843
  if (n_mu == C_NONE) {
2,563✔
844
    // then we will use 2 pts if its P0, or the default if a higher order
845
    // TODO use an error minimization algorithm that also picks n_mu
846
    if (leg.get_order() == 0) {
2,563✔
847
      n_mu = 2;
2,339✔
848
    } else {
849
      n_mu = DEFAULT_NMU;
224✔
850
    }
851
  }
852

853
  tab.base_init(n_mu, leg.gmin, leg.gmax, leg.energy, leg.mult);
2,563✔
854
  tab.scattxs = leg.scattxs;
2,563✔
855

856
  // Build mu and dmu
857
  tab.mu = xt::linspace(-1., 1., n_mu);
2,563✔
858
  tab.dmu = 2. / (n_mu - 1);
2,563✔
859

860
  // Calculate f(mu) and integrate it so we can avoid rejection sampling
861
  size_t groups = tab.energy.size();
2,563✔
862
  tab.fmu.resize(groups);
2,563✔
863
  for (int gin = 0; gin < groups; gin++) {
11,319✔
864
    int num_groups = tab.gmax[gin] - tab.gmin[gin] + 1;
8,756✔
865
    tab.fmu[gin].resize(num_groups);
8,756✔
866
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
57,130✔
867
      tab.fmu[gin][i_gout].resize(n_mu);
48,374✔
868
      for (int imu = 0; imu < n_mu; imu++) {
164,466✔
869
        tab.fmu[gin][i_gout][imu] =
116,092✔
870
          evaluate_legendre(leg.dist[gin][i_gout].size() - 1,
116,092✔
871
            leg.dist[gin][i_gout].data(), tab.mu[imu]);
116,092✔
872
      }
873

874
      // Ensure positivity
875
      for (auto& val : tab.fmu[gin][i_gout]) {
164,466✔
876
        if (val < 0.)
116,092✔
877
          val = 0.;
576✔
878
      }
879

880
      // Now re-normalize for numerical integration issues and to take care of
881
      // the above negative fix-up.  Also accrue the CDF
882
      double norm = 0.;
48,374✔
883
      tab.dist[gin][i_gout][0] = 0.;
48,374✔
884
      for (int imu = 1; imu < n_mu; imu++) {
116,092✔
885
        norm += 0.5 * tab.dmu *
135,436✔
886
                (tab.fmu[gin][i_gout][imu - 1] + tab.fmu[gin][i_gout][imu]);
67,718✔
887
        // incorporate to the CDF
888
        tab.dist[gin][i_gout][imu] = norm;
67,718✔
889
      }
890

891
      // now do the normalization
892
      if (norm > 0.) {
48,374✔
893
        for (int imu = 0; imu < n_mu; imu++) {
144,530✔
894
          tab.fmu[gin][i_gout][imu] /= norm;
102,636✔
895
          tab.dist[gin][i_gout][imu] /= norm;
102,636✔
896
        }
897
      }
898
    }
899
  }
900
}
2,563✔
901

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