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

openmc-dev / openmc / 21991279157

13 Feb 2026 02:53PM UTC coverage: 81.82% (-0.06%) from 81.875%
21991279157

Pull #3805

github

web-flow
Merge 0a7a80411 into bcb939520
Pull Request #3805: Remove xtensor and xtl Dependencies

17242 of 24268 branches covered (71.05%)

Branch coverage included in aggregate %.

977 of 1013 new or added lines in 39 files covered. (96.45%)

404 existing lines in 8 files now uncovered.

57420 of 66983 relevant lines covered (85.72%)

45458907.73 hits per line

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

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

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

7
#include "openmc/tensor.h"
8

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

15
namespace openmc {
16

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

21
void ScattData::base_init(int order, const tensor::Tensor<int>& in_gmin,
8,787✔
22
  const tensor::Tensor<int>& in_gmax, const double_2dvec& in_energy,
23
  const double_2dvec& in_mult)
24
{
25
  size_t groups = in_energy.size();
8,787✔
26

27
  gmin = in_gmin;
8,787✔
28
  gmax = in_gmax;
8,787✔
29
  energy.resize(groups);
8,787✔
30
  mult.resize(groups);
8,787✔
31
  dist.resize(groups);
8,787✔
32

33
  for (int gin = 0; gin < groups; gin++) {
39,987✔
34
    // Store the inputted data
35
    energy[gin] = in_energy[gin];
31,200✔
36
    mult[gin] = in_mult[gin];
31,200✔
37

38
    // Make sure the multiplicity does not have 0s
39
    for (int go = 0; go < mult[gin].size(); go++) {
168,338✔
40
      if (mult[gin][go] == 0.) {
137,138✔
41
        mult[gin][go] = 1.;
11,704✔
42
      }
43
    }
44

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

48
    if (norm != 0.) {
31,200✔
49
      for (auto& n : energy[gin])
167,918✔
50
        n /= norm;
136,928✔
51
    }
52

53
    // Initialize the distribution data
54
    dist[gin].resize(in_gmax[gin] - in_gmin[gin] + 1);
31,200✔
55
    for (auto& v : dist[gin]) {
168,338✔
56
      v.resize(order);
137,138✔
57
    }
58
  }
59
}
8,787✔
60

61
//==============================================================================
62

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

70
  // Now allocate and zero our storage spaces
71
  tensor::Tensor<double> this_nuscatt_matrix({groups, groups, order_dim}, 0.);
2,719✔
72
  tensor::Tensor<double> this_nuscatt_P0({groups, groups}, 0.);
2,719✔
73
  tensor::Tensor<double> this_scatt_P0({groups, groups}, 0.);
2,719✔
74
  tensor::Tensor<double> this_mult({groups, groups}, 1.);
2,719✔
75

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

80
    // Build the dense matrix for that object
81
    tensor::Tensor<double> that_matrix = that->get_matrix(max_order);
3,223✔
82

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

86
    // Do the same with the P0 matrices
87
    for (int gin = 0; gin < groups; gin++) {
14,197✔
88
      for (int go = 0; go < groups; go++) {
252,250✔
89
        this_nuscatt_P0(gin, go) +=
482,552✔
90
          scalars[i] * that->get_xs(MgxsType::NU_SCATTER, gin, &go, nullptr);
241,276✔
91
        this_scatt_P0(gin, go) +=
241,276✔
92
          scalars[i] * that->get_xs(MgxsType::SCATTER, gin, &go, nullptr);
241,276✔
93
      }
94
    }
95
  }
3,223✔
96

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

101
  // We have the data, now we need to convert to a jagged array and then use
102
  // the initialize function to store it on the object.
