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

openmc-dev / openmc / 33994779277

05 Sep 2026 10:02PM UTC coverage: 81.36% (+0.06%) from 81.301%
33994779277

Pull #4012

github

web-flow
Merge 88cb6833e into 33eaa7cf2
Pull Request #4012: Geometry debug function

18690 of 27173 branches covered (68.78%)

Branch coverage included in aggregate %.

89 of 102 new or added lines in 1 file covered. (87.25%)

2309 existing lines in 73 files now uncovered.

60586 of 70265 relevant lines covered (86.23%)

49880313.57 hits per line

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

88.82
/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,
10,134 ✔
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();
10,134 ✔
26

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

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

38
    // Make sure the multiplicity does not have 0s
39
    for (int go = 0; go < mult[gin].size(); go++) {
184,158 ✔
40
      if (mult[gin][go] == 0.) {
149,241 ✔
41
        mult[gin][go] = 1.;
12,654 ✔
42
      }
43
    }
44

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

48
    if (norm != 0.) {
34,917 ✔
49
      for (auto& n : energy[gin])
183,288 ✔
50
        n /= norm;
148,806 ✔
51
    }
52

53
    // Initialize the distribution data
54
    dist[gin].resize(in_gmax[gin] - in_gmin[gin] + 1);
34,917 ✔
55
    for (auto& v : dist[gin]) {
184,158 ✔
56
      v.resize(order);
149,241 ✔
57
    }
58
  }
59
}
10,134 ✔
60

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

63
void ScattData::base_combine(size_t max_order, size_t order_dim,
3,158 ✔
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();
3,158 ✔
69

70
  // Now allocate and zero our storage spaces
71
  tensor::Tensor<double> this_nuscatt_matrix =
3,158 ✔
72
    tensor::zeros<double>({groups, groups, order_dim});
3,158 ✔
73
  tensor::Tensor<double> this_nuscatt_P0 =
3,158 ✔
74
    tensor::zeros<double>({groups, groups});
3,158 ✔
75
  tensor::Tensor<double> this_scatt_P0 =
3,158 ✔
76
    tensor::zeros<double>({groups, groups});
3,158 ✔
77
  tensor::Tensor<double> this_mult = tensor::ones<double>({groups, groups});
3,158 ✔
78

79
  // Build the dense scattering and multiplicity matrices
80
  for (int i = 0; i < those_scatts.size(); i++) {
6,856 ✔
81
    ScattData* that = those_scatts[i];
3,698 ✔
82

83
    // Build the dense matrix for that object
84
    tensor::Tensor<double> that_matrix = that->get_matrix(max_order);
3,698 ✔
85

86
    // Now add that to this for the nu-scatter matrix
87
    this_nuscatt_matrix += scalars[i] * that_matrix;
3,698 ✔
88

89
    // Do the same with the P0 matrices
90
    for (int gin = 0; gin < groups; gin++) {
15,962 ✔
91
      for (int go = 0; go < groups; go++) {
272,216 ✔
92
        this_nuscatt_P0(gin, go) +=
519,904 ✔
93
          scalars[i] * that->get_xs(MgxsType::NU_SCATTER, gin, &go, nullptr);
259,952 ✔
94
        this_scatt_P0(gin, go) +=
519,904 ✔
95
          scalars[i] * that->get_xs(MgxsType::SCATTER, gin, &go, nullptr);
259,952 ✔
96
      }
97
    }
98
  }
3,698 ✔
99

100
  // Now we have the dense nuscatt and scatt, we can easily compute the
101
  // multiplicity matrix by dividing the two and fixing any nans
102
  this_mult = tensor::nan_to_num(this_nuscatt_P0 / this_scatt_P0);
6,316 ✔
103

104
  // We have the data, now we need to convert to a jagged array and then use
105
  // the initialize function to store it on the object.
106
  for (int gin = 0; gin < groups; gin++) {
14,387 ✔
107
    // Find the minimum and maximum group boundaries
108
    int gmin_;
109
    for (gmin_ = 0; gmin_ < groups; gmin_++) {
130,601 ✔
110
      bool non_zero = false;
379,280 ✔
111
      for (int l = 0; l < this_nuscatt_matrix.shape(2); l++) {
758,560 !
112
        if (this_nuscatt_matrix(gin, gmin_, l) != 0.) {
259,908 ✔
113
          non_zero = true;
114
          break;
115
        }
116
      }
117
      if (non_zero)
130,456 ✔
118
        break;
119
    }
120
    int gmax_;
11,229 ✔
121
    for (gmax_ = groups - 1; gmax_ >= 0; gmax_--) {
105,234 ✔
122
      bool non_zero = false;
296,399 ✔
123
      for (int l = 0; l < this_nuscatt_matrix.shape(2); l++) {
592,798 !
124
        if (this_nuscatt_matrix(gin, gmax_, l) != 0.) {
202,394 ✔
125
          non_zero = true;
126
          break;
127
        }
128
      }
129
      if (non_zero)
105,089 ✔
130
        break;
131
    }
132

133
    // treat the case of all values being 0
134
    if (gmin_ > gmax_) {
11,229 ✔
135
      gmin_ = gin;
145 ✔
136
      gmax_ = gin;
145 ✔
137
    }
138

139
    // Store the group bounds
140
    in_gmin[gin] = gmin_;
11,229 ✔
141
    in_gmax[gin] = gmax_;
11,229 ✔
142

143
    // Store the data in the compressed format
144
    sparse_scatter[gin].resize(gmax_ - gmin_ + 1);
11,229 ✔
145
    sparse_mult[gin].resize(gmax_ - gmin_ + 1);
11,229 ✔
146
    int i_gout = 0;
147
    for (int gout = gmin_; gout <= gmax_; gout++) {
60,356 ✔
148
      sparse_scatter[gin][i_gout].resize(this_nuscatt_matrix.shape(2));
98,254 !
149
      for (int l = 0; l < this_nuscatt_matrix.shape(2); l++) {
226,433 !
150
        sparse_scatter[gin][i_gout][l] = this_nuscatt_matrix(gin, gout, l);
128,179 ✔
151
      }
152
      sparse_mult[gin][i_gout] = this_mult(gin, gout);
49,127 ✔
153
      i_gout++;
49,127 ✔
154
    }
155
  }
156
}
12,632 ✔
157

