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

openmc-dev / openmc / 13894606366

17 Mar 2025 08:15AM UTC coverage: 84.924% (-0.1%) from 85.042%
13894606366

Pull #3305

github

web-flow
Merge 042852a3c into 58f2a2177
Pull Request #3305: New Feature Development: The Implementation of chord length sampling (CLS) method for stochastic media( #3286 )

20 of 161 new or added lines in 8 files covered. (12.42%)

1043 existing lines in 45 files now uncovered.

51567 of 60721 relevant lines covered (84.92%)

36823581.23 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,206✔
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,206✔
27

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

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

39
    // Make sure the multiplicity does not have 0s
40
    for (int go = 0; go < mult[gin].size(); go++) {
49,084✔
41
      if (mult[gin][go] == 0.) {
33,382✔
42
        mult[gin][go] = 1.;
64✔
43
      }
44
    }
45

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

49
    if (norm != 0.) {
15,702✔
50
      for (auto& n : energy[gin])
48,892✔
51
        n /= norm;
33,286✔
52
    }
53

54
    // Initialize the distribution data
55
    dist[gin].resize(in_gmax[gin] - in_gmin[gin] + 1);
15,702✔
56
    for (auto& v : dist[gin]) {
49,084✔
57
      v.resize(order);
33,382✔
58
    }
59
  }
60
}
7,206✔
61

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

64
void ScattData::base_combine(size_t max_order, size_t order_dim,
2,162✔
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,162✔
70

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

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

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

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

87
    // Do the same with the P0 matrices
88
    for (int gin = 0; gin < groups; gin++) {
8,628✔
89
      for (int go = 0; go < groups; go++) {
27,204✔
90
        this_nuscatt_P0(gin, go) +=
42,628✔
91
          scalars[i] * that->get_xs(MgxsType::NU_SCATTER, gin, &go, nullptr);
21,314✔
92
        this_scatt_P0(gin, go) +=
21,314✔
93
          scalars[i] * that->get_xs(MgxsType::SCATTER, gin, &go, nullptr);
21,314✔
94
      }
95
    }
96
  }
2,738✔
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,162✔
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++) {
6,948✔
105
    // Find the minimum and maximum group boundaries
106
    int gmin_;
107
    for (gmin_ = 0; gmin_ < groups; gmin_++) {
10,962✔
108
      bool non_zero = false;
10,930✔
109
      for (int l = 0; l < this_nuscatt_matrix.shape()[2]; l++) {
33,938✔
110
        if (this_nuscatt_matrix(gin, gmin_, l) != 0.) {
27,762✔
111
          non_zero = true;
4,754✔
112
          break;
4,754✔
113
        }
114
      }
115
      if (non_zero)
10,930✔
116
        break;
4,754✔
117
    }
118
    int gmax_;
119
    for (gmax_ = groups - 1; gmax_ >= 0; gmax_--) {
7,410✔
120
      bool non_zero = false;
7,378✔
121
      for (int l = 0; l < this_nuscatt_matrix.shape()[2]; l++) {
15,666✔
122
        if (this_nuscatt_matrix(gin, gmax_, l) != 0.) {
13,042✔
123
          non_zero = true;
4,754✔
124
          break;
4,754✔
125
        }
126
      }
127
      if (non_zero)
7,378✔
128
        break;
4,754✔
129
    }
130

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

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

141
    // Store the data in the compressed format
142
    sparse_scatter[gin].resize(gmax_ - gmin_ + 1);
4,786✔
143
    sparse_mult[gin].resize(gmax_ - gmin_ + 1);
4,786✔
144
    int i_gout = 0;
4,786✔
145
    for (int gout = gmin_; gout <= gmax_; gout++) {
15,236✔
146
      sparse_scatter[gin][i_gout].resize(this_nuscatt_matrix.shape()[2]);
10,450✔
147
      for (int l = 0; l < this_nuscatt_matrix.shape()[2]; l++) {
59,302✔
148
        sparse_scatter[gin][i_gout][l] = this_nuscatt_matrix(gin, gout, l);
48,852✔
149
      }
150
      sparse_mult[gin][i_gout] = this_mult(gin, gout);
10,450✔
151
      i_gout++;
10,450✔
152
    }
153
  }