103
  for (int gin = 0; gin < groups; gin++) {
12,727✔
104
    // Find the minimum and maximum group boundaries
105
    int gmin_;
106
    for (gmin_ = 0; gmin_ < groups; gmin_++) {
120,884✔
107
      bool non_zero = false;
120,814✔
108
      for (int l = 0; l < this_nuscatt_matrix.shape(2); l++) {
351,078✔
109
        if (this_nuscatt_matrix(gin, gmin_, l) != 0.) {
240,202✔
110
          non_zero = true;
9,938✔
111
          break;
9,938✔
112
        }
113
      }
114
      if (non_zero)
120,814✔
115
        break;
9,938✔
116
    }
117
    int gmax_;
118
    for (gmax_ = groups - 1; gmax_ >= 0; gmax_--) {
97,416✔
119
      bool non_zero = false;
97,346✔
120
      for (int l = 0; l < this_nuscatt_matrix.shape(2); l++) {
274,808✔
121
        if (this_nuscatt_matrix(gin, gmax_, l) != 0.) {
187,400✔
122
          non_zero = true;
9,938✔
123
          break;
9,938✔
124
        }
125
      }
126
      if (non_zero)
97,346✔
127
        break;
9,938✔
128
    }
129

130
    // treat the case of all values being 0
131
    if (gmin_ > gmax_) {
10,008✔
132
      gmin_ = gin;
70✔
133
      gmax_ = gin;
70✔
134
    }
135

136
    // Store the group bounds
137
    in_gmin[gin] = gmin_;
10,008✔
138
    in_gmax[gin] = gmax_;
10,008✔
139

140
    // Store the data in the compressed format
141
    sparse_scatter[gin].resize(gmax_ - gmin_ + 1);
10,008✔
142
    sparse_mult[gin].resize(gmax_ - gmin_ + 1);
10,008✔
143
    int i_gout = 0;
10,008✔
144
    for (int gout = gmin_; gout <= gmax_; gout++) {
55,128✔
145
      sparse_scatter[gin][i_gout].resize(this_nuscatt_matrix.shape(2));
45,120✔
146
      for (int l = 0; l < this_nuscatt_matrix.shape(2); l++) {
159,384✔
147
        sparse_scatter[gin][i_gout][l] = this_nuscatt_matrix(gin, gout, l);
114,264✔
148
      }
149
      sparse_mult[gin][i_gout] = this_mult(gin, gout);
45,120✔
150
      i_gout++;
45,120✔
151
    }
152
  }
153
}
2,719✔
154

155
//==============================================================================
156

157
void ScattData::sample_energy(int gin, int& gout, int& i_gout, uint64_t* seed)
1,517,650,640✔
158
{
159
  // Sample the outgoing group
160
  double xi = prn(seed);
1,517,650,640✔
161
  double prob = 0.;
1,517,650,640✔
162
  i_gout = 0;
1,517,650,640✔
163
  for (gout = gmin[gin]; gout < gmax[gin]; ++gout) {
1,850,683,840✔
164
    prob += energy[gin][i_gout];
1,460,122,460✔
165
    if (xi < prob)
1,460,122,460✔
166
      break;
1,127,089,260✔
167
    ++i_gout;
333,033,200✔
168
  }
169
}
1,517,650,640✔
170

171
//==============================================================================
172

173
double ScattData::get_xs(
17,492,812✔
174
  MgxsType xstype, int gin, const int* gout, const double* mu)
175
{
176
  // Set the outgoing group offset index as needed
177
  int i_gout = 0;
17,492,812✔
178
  if (gout != nullptr) {
17,492,812!
179
    // short circuit the function if gout is from a zero portion of the
180
    // scattering matrix
181
    if ((*gout < gmin[gin]) || (*gout > gmax[gin])) { // > gmax?
17,492,812✔
182
      return 0.;
581,860✔
183
    }
184
    i_gout = *gout - gmin[gin];
16,910,952✔
185
  }
186

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

225
//==============================================================================
226
// ScattDataLegendre methods
227
//==============================================================================
228

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

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

239
  // Get the scattering cross section value by summing the un-normalized P0
240
  // coefficient in the variable matrix over all outgoing groups.
241
  scattxs = tensor::zeros<double>({groups});
3,335✔
242
  for (int gin = 0; gin < groups; gin++) {
14,533✔
243
    int num_groups = in_gmax[gin] - in_gmin[gin] + 1;
11,198✔
244
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
58,110✔
245
      scattxs[gin] += matrix[gin][i_gout][0];
46,912✔
246
    }
247
  }