158
//==============================================================================
159

160
void ScattData::sample_energy(int gin, int& gout, int& i_gout, uint64_t* seed)
1,686,724,666 ✔
161
{
162
  // Sample the outgoing group
163
  double xi = prn(seed);
1,686,724,666 ✔
164
  double prob = 0.;
1,686,724,666 ✔
165
  i_gout = 0;
1,686,724,666 ✔
166
  for (gout = gmin[gin]; gout < gmax[gin]; ++gout) {
2,053,387,028 ✔
167
    prob += energy[gin][i_gout];
1,622,638,611 ✔
168
    if (xi < prob)
1,622,638,611 ✔
169
      break;
170
    ++i_gout;
366,662,362 ✔
171
  }
172
}
1,686,724,666 ✔
173

174
//==============================================================================
175

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

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

228
//==============================================================================
229
// ScattDataLegendre methods
230
//==============================================================================
231

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

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

242
  // Get the scattering cross section value by summing the un-normalized P0
243
  // coefficient in the variable matrix over all outgoing groups.
244
  scattxs = tensor::zeros<double>({groups});
3,833 ✔
245
  for (int gin = 0; gin < groups; gin++) {
16,367 ✔
246
    int num_groups = in_gmax[gin] - in_gmin[gin] + 1;
12,534 ✔
247
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
63,626 ✔
248
      scattxs[gin] += matrix[gin][i_gout][0];
51,092 ✔
249
    }
250
  }
251

252
  // Build the energy transfer matrix from data in the variable matrix while
253
  // also normalizing the variable matrix itself
254
  // (forcing the CDF of f(mu=1) == 1)
255
  double_2dvec in_energy;
3,833 ✔
256
  in_energy.resize(groups);
3,833 ✔
257
  for (int gin = 0; gin < groups; gin++) {
16,367 ✔
258
    int num_groups = in_gmax[gin] - in_gmin[gin] + 1;
12,534 ✔
259
    in_energy[gin].resize(num_groups);
12,534 ✔
260
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
63,626 ✔
261
      double norm = matrix[gin][i_gout][0];
51,092 ✔
262
      in_energy[gin][i_gout] = norm;
51,092 ✔
263
      if (norm != 0.) {
51,092 ✔
264
        for (auto& n : matrix[gin][i_gout])
92,879 ✔
265
          n /= norm;
48,082 ✔
266
      }
267
    }