154
}
2,162✔
155

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

158
void ScattData::sample_energy(int gin, int& gout, int& i_gout, uint64_t* seed)
1,669,113,996✔
159
{
160
  // Sample the outgoing group
161
  double xi = prn(seed);
1,669,113,996✔
162
  double prob = 0.;
1,669,113,996✔
163
  i_gout = 0;
1,669,113,996✔
164
  for (gout = gmin[gin]; gout < gmax[gin]; ++gout) {
2,034,723,570✔
165
    prob += energy[gin][i_gout];
1,605,008,482✔
166
    if (xi < prob)
1,605,008,482✔
167
      break;
1,239,398,908✔
168
    ++i_gout;
365,609,574✔
169
  }
170
}
1,669,113,996✔
171

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

174
double ScattData::get_xs(
18,401,668✔
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;
18,401,668✔
179
  if (gout != nullptr) {
18,401,668✔
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?
18,401,668✔
183
      return 0.;
25,888✔
184
    }
185
    i_gout = *gout - gmin[gin];
18,375,780✔
186
  }
187

188
  double val = scattxs[gin];
18,375,780✔
189
  switch (xstype) {
18,375,780✔
190
  case MgxsType::NU_SCATTER:
19,890✔
191
    if (gout != nullptr)
19,890✔
192
      val *= energy[gin][i_gout];
19,890✔
193
    break;
19,890✔
194
  case MgxsType::SCATTER:
12,114✔
195
    if (gout != nullptr) {
12,114✔
196
      val *= energy[gin][i_gout] / mult[gin][i_gout];
12,114✔
197
    } else {
UNCOV
198
      val /= std::inner_product(
×
UNCOV
199
        mult[gin].begin(), mult[gin].end(), energy[gin].begin(), 0.0);
×
200
    }
201
    break;
12,114✔
202
  case MgxsType::NU_SCATTER_FMU:
9,171,888✔
203
    if ((gout != nullptr) && (mu != nullptr)) {
9,171,888✔
204
      val *= energy[gin][i_gout] * calc_f(gin, *gout, *mu);
9,171,888✔
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,171,888✔
211
  case MgxsType::SCATTER_FMU:
9,171,888✔
212
    if ((gout != nullptr) && (mu != nullptr)) {
9,171,888✔
213
      val *= energy[gin][i_gout] * calc_f(gin, *gout, *mu) / mult[gin][i_gout];
9,171,888✔
214
    } else {
215
      // This is not an expected path (asking for f_mu without asking for a
216
      // group or mu is not useful
UNCOV
217
      fatal_error("Invalid call to get_xs");
×
218
    }
219
    break;
9,171,888✔
UNCOV
220
  default:
×
UNCOV
221
    break;
×
222
  }
223
  return val;
18,375,780✔
224
}
225

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

230
void ScattDataLegendre::init(const xt::xtensor<int, 1>& in_gmin,
2,866✔
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();
2,866✔
235
  size_t order = coeffs[0][0].size();
2,866✔
236

237
  // make a copy of coeffs that we can use to both extract data and normalize
238
  double_3dvec matrix = coeffs;
2,866✔
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});
2,866✔
243
  for (int gin = 0; gin < groups; gin++) {
9,012✔
244
    int num_groups = in_gmax[gin] - in_gmin[gin] + 1;
6,146✔
245
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
18,644✔
246
      scattxs[gin] += matrix[gin][i_gout][0];
12,498✔
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;
2,866✔
254
  in_energy.resize(groups);
2,866✔
255
  for (int gin = 0; gin < groups; gin++) {
9,012✔
256
    int num_groups = in_gmax[gin] - in_gmin[gin] + 1;
6,146✔
257
    in_energy[gin].resize(num_groups);
6,146✔
258
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
18,644✔
259
      double norm = matrix[gin][i_gout][0];
12,498✔
260
      in_energy[gin][i_gout] = norm;
12,498✔
261
      if (norm != 0.) {
12,498✔
262
        for (auto& n : matrix[gin][i_gout])
27,908✔
263
          n /= norm;
15,442✔
264
      }
265
    }
266
  }