248

249
  // Build the energy transfer matrix from data in the variable matrix while
250
  // also normalizing the variable matrix itself
251
  // (forcing the CDF of f(mu=1) == 1)
252
  double_2dvec in_energy;
3,335✔
253
  in_energy.resize(groups);
3,335✔
254
  for (int gin = 0; gin < groups; gin++) {
14,533✔
255
    int num_groups = in_gmax[gin] - in_gmin[gin] + 1;
11,198✔
256
    in_energy[gin].resize(num_groups);
11,198✔
257
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
58,110✔
258
      double norm = matrix[gin][i_gout][0];
46,912✔
259
      in_energy[gin][i_gout] = norm;
46,912✔
260
      if (norm != 0.) {
46,912✔
261
        for (auto& n : matrix[gin][i_gout])
84,808✔
262
          n /= norm;
43,706✔
263
      }
264
    }
265
  }
266

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

270
  // Set the distribution (sdata.dist) values and initialize max_val
271
  max_val.resize(groups);
3,335✔
272
  for (int gin = 0; gin < groups; gin++) {
14,533✔
273
    int num_groups = gmax[gin] - gmin[gin] + 1;
11,198✔
274
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
58,110✔
275
      dist[gin][i_gout] = matrix[gin][i_gout];
46,912✔
276
    }
277
    max_val[gin].resize(num_groups);
11,198✔
278
    for (auto& n : max_val[gin])
58,110✔
279
      n = 0.;
46,912✔
280
  }
281

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

286
//==============================================================================
287

288
void ScattDataLegendre::update_max_val()
3,335✔
289
{
290
  size_t groups = max_val.size();
3,335✔
291
  // Step through the polynomial with fixed number of points to identify the
292
  // maximal value
293
  int Nmu = 1001;
3,335✔
294
  double dmu = 2. / (Nmu - 1);
3,335✔
295
  for (int gin = 0; gin < groups; gin++) {
14,533✔
296
    int num_groups = gmax[gin] - gmin[gin] + 1;
11,198✔
297
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
58,110✔
298
      for (int imu = 0; imu < Nmu; imu++) {
47,005,824✔
299
        double mu;
300
        if (imu == 0) {
46,958,912✔
301
          mu = -1.;
46,912✔
302
        } else if (imu == (Nmu - 1)) {
46,912,000✔
303
          mu = 1.;
46,912✔
304
        } else {
305
          mu = -1. + (imu - 1) * dmu;
46,865,088✔
306
        }
307

308
        // Calculate probability
309
        double f = evaluate_legendre(
93,917,824✔
310
          dist[gin][i_gout].size() - 1, dist[gin][i_gout].data(), mu);
46,958,912✔
311

312
        // if this is a new maximum, store it
313
        if (f > max_val[gin][i_gout])
46,958,912✔
314
          max_val[gin][i_gout] = f;
1,225,026✔
315
      } // end imu loop
316

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

323
//==============================================================================
324

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

338
//==============================================================================
339

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

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

364
  // Update the weight to reflect neutron multiplicity
365
  wgt *= mult[gin][i_gout];
14,222,090✔
366
}
14,222,090✔
367

368
//==============================================================================
369

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

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

388
  tensor::Tensor<int> in_gmin({groups}, 0);
238✔
389
  tensor::Tensor<int> in_gmax({groups}, 0);
238✔
390
  double_3dvec sparse_scatter(groups);
238✔
391
  double_2dvec sparse_mult(groups);
238✔
392

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

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

404
//==============================================================================
405

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

413
  for (int gin = 0; gin < groups; gin++) {
756✔
414
    for (int i_gout = 0; i_gout < energy[gin].size(); i_gout++) {
1,260✔
415
      int gout = i_gout + gmin[gin];
756✔
416
      for (int l = 0; l < order_dim; l++) {
2,268✔
417
        matrix(gin, gout, l) =
1,512✔
418
          scattxs[gin] * energy[gin][i_gout] * dist[gin][i_gout][l];
1,512✔
419
      }
420
    }
421
  }
422
  return matrix;
252✔
423
}
424