268
  }
269

270
  // Initialize the base class attributes
271
  ScattData::base_init(order, in_gmin, in_gmax, in_energy, in_mult);
3,833 ✔
272

273
  // Set the distribution (sdata.dist) values and initialize max_val
274
  max_val.resize(groups);
3,833 ✔
275
  for (int gin = 0; gin < groups; gin++) {
16,367 ✔
276
    int num_groups = gmax[gin] - gmin[gin] + 1;
12,534 ✔
277
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
63,626 ✔
278
      dist[gin][i_gout] = matrix[gin][i_gout];
51,092 ✔
279
    }
280
    max_val[gin].resize(num_groups);
12,534 ✔
281
    for (auto& n : max_val[gin])
63,626 ✔
282
      n = 0.;
51,092 ✔
283
  }
284

285
  // Now update the maximum value
286
  update_max_val();
3,833 ✔
287
}
3,833 ✔
288

289
//==============================================================================
290

291
void ScattDataLegendre::update_max_val()
3,833 ✔
292
{
293
  size_t groups = max_val.size();
3,833 ✔
294
  // Step through the polynomial with fixed number of points to identify the
295
  // maximal value
296
  int Nmu = 1001;
3,833 ✔
297
  double dmu = 2. / (Nmu - 1);
3,833 ✔
298
  for (int gin = 0; gin < groups; gin++) {
16,367 ✔
299
    int num_groups = gmax[gin] - gmin[gin] + 1;
12,534 ✔
300
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
63,626 ✔
301
      for (int imu = 0; imu < Nmu; imu++) {
51,194,184 ✔
302
        double mu;
51,143,092 ✔
303
        if (imu == 0) {
51,143,092 ✔
304
          mu = -1.;
305
        } else if (imu == (Nmu - 1)) {
51,092,000 ✔
306
          mu = 1.;
307
        } else {
308
          mu = -1. + (imu - 1) * dmu;
51,040,908 ✔
309
        }
310

311
        // Calculate probability
312
        double f = evaluate_legendre(
102,286,184 ✔
313
          dist[gin][i_gout].size() - 1, dist[gin][i_gout].data(), mu);
51,143,092 ✔
314

315
        // if this is a new maximum, store it
316
        if (f > max_val[gin][i_gout])
51,143,092 ✔
317
          max_val[gin][i_gout] = f;
1,428,547 ✔
318
      } // end imu loop
319

320
      // Since we may not have caught the true max, add 10% margin
321
      max_val[gin][i_gout] *= 1.1;
51,092 ✔
322
    }
323
  }
324
}
3,833 ✔
325

326
//==============================================================================
327

328
double ScattDataLegendre::calc_f(int gin, int gout, double mu)
76,294,229 ✔
329
{
330
  double f;
76,294,229 ✔
331
  if ((gout < gmin[gin]) || (gout > gmax[gin])) {
76,294,229 !
332
    f = 0.;
333
  } else {
334
    int i_gout = gout - gmin[gin];
76,294,229 ✔
335
    f = evaluate_legendre(
76,294,229 ✔
336
      dist[gin][i_gout].size() - 1, dist[gin][i_gout].data(), mu);
76,294,229 ✔
337
  }
338
  return f;
76,294,229 ✔
339
}
340

341
//==============================================================================
342

343
void ScattDataLegendre::sample(
32,649,144 ✔
344
  int gin, int& gout, double& mu, double& wgt, uint64_t* seed)
345
{
346
  // Sample the outgoing energy using the base-class method
347
  int i_gout;
32,649,144 ✔
348
  sample_energy(gin, gout, i_gout, seed);
32,649,144 ✔
349

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

367
  // Update the weight to reflect neutron multiplicity
368
  wgt *= mult[gin][i_gout];
32,649,144 ✔
369
}
32,649,144 ✔
370

371
//==============================================================================
372

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

389
  size_t groups = those_scatts[0]->energy.size();
270 ✔
390

391
  tensor::Tensor<int> in_gmin({groups}, 0);
270 ✔
392
  tensor::Tensor<int> in_gmax({groups}, 0);
270 ✔
393
  double_3dvec sparse_scatter(groups);
270 ✔
394
  double_2dvec sparse_mult(groups);
270 ✔
395

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

403
  // Got everything we need, store it.
404
  init(in_gmin, in_gmax, sparse_mult, sparse_scatter);