267

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

271
  // Set the distribution (sdata.dist) values and initialize max_val
272
  max_val.resize(groups);
2,866✔
273
  for (int gin = 0; gin < groups; gin++) {
9,012✔
274
    int num_groups = gmax[gin] - gmin[gin] + 1;
6,146✔
275
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
18,644✔
276
      dist[gin][i_gout] = matrix[gin][i_gout];
12,498✔
277
    }
278
    max_val[gin].resize(num_groups);
6,146✔
279
    for (auto& n : max_val[gin])
18,644✔
280
      n = 0.;
12,498✔
281
  }
282

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

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

289
void ScattDataLegendre::update_max_val()
2,866✔
290
{
291
  size_t groups = max_val.size();
2,866✔
292
  // Step through the polynomial with fixed number of points to identify the
293
  // maximal value
294
  int Nmu = 1001;
2,866✔
295
  double dmu = 2. / (Nmu - 1);
2,866✔
296
  for (int gin = 0; gin < groups; gin++) {
9,012✔
297
    int num_groups = gmax[gin] - gmin[gin] + 1;
6,146✔
298
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
18,644✔
299
      for (int imu = 0; imu < Nmu; imu++) {
12,522,996✔
300
        double mu;
301
        if (imu == 0) {
12,510,498✔
302
          mu = -1.;
12,498✔
303
        } else if (imu == (Nmu - 1)) {
12,498,000✔
304
          mu = 1.;
12,498✔
305
        } else {
306
          mu = -1. + (imu - 1) * dmu;
12,485,502✔
307
        }
308

309
        // Calculate probability
310
        double f = evaluate_legendre(
25,020,996✔
311
          dist[gin][i_gout].size() - 1, dist[gin][i_gout].data(), mu);
12,510,498✔
312

313
        // if this is a new maximum, store it
314
        if (f > max_val[gin][i_gout])
12,510,498✔
315
          max_val[gin][i_gout] = f;
1,363,826✔
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;
12,498✔
320
    }
321
  }
322
}
2,866✔
323

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

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

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

341
void ScattDataLegendre::sample(
15,647,775✔
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,647,775✔
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,647,775✔
351
  int samples;
352
  for (samples = 0; samples < MAX_SAMPLE; ++samples) {
27,717,635✔
353
    mu = 2. * prn(seed) - 1.;
27,717,635✔
354
    double f = calc_f(gin, gout, mu);
27,717,635✔
355
    if (f > 0.) {
27,717,635✔
356
      double u = prn(seed) * M;
27,717,635✔
357
      if (u <= f)
27,717,635✔
358
        break;
15,647,775✔
359
    }
360
  }
361
  if (samples == MAX_SAMPLE) {
15,647,775✔
UNCOV
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,647,775✔
367
}
15,647,775✔
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✔
UNCOV
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✔
UNCOV
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

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

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

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

528
void ScattDataHistogram::sample(
31,944✔
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,944✔
534

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

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

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

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

556
  // Update the weight to reflect neutron multiplicity
557
  wgt *= mult[gin][i_gout];
31,944✔
558
}
31,944✔
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✔
UNCOV
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✔
UNCOV
594
      fatal_error("Cannot combine the ScattData objects!");
×
595
    }