425
//==============================================================================
426
// ScattDataHistogram methods
427
//==============================================================================
428

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

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

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

449
  // Build the energy transfer matrix from data in the variable matrix
450
  double_2dvec in_energy;
112✔
451
  in_energy.resize(groups);
112✔
452
  for (int gin = 0; gin < groups; gin++) {
336✔
453
    int num_groups = in_gmax[gin] - in_gmin[gin] + 1;
224✔
454
    in_energy[gin].resize(num_groups);
224✔
455
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
560✔
456
      double norm = std::accumulate(
672✔
457
        matrix[gin][i_gout].begin(), matrix[gin][i_gout].end(), 0.);
672✔
458
      in_energy[gin][i_gout] = norm;
336✔
459
      if (norm != 0.) {
336!
460
        for (auto& n : matrix[gin][i_gout])
9,828✔
461
          n /= norm;
9,492✔
462
      }
463
    }
464
  }
465

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

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

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

483
      // Integrate the histogram
484
      dist[gin][i_gout][0] = dmu * matrix[gin][i_gout][0];
336✔
485
      for (int imu = 1; imu < order; imu++) {
9,492✔
486
        dist[gin][i_gout][imu] =
9,156✔
487
          dmu * matrix[gin][i_gout][imu] + dist[gin][i_gout][imu - 1];
9,156✔
488
      }
489

490
      // Now re-normalize for integral to unity
491
      double norm = dist[gin][i_gout][order - 1];
336✔
492
      if (norm > 0.) {
336!
493
        for (int imu = 0; imu < order; imu++) {
9,828✔
494
          fmu[gin][i_gout][imu] /= norm;
9,492✔
495
          dist[gin][i_gout][imu] /= norm;
9,492✔
496
        }
497
      }
498
    }
499
  }
500
}
112✔
501

502
//==============================================================================
503

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

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

525
//==============================================================================
526

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

534
  // Determine the outgoing cosine bin
535
  double xi = prn(seed);
28,880✔
536

537
  int imu;
538
  if (xi < dist[gin][i_gout][0]) {
28,880✔
539
    imu = 0;
1,390✔
540
  } else {
541
    imu =
27,490✔
542
      std::upper_bound(dist[gin][i_gout].begin(), dist[gin][i_gout].end(), xi) -
27,490✔
543
      dist[gin][i_gout].begin();
54,980✔
544
  }
545

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

549
  if (mu < -1.) {
28,880!
550
    mu = -1.;
×
551
  } else if (mu > 1.) {
28,880!
552
    mu = 1.;
×
553
  }
554

555
  // Update the weight to reflect neutron multiplicity
556
  wgt *= mult[gin][i_gout];
28,880✔
557
}
28,880✔
558

559
//==============================================================================
560

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

569
  for (int gin = 0; gin < groups; gin++) {
168✔
570
    for (int i_gout = 0; i_gout < energy[gin].size(); i_gout++) {
280✔
571
      int gout = i_gout + gmin[gin];
168✔
572
      for (int l = 0; l < order_dim; l++) {
4,914✔
573
        matrix(gin, gout, l) =
4,746✔
574
          scattxs[gin] * energy[gin][i_gout] * fmu[gin][i_gout][l];
4,746✔
575
      }
576
    }
577
  }
578
  return matrix;
56✔
579
}
580

581
//==============================================================================
582

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

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

602
  tensor::Tensor<int> in_gmin({groups}, 0);