270 ✔
405
}
810 ✔
406

407
//==============================================================================
408

409
tensor::Tensor<double> ScattDataLegendre::get_matrix(size_t max_order)
285 ✔
410
{
411
  // Get the sizes and initialize the data to 0
412
  size_t groups = energy.size();
285 ✔
413
  size_t order_dim = max_order + 1;
285 ✔
414
  tensor::Tensor<double> matrix =
285 ✔
415
    tensor::zeros<double>({groups, groups, order_dim});
285 ✔
416

417
  for (int gin = 0; gin < groups; gin++) {
855 ✔
418
    for (int i_gout = 0; i_gout < energy[gin].size(); i_gout++) {
1,425 ✔
419
      int gout = i_gout + gmin[gin];
855 ✔
420
      for (int l = 0; l < order_dim; l++) {
2,565 ✔
421
        matrix(gin, gout, l) =
1,710 ✔
422
          scattxs[gin] * energy[gin][i_gout] * dist[gin][i_gout][l];
1,710 ✔
423
      }
424
    }
425
  }
426
  return matrix;
285 ✔
427
}
428

429
//==============================================================================
430
// ScattDataHistogram methods
431
//==============================================================================
432

433
void ScattDataHistogram::init(const tensor::Tensor<int>& in_gmin,
120 ✔
434
  const tensor::Tensor<int>& in_gmax, const double_2dvec& in_mult,
435
  const double_3dvec& coeffs)
436
{
437
  size_t groups = coeffs.size();
120 ✔
438
  size_t order = coeffs[0][0].size();
120 ✔
439

440
  // make a copy of coeffs that we can use to both extract data and normalize
441
  double_3dvec matrix = coeffs;
120 ✔
442

443
  // Get the scattering cross section value by summing the distribution
444
  // over all the histogram bins in angle and outgoing energy groups
445
  scattxs = tensor::zeros<double>({groups});
120 ✔
446
  for (int gin = 0; gin < groups; gin++) {
360 ✔
447
    for (int i_gout = 0; i_gout < matrix[gin].size(); i_gout++) {
600 ✔
448
      scattxs[gin] += std::accumulate(
360 ✔
449
        matrix[gin][i_gout].begin(), matrix[gin][i_gout].end(), 0.);
360 ✔
450
    }
451
  }
452

453
  // Build the energy transfer matrix from data in the variable matrix
454
  double_2dvec in_energy;
120 ✔
455
  in_energy.resize(groups);
120 ✔
456
  for (int gin = 0; gin < groups; gin++) {
360 ✔
457
    int num_groups = in_gmax[gin] - in_gmin[gin] + 1;
240 ✔
458
    in_energy[gin].resize(num_groups);
240 ✔
459
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
600 ✔
460
      double norm = std::accumulate(
720 ✔
461
        matrix[gin][i_gout].begin(), matrix[gin][i_gout].end(), 0.);
360 ✔
462
      in_energy[gin][i_gout] = norm;
360 !
463
      if (norm != 0.) {
360 !
464
        for (auto& n : matrix[gin][i_gout])
10,530 ✔
465
          n /= norm;
10,170 ✔
466
      }
467
    }
468
  }
469

470
  // Initialize the base class attributes
471
  ScattData::base_init(order, in_gmin, in_gmax, in_energy, in_mult);
120 ✔
472

473
  // Build the angular distribution mu values
474
  mu = tensor::linspace(-1., 1., order + 1);
120 ✔
475
  dmu = 2. / order;
120 ✔
476

477
  // Calculate f(mu) and integrate it so we can avoid rejection sampling
478
  fmu.resize(groups);
120 ✔
479
  for (int gin = 0; gin < groups; gin++) {
360 ✔
480
    int num_groups = gmax[gin] - gmin[gin] + 1;
240 ✔
481
    fmu[gin].resize(num_groups);
240 ✔
482
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
600 ✔
483
      fmu[gin][i_gout].resize(order);
360 ✔
484
      // The variable matrix contains f(mu); so directly assign it
485
      fmu[gin][i_gout] = matrix[gin][i_gout];
360 ✔
486

487
      // Integrate the histogram
488
      dist[gin][i_gout][0] = dmu * matrix[gin][i_gout][0];
360 ✔
489
      for (int imu = 1; imu < order; imu++) {
10,170 ✔
490
        dist[gin][i_gout][imu] =
9,810 ✔
491
          dmu * matrix[gin][i_gout][imu] + dist[gin][i_gout][imu - 1];
9,810 ✔
492
      }
493

494
      // Now re-normalize for integral to unity
495
      double norm = dist[gin][i_gout][order - 1];
360 !
496
      if (norm > 0.) {
360 !
497
        for (int imu = 0; imu < order; imu++) {
10,530 ✔
498
          fmu[gin][i_gout][imu] /= norm;
10,170 ✔
499
          dist[gin][i_gout][imu] /= norm;
10,170 ✔
500
        }
501
      }
502
    }
