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

openmc-dev / openmc / 17361657497

31 Aug 2025 07:54PM UTC coverage: 84.729%. First build
17361657497

Pull #3550

github

web-flow
Merge 98ca32e81 into 00edc7769
Pull Request #3550: [Point Detector] Add distribution get_pdf functionality

7 of 356 new or added lines in 10 files covered. (1.97%)

52947 of 62490 relevant lines covered (84.73%)

38803853.85 hits per line

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

51.8
/src/secondary_thermal.cpp
1
#include "openmc/secondary_thermal.h"
2

3
#include "openmc/hdf5_interface.h"
4
#include "openmc/random_lcg.h"
5
#include "openmc/search.h"
6
#include "openmc/vector.h"
7

8
#include "xtensor/xview.hpp"
9

10
#include <cassert>
11
#include <cmath> // for log, exp
12

13
namespace openmc {
14

15
// Helper function to get index on incident energy grid
16
void get_energy_index(
82,357,449✔
17
  const vector<double>& energies, double E, int& i, double& f)
18
{
19
  // Get index and interpolation factor for elastic grid
20
  i = 0;
82,357,449✔
21
  f = 0.0;
82,357,449✔
22
  if (E >= energies.front()) {
82,357,449✔
23
    i = lower_bound_index(energies.begin(), energies.end(), E);
82,357,438✔
24
    if (i + 1 < energies.size())
82,357,438✔
25
      f = (E - energies[i]) / (energies[i + 1] - energies[i]);
82,356,096✔
26
  }
27
}
82,357,449✔
28

NEW
29
double get_pdf_discrete(
×
30
  const vector<double>& mu, const vector<double>& w, double mu_0)
31
{
32
  // Make sure mu is in range [-1,1]
NEW
33
  if (std::abs(mu_0) > 1.0)
×
NEW
34
    mu_0 = std::copysign(1.0, mu_0);
×
35
  double a0;
36
  double a1;
37
  double b0;
38
  double b1;
NEW
39
  int32_t ai = -1;
×
NEW
40
  int32_t bi = -1;
×
NEW
41
  if (mu_0 > mu[0]) {
×
NEW
42
    ai = lower_bound_index(mu.begin(), mu.end(), mu_0);
×
NEW
43
    a0 = mu[ai];
×
NEW
44
    a1 = (ai > 1) ? mu[ai - 1] : -1.0;
×
45
  } else {
NEW
46
    a0 = -1.0;
×
NEW
47
    a1 = -1.0;
×
48
  }
NEW
49
  if (mu_0 < mu[mu.size() - 1]) {
×
NEW
50
    bi = upper_bound_index(mu.begin(), mu.end(), mu_0);
×
NEW
51
    b0 = mu[bi];
×
NEW
52
    b1 = (bi < mu.size() - 1) ? mu[bi + 1] : 1.0;
×
53
  } else {
NEW
54
    b0 = 1.0;
×
NEW
55
    b1 = 1.0;
×
56
  }
57

58
  //  Calculate Delta_a and Delta_b
NEW
59
  double delta_a = 0.5 * std::min(b0 - a0, a0 - a1);
×
NEW
60
  double delta_b = 0.5 * std::min(b1 - b0, b0 - a0);
×
61

NEW
62
  if (mu_0 < a0 + delta_a)
×
NEW
63
    return w[ai] / (2.0 * delta_a);
×
NEW
64
  else if (mu_0 + delta_b < b0)
×
NEW
65
    return w[bi] / (2.0 * delta_b);
×
66
  else
NEW
67
    return 0.0;
×
68
}
69

NEW
70
double get_pdf_discrete(const vector<double>& mu, double mu_0)
×
71
{
NEW
72
  vector<double> w(mu.size(), 1.0 / mu.size());
×
NEW
73
  return get_pdf_discrete(mu, w, mu_0);
×
74
}
75

76
//==============================================================================
77
// CoherentElasticAE implementation
78
//==============================================================================
79

80
CoherentElasticAE::CoherentElasticAE(const CoherentElasticXS& xs) : xs_ {xs} {}
140✔
81

82
void CoherentElasticAE::sample(
181,951✔
83
  double E_in, double& E_out, double& mu, uint64_t* seed) const
84
{
85
  // Energy doesn't change in elastic scattering (ENDF-102, Eq. 7-1)
86
  E_out = E_in;
181,951✔
87

88
  const auto& energies {xs_.bragg_edges()};
181,951✔
89

90
  assert(E_in >= energies.front());
148,869✔
91

92
  const int i = lower_bound_index(energies.begin(), energies.end(), E_in);
181,951✔
93

94
  // Sample a Bragg edge between 1 and i
95
  // E[0] < E_in < E[i+1] -> can scatter in bragg edges 0..i
96
  const auto& factors = xs_.factors();
181,951✔
97
  const double prob = prn(seed) * factors[i];
181,951✔
98

99
  const int k = std::lower_bound(factors.begin(), factors.begin() + i, prob) -
181,951✔
100
                factors.begin();
181,951✔
101

102
  // Characteristic scattering cosine for this Bragg edge (ENDF-102, Eq. 7-2)
103
  mu = 1.0 - 2.0 * energies[k] / E_in;
181,951✔
104
}
181,951✔
105

NEW
106
double CoherentElasticAE::get_pdf(
×
107
  double E_in, double& E_out, double& mu, uint64_t* seed) const
108
{
109
  // Energy doesn't change in elastic scattering (ENDF-102, Eq. 7-1)
110

111
  double pdf;
NEW
112
  E_out = E_in;
×
NEW
113
  const auto& energies {xs_.bragg_edges()};
×
NEW
114
  const auto& factors = xs_.factors();
×
115

NEW
116
  if (E_in < energies.front() || E_in > energies.back()) {
×
NEW
117
    return 0;
×
118
  }
119

NEW
120
  const int i = lower_bound_index(energies.begin(), energies.end(), E_in);
×
NEW
121
  vector<double> energies_cut(energies.begin(), energies.begin() + i + 1);
×
NEW
122
  vector<double> factors_cut(factors.begin(), factors.begin() + i + 1);
×
123

NEW
124
  vector<double> mu_vector_rev;
×
NEW
125
  std::transform(energies_cut.begin(), energies_cut.end(),
×
126
    std::back_inserter(mu_vector_rev),
NEW
127
    [E_in](double Ei) { return 1 - 2 * Ei / E_in; });
×
NEW
128
  vector<double> mu_vector(mu_vector_rev.rbegin(), mu_vector_rev.rend());
×
129

130
  auto f = xt::adapt(factors_cut, {
NEW
131
                                    factors_cut.size(),
×
NEW
132
                                  });
×
NEW
133
  auto weights = xt::diff(f);
×
NEW
134
  weights /= xt::sum(weights);
×
NEW
135
  vector<double> w(weights.begin(), weights.end());
×
NEW
136
  return get_pdf_discrete(mu_vector, w, mu);
×
137
}
138

139
//==============================================================================
140
// IncoherentElasticAE implementation
141
//==============================================================================
142

143
IncoherentElasticAE::IncoherentElasticAE(hid_t group)
×
144
{
145
  read_dataset(group, "debye_waller", debye_waller_);
×
146
}
147

148
void IncoherentElasticAE::sample(
×
149
  double E_in, double& E_out, double& mu, uint64_t* seed) const
150
{
151
  // Sample angle by inverting the distribution in ENDF-102, Eq. 7.4
152
  double c = 2 * E_in * debye_waller_;
×
153
  mu = std::log(1.0 + prn(seed) * (std::exp(2.0 * c) - 1)) / c - 1.0;
×
154

155
  // Energy doesn't change in elastic scattering (ENDF-102, Eq. 7.4)
156
  E_out = E_in;
×
157
}
NEW
158
double IncoherentElasticAE::get_pdf(
×
159
  double E_in, double& E_out, double& mu, uint64_t* seed) const
160
{
161
  // Sample angle by inverting the distribution in ENDF-102, Eq. 7.4
NEW
162
  double c = 2 * E_in * debye_waller_;
×
NEW
163
  E_out = E_in;
×
164

NEW
165
  double A = c / (1 - std::exp(-2.0 * c)); // normalization factor
×
NEW
166
  double pdf = A * std::exp(-c * (1 - mu));
×
NEW
167
  return pdf;
×
168

169
  // Energy doesn't change in elastic scattering (ENDF-102, Eq. 7.4)
170
}
171

172
//==============================================================================
173
// IncoherentElasticAEDiscrete implementation
174
//==============================================================================
175

176
IncoherentElasticAEDiscrete::IncoherentElasticAEDiscrete(
44✔
177
  hid_t group, const vector<double>& energy)
44✔
178
  : energy_ {energy}
44✔
179
{
180
  read_dataset(group, "mu_out", mu_out_);
44✔
181
}
44✔
182

183
void IncoherentElasticAEDiscrete::sample(
1,342✔
184
  double E_in, double& E_out, double& mu, uint64_t* seed) const
185
{
186
  // Get index and interpolation factor for elastic grid
187
  int i;
188
  double f;
189
  get_energy_index(energy_, E_in, i, f);
1,342✔
190

191
  // Interpolate between two discrete cosines corresponding to neighboring
192
  // incoming energies.
193

194
  // Sample outgoing cosine bin
195
  int n_mu = mu_out_.shape()[1];
1,342✔
196
  int k = prn(seed) * n_mu;
1,342✔
197

198
  // Rather than use the sampled discrete mu directly, it is smeared over
199
  // a bin of width 0.5*min(mu[k] - mu[k-1], mu[k+1] - mu[k]) centered on the
200
  // discrete mu value itself.
201

202
  // Interpolate kth mu value between distributions at energies i and i+1
203
  mu = mu_out_(i, k) + f * (mu_out_(i + 1, k) - mu_out_(i, k));
1,342✔
204

205
  // Inteprolate (k-1)th mu value between distributions at energies i and i+1.
206
  // When k==0, pick a value that will smear the cosine out to a minimum of -1.
207
  double mu_left = (k == 0) ? -1.0 - (mu + 1.0)
1,342✔
208
                            : mu_out_(i, k - 1) +
990✔
209
                                f * (mu_out_(i + 1, k - 1) - mu_out_(i, k - 1));
990✔
210

211
  // Inteprolate (k+1)th mu value between distributions at energies i and i+1.
212
  // When k is the last discrete value, pick a value that will smear the cosine
213
  // out to a maximum of 1.
214
  double mu_right =
215
    (k == n_mu - 1)
1,342✔
216
      ? 1.0 + (1.0 - mu)
1,342✔
217
      : mu_out_(i, k + 1) + f * (mu_out_(i + 1, k + 1) - mu_out_(i, k + 1));
990✔
218

219
  // Smear cosine
220
  mu += std::min(mu - mu_left, mu_right - mu) * (prn(seed) - 0.5);
1,342✔
221

222
  // Energy doesn't change in elastic scattering
223
  E_out = E_in;
1,342✔
224
}
1,342✔
225

NEW
226
double IncoherentElasticAEDiscrete::get_pdf(
×
227
  double E_in, double& E_out, double& mu, uint64_t* seed) const
228
{
229
  // Get index and interpolation factor for elastic grid
230
  int i;
231
  double f;
NEW
232
  get_energy_index(energy_, E_in, i, f);
×
233
  // Energy doesn't change in elastic scattering
NEW
234
  E_out = E_in;
×
NEW
235
  int n_mu = mu_out_.shape()[1];
×
236

NEW
237
  std::vector<double> mu_vector;
×
238

NEW
239
  for (int k = 0; k < n_mu; ++k) {
×
NEW
240
    double mu_k = mu_out_(i, k) + f * (mu_out_(i + 1, k) - mu_out_(i, k));
×
NEW
241
    mu_vector.push_back(mu_k);
×
242
  }
243

NEW
244
  return get_pdf_discrete(mu_vector, mu);
×
245
}
246

247
//==============================================================================
248
// IncoherentInelasticAEDiscrete implementation
249
//==============================================================================
250

251
IncoherentInelasticAEDiscrete::IncoherentInelasticAEDiscrete(
1,174✔
252
  hid_t group, const vector<double>& energy)
1,174✔
253
  : energy_ {energy}
1,174✔
254
{
255
  read_dataset(group, "energy_out", energy_out_);
1,174✔
256
  read_dataset(group, "mu_out", mu_out_);
1,174✔
257
  read_dataset(group, "skewed", skewed_);
1,174✔
258
}
1,174✔
259

260
void IncoherentInelasticAEDiscrete::sample(
78,741,210✔
261
  double E_in, double& E_out, double& mu, uint64_t* seed) const
262
{
263
  // Get index and interpolation factor for inelastic grid
264
  int i;
265
  double f;
266
  get_energy_index(energy_, E_in, i, f);
78,741,210✔
267

268
  // Now that we have an incoming energy bin, we need to determine the outgoing
269
  // energy bin. This will depend on whether the outgoing energy distribution is
270
  // skewed. If it is skewed, then the first two and last two bins have lower
271
  // probabilities than the other bins (0.1 for the first and last bins and 0.4
272
  // for the second and second to last bins, relative to a normal bin
273
  // probability of 1). Otherwise, each bin is equally probable.
274

275
  int j;
276
  int n = energy_out_.shape()[1];
78,741,210✔
277
  if (!skewed_) {
78,741,210✔
278
    // All bins equally likely
279
    j = prn(seed) * n;
×
280
  } else {
281
    // Distribution skewed away from edge points
282
    double r = prn(seed) * (n - 3);
78,741,210✔
283
    if (r > 1.0) {
78,741,210✔
284
      // equally likely N-4 middle bins
285
      j = r + 1;
77,449,864✔
286
    } else if (r > 0.6) {
1,291,346✔
287
      // second to last bin has relative probability of 0.4
288
      j = n - 2;
515,942✔
289
    } else if (r > 0.5) {
775,404✔
290
      // last bin has relative probability of 0.1
291
      j = n - 1;
129,073✔
292
    } else if (r > 0.1) {
646,331✔
293
      // second bin has relative probability of 0.4
294
      j = 1;
518,560✔
295
    } else {
296
      // first bin has relative probability of 0.1
297
      j = 0;
127,771✔
298
    }
299
  }
300

301
  // Determine outgoing energy corresponding to E_in[i] and E_in[i+1]
302
  double E_ij = energy_out_(i, j);
78,741,210✔
303
  double E_i1j = energy_out_(i + 1, j);
78,741,210✔
304

305
  // Outgoing energy
306
  E_out = (1 - f) * E_ij + f * E_i1j;
78,741,210✔
307

308
  // Sample outgoing cosine bin
309
  int m = mu_out_.shape()[2];
78,741,210✔
310
  int k = prn(seed) * m;
78,741,210✔
311

312
  // Determine outgoing cosine corresponding to E_in[i] and E_in[i+1]
313
  double mu_ijk = mu_out_(i, j, k);
78,741,210✔
314
  double mu_i1jk = mu_out_(i + 1, j, k);
78,741,210✔
315

316
  // Cosine of angle between incoming and outgoing neutron
317
  mu = (1 - f) * mu_ijk + f * mu_i1jk;
78,741,210✔
318
}
78,741,210✔
319

NEW
320
double IncoherentInelasticAEDiscrete::get_pdf(
×
321
  double E_in, double& E_out, double& mu, uint64_t* seed, int l) const
322
{
323
  // Get index and interpolation factor for inelastic grid
324
  int i;
325
  double f;
NEW
326
  get_energy_index(energy_, E_in, i, f);
×
327
  int j;
NEW
328
  int n = energy_out_.shape()[1];
×
NEW
329
  if (!skewed_) {
×
330
    // All bins equally likely
NEW
331
    j = prn(seed) * n;
×
332
  } else {
333
    // Distribution skewed away from edge points
NEW
334
    double r = prn(seed) * (n - 3);
×
NEW
335
    if (r > 1.0) {
×
336
      // equally likely N-4 middle bins
NEW
337
      j = r + 1;
×
NEW
338
    } else if (r > 0.6) {
×
339
      // second to last bin has relative probability of 0.4
NEW
340
      j = n - 2;
×
NEW
341
    } else if (r > 0.5) {
×
342
      // last bin has relative probability of 0.1
NEW
343
      j = n - 1;
×
NEW
344
    } else if (r > 0.1) {
×
345
      // second bin has relative probability of 0.4
NEW
346
      j = 1;
×
347
    } else {
348
      // first bin has relative probability of 0.1
NEW
349
      j = 0;
×
350
    }
351
  }
NEW
352
  if (l != -1) {
×
NEW
353
    j = l;
×
354
  } // take j as param
355
  // Determine outgoing energy corresponding to E_in[i] and E_in[i+1]
NEW
356
  double E_ij = energy_out_(i, j);
×
NEW
357
  double E_i1j = energy_out_(i + 1, j);
×
358

359
  // Outgoing energy
NEW
360
  E_out = (1 - f) * E_ij + f * E_i1j;
×
NEW
361
  int m = mu_out_.shape()[2];
×
NEW
362
  std::vector<double> mu_vector;
×
363

NEW
364
  for (int k = 0; k < m; ++k) {
×
NEW
365
    double mu_ijk = mu_out_(i, j, k);
×
NEW
366
    double mu_i1jk = mu_out_(i + 1, j, k);
×
NEW
367
    double mu_k = (1 - f) * mu_ijk + f * mu_i1jk;
×
NEW
368
    mu_vector.push_back(mu_k);
×
369
  }
370

NEW
371
  return get_pdf_discrete(mu_vector, mu);
×
372
}
373

374
//==============================================================================
375
// IncoherentInelasticAE implementation
376
//==============================================================================
377

378
IncoherentInelasticAE::IncoherentInelasticAE(hid_t group)
44✔
379
{
380
  // Read correlated angle-energy distribution
381
  CorrelatedAngleEnergy dist {group};
44✔
382

383
  // Copy incident energies
384
  energy_ = dist.energy();
44✔
385

386
  // Convert to S(a,b) native format
387
  for (const auto& edist : dist.distribution()) {
176✔
388
    // Create temporary distribution
389
    DistEnergySab d;
132✔
390

391
    // Copy outgoing energy distribution
392
    d.n_e_out = edist.e_out.size();
132✔
393
    d.e_out = edist.e_out;
132✔
394
    d.e_out_pdf = edist.p;
132✔
395
    d.e_out_cdf = edist.c;
132✔
396

397
    for (int j = 0; j < d.n_e_out; ++j) {
660✔
398
      auto adist = dynamic_cast<Tabular*>(edist.angle[j].get());
528✔
399
      if (adist) {
528✔
400
        // On first pass, allocate space for angles
401
        if (j == 0) {
528✔
402
          auto n_mu = adist->x().size();
132✔
403
          d.mu = xt::empty<double>({d.n_e_out, n_mu});
132✔
404
        }
405

406
        // Copy outgoing angles
407
        auto mu_j = xt::view(d.mu, j);
528✔
408
        std::copy(adist->x().begin(), adist->x().end(), mu_j.begin());
528✔
409
      }
528✔
410
    }
411

412
    distribution_.emplace_back(std::move(d));
132✔
413
  }
132✔
414
}
44✔
415

416
void IncoherentInelasticAE::sample(
3,614,897✔
417
  double E_in, double& E_out, double& mu, uint64_t* seed) const
418
{
419
  // Get index and interpolation factor for inelastic grid
420
  int i;
421
  double f;
422
  get_energy_index(energy_, E_in, i, f);
3,614,897✔
423

424
  // Pick closer energy based on interpolation factor
425
  int l = f > 0.5 ? i + 1 : i;
3,614,897✔
426

427
  // Determine outgoing energy bin
428
  // (First reset n_energy_out to the right value)
429
  auto n = distribution_[l].n_e_out;
3,614,897✔
430
  double r1 = prn(seed);
3,614,897✔
431
  double c_j = distribution_[l].e_out_cdf[0];
3,614,897✔
432
  double c_j1;
433
  std::size_t j;
434
  for (j = 0; j < n - 1; ++j) {
8,721,537✔
435
    c_j1 = distribution_[l].e_out_cdf[j + 1];
8,721,537✔
436
    if (r1 < c_j1)
8,721,537✔
437
      break;
3,614,897✔
438
    c_j = c_j1;
5,106,640✔
439
  }
440

441
  // check to make sure j is <= n_energy_out - 2
442
  j = std::min(j, n - 2);
3,614,897✔
443

444
  // Get the data to interpolate between
445
  double E_l_j = distribution_[l].e_out[j];
3,614,897✔
446
  double p_l_j = distribution_[l].e_out_pdf[j];
3,614,897✔
447

448
  // Next part assumes linear-linear interpolation in standard
449
  double E_l_j1 = distribution_[l].e_out[j + 1];
3,614,897✔
450
  double p_l_j1 = distribution_[l].e_out_pdf[j + 1];
3,614,897✔
451

452
  // Find secondary energy (variable E)
453
  double frac = (p_l_j1 - p_l_j) / (E_l_j1 - E_l_j);
3,614,897✔
454
  if (frac == 0.0) {
3,614,897✔
455
    E_out = E_l_j + (r1 - c_j) / p_l_j;
3,614,897✔
456
  } else {
457
    E_out = E_l_j +
×
458
            (std::sqrt(std::max(0.0, p_l_j * p_l_j + 2.0 * frac * (r1 - c_j))) -
×
459
              p_l_j) /
×
460
              frac;
461
  }
462

463
  // Adjustment of outgoing energy
464
  double E_l = energy_[l];
3,614,897✔
465
  if (E_out < 0.5 * E_l) {
3,614,897✔
466
    E_out *= 2.0 * E_in / E_l - 1.0;
344,476✔
467
  } else {
468
    E_out += E_in - E_l;
3,270,421✔
469
  }
470

471
  // Sample outgoing cosine bin
472
  int n_mu = distribution_[l].mu.shape()[1];
3,614,897✔
473
  std::size_t k = prn(seed) * n_mu;
3,614,897✔
474

475
  // Rather than use the sampled discrete mu directly, it is smeared over
476
  // a bin of width 0.5*min(mu[k] - mu[k-1], mu[k+1] - mu[k]) centered on the
477
  // discrete mu value itself.
478
  const auto& mu_l = distribution_[l].mu;
3,614,897✔
479
  f = (r1 - c_j) / (c_j1 - c_j);
3,614,897✔
480

481
  // Interpolate kth mu value between distributions at energies j and j+1
482
  mu = mu_l(j, k) + f * (mu_l(j + 1, k) - mu_l(j, k));
3,614,897✔
483

484
  // Inteprolate (k-1)th mu value between distributions at energies j and j+1.
485
  // When k==0, pick a value that will smear the cosine out to a minimum of -1.
486
  double mu_left =
487
    (k == 0)
488
      ? mu_left = -1.0 - (mu + 1.0)
3,614,897✔
489
      : mu_left = mu_l(j, k - 1) + f * (mu_l(j + 1, k - 1) - mu_l(j, k - 1));
3,159,464✔
490

491
  // Inteprolate (k+1)th mu value between distributions at energies j and j+1.
492
  // When k is the last discrete value, pick a value that will smear the cosine
493
  // out to a maximum of 1.
494
  double mu_right =
495
    (k == n_mu - 1)
3,614,897✔
496
      ? mu_right = 1.0 + (1.0 - mu)
3,614,897✔
497
      : mu_right = mu_l(j, k + 1) + f * (mu_l(j + 1, k + 1) - mu_l(j, k + 1));
3,160,762✔
498

499
  // Smear cosine
500
  mu += std::min(mu - mu_left, mu_right - mu) * (prn(seed) - 0.5);
3,614,897✔
501
}
3,614,897✔
502

NEW
503
double IncoherentInelasticAE::get_pdf(
×
504
  double E_in, double& E_out, double& mu, uint64_t* seed) const
505
{
506
  // Get index and interpolation factor for inelastic grid
507
  int i;
508
  double f;
NEW
509
  get_energy_index(energy_, E_in, i, f);
×
510

511
  // Pick closer energy based on interpolation factor
NEW
512
  int l = f > 0.5 ? i + 1 : i;
×
513

514
  // Determine outgoing energy bin
515
  // (First reset n_energy_out to the right value)
NEW
516
  auto n = distribution_[l].n_e_out;
×
NEW
517
  double r1 = prn(seed);
×
NEW
518
  double c_j = distribution_[l].e_out_cdf[0];
×
519
  double c_j1;
520
  std::size_t j;
NEW
521
  for (j = 0; j < n - 1; ++j) {
×
NEW
522
    c_j1 = distribution_[l].e_out_cdf[j + 1];
×
NEW
523
    if (r1 < c_j1)
×
NEW
524
      break;
×
NEW
525
    c_j = c_j1;
×
526
  }
527

528
  // check to make sure j is <= n_energy_out - 2
NEW
529
  j = std::min(j, n - 2);
×
530

531
  // Get the data to interpolate between
NEW
532
  double E_l_j = distribution_[l].e_out[j];
×
NEW
533
  double p_l_j = distribution_[l].e_out_pdf[j];
×
534

535
  // Next part assumes linear-linear interpolation in standard
NEW
536
  double E_l_j1 = distribution_[l].e_out[j + 1];
×
NEW
537
  double p_l_j1 = distribution_[l].e_out_pdf[j + 1];
×
538

539
  // Find secondary energy (variable E)
NEW
540
  double frac = (p_l_j1 - p_l_j) / (E_l_j1 - E_l_j);
×
NEW
541
  if (frac == 0.0) {
×
NEW
542
    E_out = E_l_j + (r1 - c_j) / p_l_j;
×
543
  } else {
NEW
544
    E_out = E_l_j +
×
NEW
545
            (std::sqrt(std::max(0.0, p_l_j * p_l_j + 2.0 * frac * (r1 - c_j))) -
×
NEW
546
              p_l_j) /
×
547
              frac;
548
  }
549

550
  // Adjustment of outgoing energy
NEW
551
  double E_l = energy_[l];
×
NEW
552
  if (E_out < 0.5 * E_l) {
×
NEW
553
    E_out *= 2.0 * E_in / E_l - 1.0;
×
554
  } else {
NEW
555
    E_out += E_in - E_l;
×
556
  }
557

558
  // Sample outgoing cosine bin
NEW
559
  int n_mu = distribution_[l].mu.shape()[1];
×
NEW
560
  const auto& mu_l = distribution_[l].mu;
×
NEW
561
  f = (r1 - c_j) / (c_j1 - c_j);
×
562

NEW
563
  std::vector<double> mu_vector;
×
564

NEW
565
  for (int k = 0; k < n_mu; ++k) {
×
NEW
566
    double mu_k = mu_l(j, k) + f * (mu_l(j + 1, k) - mu_l(j, k));
×
NEW
567
    mu_vector.push_back(mu_k);
×
568
  }
569

NEW
570
  return get_pdf_discrete(mu_vector, mu);
×
571
}
572

573
//==============================================================================
574
// MixedElasticAE implementation
575
//==============================================================================
576

577
MixedElasticAE::MixedElasticAE(
44✔
578
  hid_t group, const CoherentElasticXS& coh_xs, const Function1D& incoh_xs)
44✔
579
  : coherent_dist_(coh_xs), coherent_xs_(coh_xs), incoherent_xs_(incoh_xs)
44✔
580
{
581
  // Read incoherent elastic distribution
582
  hid_t incoherent_group = open_group(group, "incoherent");
44✔
583
  std::string temp;
44✔
584
  read_attribute(incoherent_group, "type", temp);
44✔
585
  if (temp == "incoherent_elastic") {
44✔
586
    incoherent_dist_ = make_unique<IncoherentElasticAE>(incoherent_group);
×
587
  } else if (temp == "incoherent_elastic_discrete") {
44✔
588
    auto xs = dynamic_cast<const Tabulated1D*>(&incoh_xs);
44✔
589
    incoherent_dist_ =
590
      make_unique<IncoherentElasticAEDiscrete>(incoherent_group, xs->x());
44✔
591
  }
592
  close_group(incoherent_group);
44✔
593
}
44✔
594

595
void MixedElasticAE::sample(
46,486✔
596
  double E_in, double& E_out, double& mu, uint64_t* seed) const
597
{
598
  // Evaluate coherent and incoherent elastic cross sections
599
  double xs_coh = coherent_xs_(E_in);
46,486✔
600
  double xs_incoh = incoherent_xs_(E_in);
46,486✔
601

602
  if (prn(seed) * (xs_coh + xs_incoh) < xs_coh) {
46,486✔
603
    coherent_dist_.sample(E_in, E_out, mu, seed);
45,144✔
604
  } else {
605
    incoherent_dist_->sample(E_in, E_out, mu, seed);
1,342✔
606
  }
607
}
46,486✔
608

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