56✔
603
  tensor::Tensor<int> in_gmax({groups}, 0);
56✔
604
  double_3dvec sparse_scatter(groups);
56✔
605
  double_2dvec sparse_mult(groups);
56✔
606

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

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

618
//==============================================================================
619
// ScattDataTabular methods
620
//==============================================================================
621

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

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

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

636
  // Get the scattering cross section value by integrating the distribution
637
  // over all mu points and then combining over all outgoing groups
638
  scattxs = tensor::zeros<double>({groups});
2,481✔
639
  for (int gin = 0; gin < groups; gin++) {
12,013✔
640
    for (int i_gout = 0; i_gout < matrix[gin].size(); i_gout++) {
53,938✔
641
      for (int imu = 1; imu < order; imu++) {
111,114✔
642
        scattxs[gin] +=
66,708✔
643
          0.5 * dmu * (matrix[gin][i_gout][imu - 1] + matrix[gin][i_gout][imu]);
66,708✔
644
      }
645
    }
646
  }
647

648
  // Build the energy transfer matrix from data in the variable matrix
649
  double_2dvec in_energy(groups);
2,481✔
650
  for (int gin = 0; gin < groups; gin++) {
12,013✔
651
    int num_groups = in_gmax[gin] - in_gmin[gin] + 1;
9,532✔
652
    in_energy[gin].resize(num_groups);
9,532✔
653
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
53,938✔
654
      double norm = 0.;
44,406✔
655
      for (int imu = 1; imu < order; imu++) {
111,114✔
656
        norm +=
66,708✔
657
          0.5 * dmu * (matrix[gin][i_gout][imu - 1] + matrix[gin][i_gout][imu]);
66,708✔
658
      }
659
      in_energy[gin][i_gout] = norm;
44,406✔
660
    }
661
  }
662

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

666
  // Calculate f(mu) and integrate it so we can avoid rejection sampling
667
  fmu.resize(groups);
2,481✔
668
  for (int gin = 0; gin < groups; gin++) {
12,013✔
669
    int num_groups = gmax[gin] - gmin[gin] + 1;
9,532✔
670
    fmu[gin].resize(num_groups);
9,532✔
671
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
53,938✔
672
      fmu[gin][i_gout].resize(order);
44,406✔
673
      // The variable matrix contains f(mu); so directly assign it
674
      fmu[gin][i_gout] = matrix[gin][i_gout];
44,406✔
675

676
      // Ensure positivity
677
      for (auto& val : fmu[gin][i_gout]) {
155,520✔
678
        if (val < 0.)
111,114✔
679
          val = 0.;
1,540✔
680
      }
681

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

691
      // now do the normalization
692
      if (norm > 0.) {
44,406✔
693
        for (int imu = 0; imu < order; imu++) {
135,346✔
694
          fmu[gin][i_gout][imu] /= norm;
97,520✔
695
          dist[gin][i_gout][imu] /= norm;
97,520✔
696
        }
697
      }
698
    }
699
  }
700
}
2,481✔
701

702
//==============================================================================
703

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

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

726
//==============================================================================
727

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

735
  // Determine the outgoing cosine bin
736
  int NP = this->mu.shape(0);
1,503,399,670✔
737
  double xi = prn(seed);
1,503,399,670✔
738

739
  double c_k = dist[gin][i_gout][0];
1,503,399,670✔
740
  int k;
741
  for (k = 0; k < NP - 1; k++) {
1,515,879,770!
742
    double c_k1 = dist[gin][i_gout][k + 1];
1,515,879,770✔
743
    if (xi < c_k1)
1,515,879,770✔
744
      break;
1,503,399,670✔
745
    c_k = c_k1;
12,480,100✔
746
  }
747

748
  // Check to make sure k is <= NP - 1
749
  k = std::min(k, NP - 2);
1,503,399,670✔
750

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