596
    if (max_order != that->get_order()) {
64✔
UNCOV
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,
1,890✔
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();
1,890✔
628
  size_t order = coeffs[0][0].size();
1,890✔
629

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

633
  // Build the angular distribution mu values
634
  mu = xt::linspace(-1., 1., order);
1,890✔
635
  dmu = 2. / (order - 1);
1,890✔
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});
1,890✔
640
  for (int gin = 0; gin < groups; gin++) {
6,132✔
641
    for (int i_gout = 0; i_gout < matrix[gin].size(); i_gout++) {
13,876✔
642
      for (int imu = 1; imu < order; imu++) {
45,252✔
643
        scattxs[gin] +=
35,618✔
644
          0.5 * dmu * (matrix[gin][i_gout][imu - 1] + matrix[gin][i_gout][imu]);
35,618✔
645
      }
646
    }
647
  }
648

649
  // Build the energy transfer matrix from data in the variable matrix
650
  double_2dvec in_energy(groups);
1,890✔
651
  for (int gin = 0; gin < groups; gin++) {
6,132✔
652
    int num_groups = in_gmax[gin] - in_gmin[gin] + 1;
4,242✔
653
    in_energy[gin].resize(num_groups);
4,242✔
654
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
13,876✔
655
      double norm = 0.;
9,634✔
656
      for (int imu = 1; imu < order; imu++) {
45,252✔
657
        norm +=
35,618✔
658
          0.5 * dmu * (matrix[gin][i_gout][imu - 1] + matrix[gin][i_gout][imu]);
35,618✔
659
      }
660
      in_energy[gin][i_gout] = norm;
9,634✔
661
    }
662
  }
663

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

667
  // Calculate f(mu) and integrate it so we can avoid rejection sampling
668
  fmu.resize(groups);
1,890✔
669
  for (int gin = 0; gin < groups; gin++) {
6,132✔
670
    int num_groups = gmax[gin] - gmin[gin] + 1;
4,242✔
671
    fmu[gin].resize(num_groups);
4,242✔
672
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
13,876✔
673
      fmu[gin][i_gout].resize(order);
9,634✔
674
      // The variable matrix contains f(mu); so directly assign it
675
      fmu[gin][i_gout] = matrix[gin][i_gout];
9,634✔
676

677
      // Ensure positivity
678
      for (auto& val : fmu[gin][i_gout]) {
54,886✔
679
        if (val < 0.)
45,252✔
UNCOV
680
          val = 0.;
×
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.;
9,634✔
686
      for (int imu = 1; imu < order; imu++) {
45,252✔
687
        norm += 0.5 * dmu * (fmu[gin][i_gout][imu - 1] + fmu[gin][i_gout][imu]);
35,618✔
688
        // incorporate to the CDF
689
        dist[gin][i_gout][imu] = norm;
35,618✔
690
      }
691

692
      // now do the normalization
693
      if (norm > 0.) {
9,634✔
694
        for (int imu = 0; imu < order; imu++) {
53,798✔
695
          fmu[gin][i_gout][imu] /= norm;
44,196✔
696
          dist[gin][i_gout][imu] /= norm;
44,196✔
697
        }
698
      }
699
    }
700
  }
701
}
1,890✔
702

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

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

UNCOV
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,434,277✔
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,434,277✔
735

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

740
  double c_k = dist[gin][i_gout][0];
1,653,434,277✔
741
  int k;
742
  for (k = 0; k < NP - 1; k++) {
1,666,907,473✔
743
    double c_k1 = dist[gin][i_gout][k + 1];
1,666,907,451✔
744
    if (xi < c_k1)
1,666,907,451✔
745
      break;
1,653,434,255✔
746
    c_k = c_k1;
13,473,196✔
747
  }
748

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

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

758
  if (p0 == p1) {
1,653,434,277✔
759
    mu = mu0 + (xi - c_k) / p0;
1,652,749,340✔
760
  } else {
761
    double frac = (p1 - p0) / (mu1 - mu0);
684,937✔
762
    mu =
684,937✔
763
      mu0 +
684,937✔
764
      (std::sqrt(std::max(0., p0 * p0 + 2. * frac * (xi - c_k))) - p0) / frac;
684,937✔
765
  }
766

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

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

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

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