503
  }
504
}
120 ✔
505

506
//==============================================================================
507

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

524
    f = fmu[gin][i_gout][imu];
×
525
  }
526
  return f;
×
527
}
528

529
//==============================================================================
530

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

538
  // Determine the outgoing cosine bin
539
  double xi = prn(seed);
31,768 ✔
540

541
  int imu;
31,768 ✔
542
  if (xi < dist[gin][i_gout][0]) {
31,768 ✔
543
    imu = 0;
544
  } else {
545
    imu =
30,239 ✔
546
      std::upper_bound(dist[gin][i_gout].begin(), dist[gin][i_gout].end(), xi) -
30,239 ✔
547
      dist[gin][i_gout].begin();
30,239 ✔
548
  }
549

550
  // Randomly select mu within the imu bin
551
  mu = prn(seed) * dmu + this->mu[imu];
31,768 !
552

553
  mu = std::clamp(mu, -1., 1.);
31,768 !
554

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

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

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

569
  for (int gin = 0; gin < groups; gin++) {
180 ✔
570
    for (int i_gout = 0; i_gout < energy[gin].size(); i_gout++) {
300 ✔
571
      int gout = i_gout + gmin[gin];
180 ✔
572
      for (int l = 0; l < order_dim; l++) {
5,265 ✔
573
        matrix(gin, gout, l) =
5,085 ✔
574
          scattxs[gin] * energy[gin][i_gout] * fmu[gin][i_gout][l];
5,085 ✔
575
      }
576
    }
577
  }
578
  return matrix;
60 ✔
579
}
580

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

583
void ScattDataHistogram::combine(
60 ✔
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();
60 ✔
588
  for (int i = 0; i < those_scatts.size(); i++) {
120 ✔
589
    // Lets also make sure these items are combineable
590
    ScattDataHistogram* that =
60 ✔
591
      dynamic_cast<ScattDataHistogram*>(those_scatts[i]);
60 !
592
    if (!that) {
60 !
UNCOV
593
      fatal_error("Cannot combine the ScattData objects!");
×
594
    }
595
    if (max_order != that->get_order()) {
60 !
UNCOV
596
      fatal_error("Cannot combine the ScattData objects!");
×
597
    }
598
  }
599

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

602
  tensor::Tensor<int> in_gmin({groups}, 0);
60 ✔
603
  tensor::Tensor<int> in_gmax({groups}, 0);
60 ✔
604
  double_3dvec sparse_scatter(groups);
60 ✔
605
  double_2dvec sparse_mult(groups);
60 ✔
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;
60 ✔
611
  ScattData::base_combine(max_order, order_dim, those_scatts, scalars, in_gmin,
60 ✔
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);
60 ✔
616
}
180 ✔
617

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

622
void ScattDataTabular::init(const tensor::Tensor<int>& in_gmin,
2,888 ✔
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,888 ✔
627
  size_t order = coeffs[0][0].size();
2,888 ✔
628

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

632
  // Build the angular distribution mu values
633
  mu = tensor::linspace(-1., 1., order);
2,888 ✔
634
  dmu = 2. / (order - 1);
2,888 ✔
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,888 ✔
639
  for (int gin = 0; gin < groups; gin++) {
13,577 ✔
640
    for (int i_gout = 0; i_gout < matrix[gin].size(); i_gout++) {
59,006 ✔
641
      for (int imu = 1; imu < order; imu++) {
124,714 ✔
642
        scattxs[gin] +=
76,397 ✔
643
          0.5 * dmu * (matrix[gin][i_gout][imu - 1] + matrix[gin][i_gout][imu]);
76,397 ✔
644
      }
645
    }
646
  }
647

648
  // Build the energy transfer matrix from data in the variable matrix
649
  double_2dvec in_energy(groups);
2,888 ✔
650
  for (int gin = 0; gin < groups; gin++) {
13,577 ✔
651
    int num_groups = in_gmax[gin] - in_gmin[gin] + 1;
10,689 ✔
652
    in_energy[gin].resize(num_groups);
10,689 ✔
653
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
59,006 ✔
654
      double norm = 0.;
655
      for (int imu = 1; imu < order; imu++) {
124,714 ✔
656
        norm +=
76,397 ✔
657
          0.5 * dmu * (matrix[gin][i_gout][imu - 1] + matrix[gin][i_gout][imu]);
76,397 ✔
658
      }
659
      in_energy[gin][i_gout] = norm;
48,317 ✔
660
    }
661
  }
662

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

666
  // Calculate f(mu) and integrate it so we can avoid rejection sampling
667
  fmu.resize(groups);
2,888 ✔
668
  for (int gin = 0; gin < groups; gin++) {
13,577 ✔
669
    int num_groups = gmax[gin] - gmin[gin] + 1;
10,689 ✔
670
    fmu[gin].resize(num_groups);
10,689 ✔
671
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
59,006 ✔
672
      fmu[gin][i_gout].resize(order);
48,317 ✔
673
      // The variable matrix contains f(mu); so directly assign it
674
      fmu[gin][i_gout] = matrix[gin][i_gout];
48,317 ✔
675

676
      // Ensure positivity
677
      for (auto& val : fmu[gin][i_gout]) {
173,031 ✔
678
        if (val < 0.)
124,714 ✔
679
          val = 0.;
1,650 ✔
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.;
685
      for (int imu = 1; imu < order; imu++) {
124,714 ✔
686
        norm += 0.5 * dmu * (fmu[gin][i_gout][imu - 1] + fmu[gin][i_gout][imu]);
76,397 ✔
687
        // incorporate to the CDF
688
        dist[gin][i_gout][imu] = norm;
76,397 ✔
689
      }
690

691
      // now do the normalization
692
      if (norm > 0.) {
48,317 ✔
693
        for (int imu = 0; imu < order; imu++) {
151,206 ✔
694
          fmu[gin][i_gout][imu] /= norm;
110,009 ✔
695
          dist[gin][i_gout][imu] /= norm;
110,009 ✔
696
        }
697
      }
698
    }
699
  }
700
}
2,888 ✔
701

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

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

UNCOV
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
  }
UNCOV
723
  return f;
×
724
}
725

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

728
void ScattDataTabular::sample(
1,654,043,754 ✔
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;
1,654,043,754 ✔
733
  sample_energy(gin, gout, i_gout, seed);
1,654,043,754 ✔
734

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

739
  double c_k = dist[gin][i_gout][0];
1,654,043,754 ✔
740
  int k;
1,654,043,754 ✔
741
  for (k = 0; k < NP - 1; k++) {
1,674,592,579 !
742
    double c_k1 = dist[gin][i_gout][k + 1];
1,674,592,579 ✔
743
    if (xi < c_k1)
1,674,592,579 ✔
744
      break;
745
    c_k = c_k1;
20,548,825 ✔
746
  }
747

748
  // Check to make sure k is <= NP - 1
749
  k = std::min(k, NP - 2);
1,654,043,754 !
750

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

757
  if (p0 == p1) {
1,654,043,754 ✔
758
    mu = mu0 + (xi - c_k) / p0;
1,653,021,040 ✔
759
  } else {
760
    double frac = (p1 - p0) / (mu1 - mu0);
1,022,714 ✔
761
    mu =
2,045,428 ✔
762
      mu0 +
1,022,714 ✔
763
      (std::sqrt(std::max(0., p0 * p0 + 2. * frac * (xi - c_k))) - p0) / frac;
2,045,428 !
764
  }
765

766
  mu = std::clamp(mu, -1., 1.);
1,654,043,754 !
767

768
  // Update the weight to reflect neutron multiplicity
769
  wgt *= mult[gin][i_gout];
1,654,043,754 ✔
770
}
1,654,043,754 ✔
771

772
//==============================================================================
773

