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

openmc-dev / openmc / 17444981192

03 Sep 2025 08:14PM UTC coverage: 84.747%. First build
17444981192

Pull #3550

github

web-flow
Merge e2de1c407 into 591856472
Pull Request #3550: [Point Detector] Add distribution get_pdf functionality

0 of 336 new or added lines in 10 files covered. (0.0%)

52937 of 62465 relevant lines covered (84.75%)

37875629.96 hits per line

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

52.17
/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(
72,221,264✔
17
  const vector<double>& energies, double E, int& i, double& f)
18
{
19
  // Get index and interpolation factor for elastic grid
20
  i = 0;
72,221,264✔
21
  f = 0.0;
72,221,264✔
22
  if (E >= energies.front()) {
72,221,264✔
23
    i = lower_bound_index(energies.begin(), energies.end(), E);
72,221,253✔
24
    if (i + 1 < energies.size())
72,221,253✔
25
      f = (E - energies[i]) / (energies[i + 1] - energies[i]);
72,219,911✔
26
  }
27
}
72,221,264✔
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 mu, double& E_out, 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 mu, double& E_out, 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 mu, double& E_out, 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,101✔
252
  hid_t group, const vector<double>& energy)
1,101✔
253
  : energy_ {energy}
1,101✔
254
{
255
  read_dataset(group, "energy_out", energy_out_);
1,101✔
256
  read_dataset(group, "mu_out", mu_out_);
1,101✔
257
  read_dataset(group, "skewed", skewed_);
1,101✔
258
}
1,101✔
259

260
void IncoherentInelasticAEDiscrete::sample(
68,605,025✔
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);
68,605,025✔
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];
68,605,025✔
277
  if (!skewed_) {
68,605,025✔
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);
68,605,025✔
283
    if (r > 1.0) {
68,605,025✔
284
      // equally likely N-4 middle bins
285
      j = r + 1;
67,479,271✔
286
    } else if (r > 0.6) {
1,125,754✔
287
      // second to last bin has relative probability of 0.4
288
      j = n - 2;
449,424✔
289
    } else if (r > 0.5) {
676,330✔
290
      // last bin has relative probability of 0.1
291
      j = n - 1;
112,620✔
292
    } else if (r > 0.1) {
563,710✔
293
      // second bin has relative probability of 0.4
294
      j = 1;
452,476✔
295
    } else {
296
      // first bin has relative probability of 0.1
297
      j = 0;
111,234✔
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);
68,605,025✔
303
  double E_i1j = energy_out_(i + 1, j);
68,605,025✔
304

305
  // Outgoing energy
306
  E_out = (1 - f) * E_ij + f * E_i1j;
68,605,025✔
307

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

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

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

NEW
320
double IncoherentInelasticAEDiscrete::get_pdf(
×
321
  double E_in, double mu, double& E_out, uint64_t* seed) 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
  }
352

353
  // Determine outgoing energy corresponding to E_in[i] and E_in[i+1]
NEW
354
  double E_ij = energy_out_(i, j);
×
NEW
355
  double E_i1j = energy_out_(i + 1, j);
×
356

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

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

NEW
369
  return get_pdf_discrete(mu_vector, mu);
×
370
}
371

372
//==============================================================================
373
// IncoherentInelasticAE implementation
374
//==============================================================================
375

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

381
  // Copy incident energies
382
  energy_ = dist.energy();
44✔
383

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

NEW
561
  std::vector<double> mu_vector;
×
562

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

NEW
568
  return get_pdf_discrete(mu_vector, mu);
×
569
}
570

571
//==============================================================================
572
// MixedElasticAE implementation
573
//==============================================================================
574

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

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

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

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