757
  if (p0 == p1) {
1,503,399,670✔
758
    mu = mu0 + (xi - c_k) / p0;
1,502,756,970✔
759
  } else {
760
    double frac = (p1 - p0) / (mu1 - mu0);
642,700✔
761
    mu =
642,700✔
762
      mu0 +
642,700✔
763
      (std::sqrt(std::max(0., p0 * p0 + 2. * frac * (xi - c_k))) - p0) / frac;
642,700✔
764
  }
765

766
  if (mu < -1.) {
1,503,399,670!
767
    mu = -1.;
×
768
  } else if (mu > 1.) {
1,503,399,670!
769
    mu = 1.;
×
770
  }
771

772
  // Update the weight to reflect neutron multiplicity
773
  wgt *= mult[gin][i_gout];
1,503,399,670✔
774
}
1,503,399,670✔
775

776
//==============================================================================
777

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

786
  for (int gin = 0; gin < groups; gin++) {
13,273✔
787
    for (int i_gout = 0; i_gout < energy[gin].size(); i_gout++) {
56,010✔
788
      int gout = i_gout + gmin[gin];
45,652✔
789
      for (int l = 0; l < order_dim; l++) {
156,570✔
790
        matrix(gin, gout, l) =
110,918✔
791
          scattxs[gin] * energy[gin][i_gout] * fmu[gin][i_gout][l];
110,918✔
792
      }
793
    }
794
  }
795
  return matrix;
2,915✔
796
}
797

798
//==============================================================================
799

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

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

818
  tensor::Tensor<int> in_gmin({groups}, 0);
2,425✔
819
  tensor::Tensor<int> in_gmax({groups}, 0);
2,425✔
820
  double_3dvec sparse_scatter(groups);
2,425✔
821
  double_2dvec sparse_mult(groups);
2,425✔
822

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

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

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

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

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

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

859
  // Calculate f(mu) and integrate it so we can avoid rejection sampling
860
  size_t groups = tab.energy.size();
2,859✔
861
  tab.fmu.resize(groups);
2,859✔
862
  for (int gin = 0; gin < groups; gin++) {
13,105✔
863
    int num_groups = tab.gmax[gin] - tab.gmin[gin] + 1;
10,246✔
864
    tab.fmu[gin].resize(num_groups);
10,246✔
865
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
55,730✔
866
      tab.fmu[gin][i_gout].resize(n_mu);
45,484✔
867
      for (int imu = 0; imu < n_mu; imu++) {
153,378✔
868
        tab.fmu[gin][i_gout][imu] =
107,894✔
869
          evaluate_legendre(leg.dist[gin][i_gout].size() - 1,
107,894✔
870
            leg.dist[gin][i_gout].data(), tab.mu[imu]);
107,894✔
871
      }
872

873
      // Ensure positivity
874
      for (auto& val : tab.fmu[gin][i_gout]) {
153,378✔
875
        if (val < 0.)
107,894✔
876
          val = 0.;
504✔
877
      }
878

879
      // Now re-normalize for numerical integration issues and to take care of
880
      // the above negative fix-up.  Also accrue the CDF
881
      double norm = 0.;
45,484✔
882
      tab.dist[gin][i_gout][0] = 0.;
45,484✔
883
      for (int imu = 1; imu < n_mu; imu++) {
107,894✔
884
        norm += 0.5 * tab.dmu *
124,820✔
885
                (tab.fmu[gin][i_gout][imu - 1] + tab.fmu[gin][i_gout][imu]);
62,410✔
886
        // incorporate to the CDF
887
        tab.dist[gin][i_gout][imu] = norm;
62,410✔
888
      }
889

890
      // now do the normalization
891
      if (norm > 0.) {
45,484✔
892
        for (int imu = 0; imu < n_mu; imu++) {
135,514✔
893
          tab.fmu[gin][i_gout][imu] /= norm;
95,840✔
894
          tab.dist[gin][i_gout][imu] /= norm;
95,840✔
895
        }
896
      }
897
    }
898
  }
899
}
2,859✔
900

901
} // namespace openmc
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc