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

openmc-dev / openmc / 20197979645

13 Dec 2025 09:16PM UTC coverage: 82.118% (+0.008%) from 82.11%
20197979645

Pull #3675

github

web-flow
Merge 3fafce82c into bbfa18d72
Pull Request #3675: Extend level scattering to support incident photons

17013 of 23594 branches covered (72.11%)

Branch coverage included in aggregate %.

55 of 76 new or added lines in 4 files covered. (72.37%)

193 existing lines in 7 files now uncovered.

55125 of 64253 relevant lines covered (85.79%)

43453609.56 hits per line

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

82.45
/src/distribution_energy.cpp
1
#include "openmc/distribution_energy.h"
2

3
#include <algorithm> // for max, min, copy, move
4
#include <cmath>     // for sqrt, abs
5
#include <cstddef>   // for size_t
6
#include <iterator>  // for back_inserter
7

8
#include "xtensor/xview.hpp"
9

10
#include "openmc/constants.h"
11
#include "openmc/endf.h"
12
#include "openmc/hdf5_interface.h"
13
#include "openmc/math_functions.h"
14
#include "openmc/particle.h"
15
#include "openmc/random_dist.h"
16
#include "openmc/random_lcg.h"
17
#include "openmc/search.h"
18

19
namespace openmc {
20

21
//==============================================================================
22
// DiscretePhoton implementation
23
//==============================================================================
24

25
DiscretePhoton::DiscretePhoton(hid_t group)
2,277,079✔
26
{
27
  read_attribute(group, "primary_flag", primary_flag_);
2,277,079✔
28
  read_attribute(group, "energy", energy_);
2,277,079✔
29
  read_attribute(group, "atomic_weight_ratio", A_);
2,277,079✔
30
}
2,277,079✔
31

32
double DiscretePhoton::sample(double E, uint64_t* seed) const
201,586✔
33
{
34
  if (primary_flag_ == 2) {
201,586✔
35
    return energy_ + A_ / (A_ + 1) * E;
29,392✔
36
  } else {
37
    return energy_;
172,194✔
38
  }
39
}
40

41
//==============================================================================
42
// LevelInelastic implementation
43
//==============================================================================
44

45
LevelInelastic::LevelInelastic(hid_t group)
507,839✔
46
{
47
  // for backwards compatibility:
48
  if (attribute_exists(group, "mass_ratio")) {
507,839!
49
    read_attribute(group, "threshold", b_);
507,839✔
50
    read_attribute(group, "mass_ratio", a_);
507,839✔
51
    c_ = 0.0;
507,839✔
52
  } else {
53
    double A, Q;
NEW
54
    std::string temp;
×
NEW
55
    read_attribute(group, "mass", A);
×
NEW
56
    read_attribute(group, "q_value", Q);
×
NEW
57
    read_attribute(group, "particle", temp);
×
NEW
58
    auto particle = str_to_particle_type(temp);
×
NEW
59
    if (particle == ParticleType::neutron) {
×
NEW
60
      a_ = (A / (A + 1.0)) * (A / (A + 1.0));
×
NEW
61
      b_ = (A + 1.0) / A * std::abs(Q);
×
NEW
62
      c_ = 0.0;
×
NEW
63
    } else if (particle == ParticleType::photon) {
×
NEW
64
      a_ = (A - 1.0) / A;
×
NEW
65
      b_ = std::abs(Q);
×
NEW
66
      c_ = 1.0 / (2.0 * MASS_NEUTRON_EV * (A - 1.0));
×
67
    } else {
NEW
68
      fatal_error("Unrecognized particle: " + temp);
×
69
    }
NEW
70
  }
×
71
}
507,839✔
72

73
double LevelInelastic::sample(double E, uint64_t* seed) const
11,668,885✔
74
{
75
  return a_ * (E - b_ - c_ * (E * E));
11,668,885✔
76
}
77

78
//==============================================================================
79
// ContinuousTabular implementation
80
//==============================================================================
81

82
ContinuousTabular::ContinuousTabular(hid_t group)
299,644✔
83
{
84
  // Open incoming energy dataset
85
  hid_t dset = open_dataset(group, "energy");
299,644✔
86

87
  // Get interpolation parameters
88
  xt::xarray<int> temp;
299,644✔
89
  read_attribute(dset, "interpolation", temp);
299,644✔
90

91
  auto temp_b = xt::view(temp, 0); // view of breakpoints
299,644✔
92
  auto temp_i = xt::view(temp, 1); // view of interpolation parameters
299,644✔
93

94
  std::copy(temp_b.begin(), temp_b.end(), std::back_inserter(breakpoints_));
299,644✔
95
  for (const auto i : temp_i)
599,534✔
96
    interpolation_.push_back(int2interp(i));
299,890✔
97
  n_region_ = breakpoints_.size();
299,644✔
98

99
  // Get incoming energies
100
  read_dataset(dset, energy_);
299,644✔
101
  std::size_t n_energy = energy_.size();
299,644✔
102
  close_dataset(dset);
299,644✔
103

104
  // Get outgoing energy distribution data
105
  dset = open_dataset(group, "distribution");
299,644✔
106
  vector<int> offsets;
299,644✔
107
  vector<int> interp;
299,644✔
108
  vector<int> n_discrete;
299,644✔
109
  read_attribute(dset, "offsets", offsets);
299,644✔
110
  read_attribute(dset, "interpolation", interp);
299,644✔
111
  read_attribute(dset, "n_discrete_lines", n_discrete);
299,644✔
112

113
  xt::xarray<double> eout;
299,644✔
114
  read_dataset(dset, eout);
299,644✔
115
  close_dataset(dset);
299,644✔
116

117
  for (int i = 0; i < n_energy; ++i) {
4,484,721✔
118
    // Determine number of outgoing energies
119
    int j = offsets[i];
4,185,077✔
120
    int n;
121
    if (i < n_energy - 1) {
4,185,077✔
122
      n = offsets[i + 1] - j;
3,885,433✔
123
    } else {
124
      n = eout.shape()[1] - j;
299,644✔
125
    }
126

127
    // Assign interpolation scheme and number of discrete lines
128
    CTTable d;
4,185,077✔
129
    d.interpolation = int2interp(interp[i]);
4,185,077✔
130
    d.n_discrete = n_discrete[i];
4,185,077✔
131

132
    // Copy data
133
    d.e_out = xt::view(eout, 0, xt::range(j, j + n));
4,185,077✔
134
    d.p = xt::view(eout, 1, xt::range(j, j + n));
4,185,077✔
135

136
    // To get answers that match ACE data, for now we still use the tabulated
137
    // CDF values that were passed through to the HDF5 library. At a later
138
    // time, we can remove the CDF values from the HDF5 library and
139
    // reconstruct them using the PDF
140
    if (true) {
141
      d.c = xt::view(eout, 2, xt::range(j, j + n));
4,185,077✔
142
    } else {
143
      // Calculate cumulative distribution function -- discrete portion
144
      for (int k = 0; k < d.n_discrete; ++k) {
145
        if (k == 0) {
146
          d.c[k] = d.p[k];
147
        } else {
148
          d.c[k] = d.c[k - 1] + d.p[k];
149
        }
150
      }
151

152
      // Continuous portion
153
      for (int k = d.n_discrete; k < n; ++k) {
154
        if (k == d.n_discrete) {
155
          d.c[k] = d.c[k - 1] + d.p[k];
156
        } else {
157
          if (d.interpolation == Interpolation::histogram) {
158
            d.c[k] = d.c[k - 1] + d.p[k - 1] * (d.e_out[k] - d.e_out[k - 1]);
159
          } else if (d.interpolation == Interpolation::lin_lin) {
160
            d.c[k] = d.c[k - 1] + 0.5 * (d.p[k - 1] + d.p[k]) *
161
                                    (d.e_out[k] - d.e_out[k - 1]);
162
          }
163
        }
164
      }
165

166
      // Normalize density and distribution functions
167
      d.p /= d.c[n - 1];
168
      d.c /= d.c[n - 1];
169
    }
170

171
    distribution_.push_back(std::move(d));
4,185,077✔
172
  } // incoming energies
4,185,077✔
173
}
299,644✔
174

175
double ContinuousTabular::sample(double E, uint64_t* seed) const
28,188,787✔
176
{
177
  // Read number of interpolation regions and incoming energies
178
  bool histogram_interp;
179
  if (n_region_ == 1) {
28,188,787!
180
    histogram_interp = (interpolation_[0] == Interpolation::histogram);
28,188,787✔
181
  } else {
182
    histogram_interp = false;
×
183
  }
184

185
  // Find energy bin and calculate interpolation factor -- if the energy is
186
  // outside the range of the tabulated energies, choose the first or last bins
187
  auto n_energy_in = energy_.size();
28,188,787✔
188
  int i;
189
  double r;
190
  if (E < energy_[0]) {
28,188,787!
191
    i = 0;
×
192
    r = 0.0;
×
193
  } else if (E > energy_[n_energy_in - 1]) {
28,188,787!
194
    i = n_energy_in - 2;
×
195
    r = 1.0;
×
196
  } else {
197
    i = lower_bound_index(energy_.begin(), energy_.end(), E);
28,188,787✔
198
    r = (E - energy_[i]) / (energy_[i + 1] - energy_[i]);
28,188,787✔
199
  }
200

201
  // Sample between the ith and [i+1]th bin
202
  int l;
203
  if (histogram_interp) {
28,188,787✔
204
    l = i;
2,211✔
205
  } else {
206
    l = r > prn(seed) ? i + 1 : i;
28,186,576✔
207
  }
208

209
  // Determine outgoing energy bin
210
  int n_energy_out = distribution_[l].e_out.size();
28,188,787✔
211
  int n_discrete = distribution_[l].n_discrete;
28,188,787✔
212
  double r1 = prn(seed);
28,188,787✔
213
  double c_k = distribution_[l].c[0];
28,188,787✔
214
  int k = 0;
28,188,787✔
215
  int end = n_energy_out - 2;
28,188,787✔
216

217
  // Discrete portion
218
  for (int j = 0; j < n_discrete; ++j) {
28,769,708✔
219
    k = j;
727,397✔
220
    c_k = distribution_[l].c[k];
727,397✔
221
    if (r1 < c_k) {
727,397✔
222
      end = j;
146,476✔
223
      break;
146,476✔
224
    }
225
  }
226

227
  // Continuous portion
228
  double c_k1;
229
  for (int j = n_discrete; j < end; ++j) {
2,147,483,647✔
230
    k = j;
2,147,483,647✔
231
    c_k1 = distribution_[l].c[k + 1];
2,147,483,647✔
232
    if (r1 < c_k1)
2,147,483,647✔
233
      break;
28,041,981✔
234
    k = j + 1;
2,147,483,647✔
235
    c_k = c_k1;
2,147,483,647✔
236
  }
237

238
  double E_l_k = distribution_[l].e_out[k];
28,188,787✔
239
  double p_l_k = distribution_[l].p[k];
28,188,787✔
240
  double E_out = E_l_k;
28,188,787✔
241
  if (distribution_[l].interpolation == Interpolation::histogram) {
28,188,787✔
242
    // Histogram interpolation
243
    if (p_l_k > 0.0 && k >= n_discrete) {
722,785!
244
      E_out = E_l_k + (r1 - c_k) / p_l_k;
576,309✔
245
    }
246

247
  } else if (distribution_[l].interpolation == Interpolation::lin_lin) {
27,466,002!
248
    // Linear-linear interpolation
249
    double E_l_k1 = distribution_[l].e_out[k + 1];
27,466,002✔
250
    double p_l_k1 = distribution_[l].p[k + 1];
27,466,002✔
251

252
    if (E_l_k != E_l_k1) {
27,466,002!
253
      double frac = (p_l_k1 - p_l_k) / (E_l_k1 - E_l_k);
27,466,002✔
254
      if (frac == 0.0) {
27,466,002!
255
        E_out = E_l_k + (r1 - c_k) / p_l_k;
×
256
      } else {
257
        E_out =
27,466,002✔
258
          E_l_k +
259
          (std::sqrt(std::max(0.0, p_l_k * p_l_k + 2.0 * frac * (r1 - c_k))) -
27,466,002✔
260
            p_l_k) /
27,466,002✔
261
            frac;
262
      }
263
    }
264
  } else {
265
    throw std::runtime_error {"Unexpected interpolation for continuous energy "
×
266
                              "distribution."};
×
267
  }
268

269
  // Now interpolate between incident energy bins i and i + 1
270
  if (!histogram_interp && n_energy_out > 1 && k >= n_discrete) {
28,188,787✔
271
    // Interpolation for energy E1 and EK
272
    n_energy_out = distribution_[i].e_out.size();
28,040,100✔
273
    n_discrete = distribution_[i].n_discrete;
28,040,100✔
274
    const double E_i_1 = distribution_[i].e_out[n_discrete];
28,040,100✔
275
    const double E_i_K = distribution_[i].e_out[n_energy_out - 1];
28,040,100✔
276

277
    n_energy_out = distribution_[i + 1].e_out.size();
28,040,100✔
278
    n_discrete = distribution_[i + 1].n_discrete;
28,040,100✔
279
    const double E_i1_1 = distribution_[i + 1].e_out[n_discrete];
28,040,100✔
280
    const double E_i1_K = distribution_[i + 1].e_out[n_energy_out - 1];
28,040,100✔
281

282
    const double E_1 = E_i_1 + r * (E_i1_1 - E_i_1);
28,040,100✔
283
    const double E_K = E_i_K + r * (E_i1_K - E_i_K);
28,040,100✔
284

285
    if (l == i) {
28,040,100✔
286
      return E_1 + (E_out - E_i_1) * (E_K - E_1) / (E_i_K - E_i_1);
18,854,803✔
287
    } else {
288
      return E_1 + (E_out - E_i1_1) * (E_K - E_1) / (E_i1_K - E_i1_1);
9,185,297✔
289
    }
290
  } else {
291
    return E_out;
148,687✔
292
  }
293
}
294

295
//==============================================================================
296
// MaxwellEnergy implementation
297
//==============================================================================
298

299
MaxwellEnergy::MaxwellEnergy(hid_t group)
2,612✔
300
{
301
  read_attribute(group, "u", u_);
2,612✔
302
  hid_t dset = open_dataset(group, "theta");
2,612✔
303
  theta_ = Tabulated1D {dset};
2,612✔
304
  close_dataset(dset);
2,612✔
305
}
2,612✔
306

307
double MaxwellEnergy::sample(double E, uint64_t* seed) const
143,473✔
308
{
309
  // Get temperature corresponding to incoming energy
310
  double theta = theta_(E);
143,473✔
311

312
  while (true) {
313
    // Sample maxwell fission spectrum
314
    double E_out = maxwell_spectrum(theta, seed);
143,473✔
315

316
    // Accept energy based on restriction energy
317
    if (E_out <= E - u_)
143,473!
318
      return E_out;
143,473✔
319
  }
×
320
}
321

322
//==============================================================================
323
// Evaporation implementation
324
//==============================================================================
325

326
Evaporation::Evaporation(hid_t group)
4,719✔
327
{
328
  read_attribute(group, "u", u_);
4,719✔
329
  hid_t dset = open_dataset(group, "theta");
4,719✔
330
  theta_ = Tabulated1D {dset};
4,719✔
331
  close_dataset(dset);
4,719✔
332
}
4,719✔
333

334
double Evaporation::sample(double E, uint64_t* seed) const
27,280✔
335
{
336
  // Get temperature corresponding to incoming energy
337
  double theta = theta_(E);
27,280✔
338

339
  double y = (E - u_) / theta;
27,280✔
340
  double v = 1.0 - std::exp(-y);
27,280✔
341

342
  // Sample outgoing energy based on evaporation spectrum probability
343
  // density function
344
  double x;
345
  while (true) {
346
    x = -std::log((1.0 - v * prn(seed)) * (1.0 - v * prn(seed)));
32,120✔
347
    if (x <= y)
32,120✔
348
      break;
27,280✔
349
  }
350

351
  return x * theta;
27,280✔
352
}
353

354
//==============================================================================
355
// WattEnergy implementation
356
//==============================================================================
357

358
WattEnergy::WattEnergy(hid_t group)
197✔
359
{
360
  // Read restriction energy
361
  read_attribute(group, "u", u_);
197✔
362

363
  // Read tabulated functions
364
  hid_t dset = open_dataset(group, "a");
197✔
365
  a_ = Tabulated1D {dset};
197✔
366
  close_dataset(dset);
197✔
367
  dset = open_dataset(group, "b");
197✔
368
  b_ = Tabulated1D {dset};
197✔
369
  close_dataset(dset);
197✔
370
}
197✔
371

372
double WattEnergy::sample(double E, uint64_t* seed) const
1,831,599✔
373
{
374
  // Determine Watt parameters at incident energy
375
  double a = a_(E);
1,831,599✔
376
  double b = b_(E);
1,831,599✔
377

378
  while (true) {
379
    // Sample energy-dependent Watt fission spectrum
380
    double E_out = watt_spectrum(a, b, seed);
1,831,599✔
381

382
    // Accept energy based on restriction energy
383
    if (E_out <= E - u_)
1,831,599!
384
      return E_out;
1,831,599✔
385
  }
×
386
}
387

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