774
tensor::Tensor<double> ScattDataTabular::get_matrix(size_t max_order)
3,353 ✔
775
{
776
  // Get the sizes and initialize the data to 0
777
  size_t groups = energy.size();
3,353 ✔
778
  // We ignore the requested order for Histogram and Tabular representations
779
  size_t order_dim = get_order();
3,353 ✔
780
  tensor::Tensor<double> matrix =
3,353 ✔
781
    tensor::zeros<double>({groups, groups, order_dim});
3,353 ✔
782

783
  for (int gin = 0; gin < groups; gin++) {
14,927 ✔
784
    for (int i_gout = 0; i_gout < energy[gin].size(); i_gout++) {
61,226 ✔
785
      int gout = i_gout + gmin[gin];
49,652 ✔
786
      for (int l = 0; l < order_dim; l++) {
174,156 ✔
787
        matrix(gin, gout, l) =
124,504 ✔
788
          scattxs[gin] * energy[gin][i_gout] * fmu[gin][i_gout][l];
124,504 ✔
789
      }
790
    }
791
  }
792
  return matrix;
3,353 ✔
793
}
794

795
//==============================================================================
796

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

813
  size_t groups = those_scatts[0]->energy.size();
2,828 ✔
814

815
  tensor::Tensor<int> in_gmin({groups}, 0);
2,828 ✔
816
  tensor::Tensor<int> in_gmax({groups}, 0);
2,828 ✔
817
  double_3dvec sparse_scatter(groups);
2,828 ✔
818
  double_2dvec sparse_mult(groups);
2,828 ✔
819

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

827
  // Got everything we need, store it.
828
  init(in_gmin, in_gmax, sparse_mult, sparse_scatter);
2,828 ✔
829
}
8,484 ✔
830

831
//==============================================================================
832
// module-level methods
833
//==============================================================================
834

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

849
  tab.base_init(n_mu, leg.gmin, leg.gmax, leg.energy, leg.mult);
3,293 ✔
850
  tab.scattxs = leg.scattxs;
3,293 ✔
851

852
  // Build mu and dmu
853
  tab.mu = tensor::linspace(-1., 1., n_mu);
3,293 ✔
854
  tab.dmu = 2. / (n_mu - 1);
3,293 ✔
855

856
  // Calculate f(mu) and integrate it so we can avoid rejection sampling
857
  size_t groups = tab.energy.size();
3,293 ✔
858
  tab.fmu.resize(groups);
3,293 ✔
859
  for (int gin = 0; gin < groups; gin++) {
14,747 ✔
860
    int num_groups = tab.gmax[gin] - tab.gmin[gin] + 1;
11,454 ✔
861
    tab.fmu[gin].resize(num_groups);
11,454 ✔
862
    for (int i_gout = 0; i_gout < num_groups; i_gout++) {
60,926 ✔
863
      tab.fmu[gin][i_gout].resize(n_mu);
49,472 ✔
864
      for (int imu = 0; imu < n_mu; imu++) {
170,736 ✔
865
        tab.fmu[gin][i_gout][imu] =
121,264 ✔
866
          evaluate_legendre(leg.dist[gin][i_gout].size() - 1,
121,264 ✔
867
            leg.dist[gin][i_gout].data(), tab.mu[imu]);
121,264 ✔
868
      }
869

870
      // Ensure positivity
871
      for (auto& val : tab.fmu[gin][i_gout]) {
170,736 ✔
872
        if (val < 0.)
121,264 ✔
873
          val = 0.;
870 ✔
874
      }
875

876
      // Now re-normalize for numerical integration issues and to take care of
877
      // the above negative fix-up.  Also accrue the CDF
878
      double norm = 0.;
49,472 ✔
879
      tab.dist[gin][i_gout][0] = 0.;
49,472 ✔
880
      for (int imu = 1; imu < n_mu; imu++) {
121,264 ✔
881
        norm += 0.5 * tab.dmu *
71,792 ✔
882
                (tab.fmu[gin][i_gout][imu - 1] + tab.fmu[gin][i_gout][imu]);
71,792 ✔
883
        // incorporate to the CDF
884
        tab.dist[gin][i_gout][imu] = norm;
71,792 ✔
885
      }
886

887
      // now do the normalization
888
      if (norm > 0.) {
49,472 ✔
889
        for (int imu = 0; imu < n_mu; imu++) {
151,386 ✔
890
          tab.fmu[gin][i_gout][imu] /= norm;
108,209 ✔
891
          tab.dist[gin][i_gout][imu] /= norm;
108,209 ✔
892
        }
893
      }
894
    }
895
  }
896
}
3,293 ✔
897

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

© 2026 Coveralls, Inc