787
  for (int gin = 0; gin < groups; gin++) {
7,572✔
788
    for (int i_gout = 0; i_gout < energy[gin].size(); i_gout++) {
16,244✔
789
      int gout = i_gout + gmin[gin];
11,058✔
790
      for (int l = 0; l < order_dim; l++) {
56,086✔
791
        matrix(gin, gout, l) =
45,028✔
792
          scattxs[gin] * energy[gin][i_gout] * fmu[gin][i_gout][l];
45,028✔
793
      }
794
    }
795
  }
796
  return matrix;
2,386✔
UNCOV
797
}
×
798

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

801
void ScattDataTabular::combine(
1,826✔
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();
1,826✔
806
  for (int i = 0; i < those_scatts.size(); i++) {
4,212✔
807
    // Lets also make sure these items are combineable
808
    ScattDataTabular* that = dynamic_cast<ScattDataTabular*>(those_scatts[i]);
2,386✔
809
    if (!that) {
2,386✔
UNCOV
810
      fatal_error("Cannot combine the ScattData objects!");
×
811
    }
812
    if (max_order != that->get_order()) {
2,386✔
UNCOV
813
      fatal_error("Cannot combine the ScattData objects!");
×
814
    }
815
  }
816

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

819
  xt::xtensor<int, 1> in_gmin({groups}, 0);
1,826✔
820
  xt::xtensor<int, 1> in_gmax({groups}, 0);
1,826✔
821
  double_3dvec sparse_scatter(groups);
1,826✔
822
  double_2dvec sparse_mult(groups);
1,826✔
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;
1,826✔
828
  ScattData::base_combine(max_order, order_dim, those_scatts, scalars, in_gmin,
1,826✔
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);
1,826✔
833
}
1,826✔
834

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

839
void convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab)
2,322✔
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,322✔
843
  if (n_mu == C_NONE) {
2,322✔
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,322✔
847
      n_mu = 2;
2,098✔
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,322✔
854
  tab.scattxs = leg.scattxs;
2,322✔
855

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

860
  // Calculate f(mu) and integrate it so we can avoid rejection sampling
861
  size_t groups = tab.energy.size();
2,322✔
862
  tab.fmu.resize(groups);
2,322✔
863
  for (int gin = 0; gin < groups; gin++) {
7,380✔
864
    int num_groups = tab.gmax[gin] - tab.gmin[gin] + 1;
5,058✔
865
    tab.fmu[gin].resize(num_groups);
5,058✔
866
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
15,924✔
867
      tab.fmu[gin][i_gout].resize(n_mu);
10,866✔
868
      for (int imu = 0; imu < n_mu; imu++) {
52,438✔
869
        tab.fmu[gin][i_gout][imu] =
41,572✔
870
          evaluate_legendre(leg.dist[gin][i_gout].size() - 1,
41,572✔
871
            leg.dist[gin][i_gout].data(), tab.mu[imu]);
41,572✔
872
      }
873

874
      // Ensure positivity
875
      for (auto& val : tab.fmu[gin][i_gout]) {
52,438✔
876
        if (val < 0.)
41,572✔
877
          val = 0.;
624✔
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.;
10,866✔
883
      tab.dist[gin][i_gout][0] = 0.;
10,866✔
884
      for (int imu = 1; imu < n_mu; imu++) {
41,572✔
885
        norm += 0.5 * tab.dmu *
61,412✔
886
                (tab.fmu[gin][i_gout][imu - 1] + tab.fmu[gin][i_gout][imu]);
30,706✔
887
        // incorporate to the CDF
888
        tab.dist[gin][i_gout][imu] = norm;
30,706✔
889
      }
890

891
      // now do the normalization
892
      if (norm > 0.) {
10,866✔
893
        for (int imu = 0; imu < n_mu; imu++) {
51,350✔
894
          tab.fmu[gin][i_gout][imu] /= norm;
40,516✔
895
          tab.dist[gin][i_gout][imu] /= norm;
40,516✔
896
        }
897
      }
898
    }
899
  }
900
}
2,322✔
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