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

openmc-dev / openmc / 21489819490

29 Jan 2026 06:21PM UTC coverage: 80.077% (-1.9%) from 81.953%
21489819490

Pull #3757

github

web-flow
Merge d08626053 into f7a734189
Pull Request #3757: Testing point detectors

16004 of 22621 branches covered (70.75%)

Branch coverage included in aggregate %.

94 of 518 new or added lines in 26 files covered. (18.15%)

1021 existing lines in 52 files now uncovered.

53779 of 64524 relevant lines covered (83.35%)

8016833.26 hits per line

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

91.48
/src/secondary_correlated.cpp
1
#include "openmc/secondary_correlated.h"
2

3
#include <algorithm> // for copy
4
#include <cmath>
5
#include <cstddef>  // for size_t
6
#include <iterator> // for back_inserter
7

8
#include "xtensor/xarray.hpp"
9
#include "xtensor/xview.hpp"
10

11
#include "openmc/endf.h"
12
#include "openmc/hdf5_interface.h"
13
#include "openmc/math_functions.h"
14
#include "openmc/random_lcg.h"
15
#include "openmc/search.h"
16

17
namespace openmc {
18

19
//==============================================================================
20
//! CorrelatedAngleEnergy implementation
21
//==============================================================================
22

23
CorrelatedAngleEnergy::CorrelatedAngleEnergy(hid_t group)
3,712✔
24
{
25
  // Open incoming energy dataset
26
  hid_t dset = open_dataset(group, "energy");
3,712✔
27

28
  // Get interpolation parameters
29
  xt::xarray<int> temp;
3,712✔
30
  read_attribute(dset, "interpolation", temp);
3,712✔
31

32
  auto temp_b = xt::view(temp, 0); // view of breakpoints
3,712✔
33
  auto temp_i = xt::view(temp, 1); // view of interpolation parameters
3,712✔
34

35
  std::copy(temp_b.begin(), temp_b.end(), std::back_inserter(breakpoints_));
3,712✔
36
  for (const auto i : temp_i)
7,424✔
37
    interpolation_.push_back(int2interp(i));
3,712✔
38
  n_region_ = breakpoints_.size();
3,712✔
39

40
  // Get incoming energies
41
  read_dataset(dset, energy_);
3,712✔
42
  std::size_t n_energy = energy_.size();
3,712✔
43
  close_dataset(dset);
3,712✔
44

45
  // Get outgoing energy distribution data
46
  dset = open_dataset(group, "energy_out");
3,712✔
47
  vector<int> offsets;
3,712✔
48
  vector<int> interp;
3,712✔
49
  vector<int> n_discrete;
3,712✔
50
  read_attribute(dset, "offsets", offsets);
3,712✔
51
  read_attribute(dset, "interpolation", interp);
3,712✔
52
  read_attribute(dset, "n_discrete_lines", n_discrete);
3,712✔
53

54
  xt::xarray<double> eout;
3,712✔
55
  read_dataset(dset, eout);
3,712✔
56
  close_dataset(dset);
3,712✔
57

58
  // Read angle distributions
59
  xt::xarray<double> mu;
3,712✔
60
  read_dataset(group, "mu", mu);
3,712✔
61

62
  for (int i = 0; i < n_energy; ++i) {
76,509✔
63
    // Determine number of outgoing energies
64
    int j = offsets[i];
72,797✔
65
    int n;
66
    if (i < n_energy - 1) {
72,797✔
67
      n = offsets[i + 1] - j;
69,085✔
68
    } else {
69
      n = eout.shape()[1] - j;
3,712✔
70
    }
71

72
    // Assign interpolation scheme and number of discrete lines
73
    CorrTable d;
72,797✔
74
    d.interpolation = int2interp(interp[i]);
72,797✔
75
    d.n_discrete = n_discrete[i];
72,797✔
76

77
    // Copy data
78
    d.e_out = xt::view(eout, 0, xt::range(j, j + n));
72,797✔
79
    d.p = xt::view(eout, 1, xt::range(j, j + n));
72,797✔
80
    d.c = xt::view(eout, 2, xt::range(j, j + n));
72,797✔
81

82
    // To get answers that match ACE data, for now we still use the tabulated
83
    // CDF values that were passed through to the HDF5 library. At a later
84
    // time, we can remove the CDF values from the HDF5 library and
85
    // reconstruct them using the PDF
86
    if (false) {
87
      // Calculate cumulative distribution function -- discrete portion
88
      for (int k = 0; k < d.n_discrete; ++k) {
89
        if (k == 0) {
90
          d.c[k] = d.p[k];
91
        } else {
92
          d.c[k] = d.c[k - 1] + d.p[k];
93
        }
94
      }
95

96
      // Continuous portion
97
      for (int k = d.n_discrete; k < n; ++k) {
98
        if (k == d.n_discrete) {
99
          d.c[k] = d.c[k - 1] + d.p[k];
100
        } else {
101
          if (d.interpolation == Interpolation::histogram) {
102
            d.c[k] = d.c[k - 1] + d.p[k - 1] * (d.e_out[k] - d.e_out[k - 1]);
103
          } else if (d.interpolation == Interpolation::lin_lin) {
104
            d.c[k] = d.c[k - 1] + 0.5 * (d.p[k - 1] + d.p[k]) *
105
                                    (d.e_out[k] - d.e_out[k - 1]);
106
          }
107
        }
108
      }
109

110
      // Normalize density and distribution functions
111
      d.p /= d.c[n - 1];
112
      d.c /= d.c[n - 1];
113
    }
114

115
    for (j = 0; j < n; ++j) {
1,731,689✔
116
      // Get interpolation scheme
117
      int interp_mu = std::lround(eout(3, offsets[i] + j));
1,658,892✔
118

119
      // Determine offset and size of distribution
120
      int offset_mu = std::lround(eout(4, offsets[i] + j));
1,658,892✔
121
      int m;
122
      if (offsets[i] + j + 1 < eout.shape()[1]) {
1,658,892✔
123
        m = std::lround(eout(4, offsets[i] + j + 1)) - offset_mu;
1,655,180✔
124
      } else {
125
        m = mu.shape()[1] - offset_mu;
3,712✔
126
      }
127

128
      // For incoherent inelastic thermal scattering, the angle distributions
129
      // may be given as discrete mu values. In this case, interpolation values
130
      // of zero appear in the HDF5 file. Here we change it to a 1 so that
131
      // int2interp doesn't fail.
132
      if (interp_mu == 0)
1,658,892✔
133
        interp_mu = 1;
48✔
134

135
      auto interp = int2interp(interp_mu);
1,658,892✔
136
      auto xs = xt::view(mu, 0, xt::range(offset_mu, offset_mu + m));
1,658,892✔
137
      auto ps = xt::view(mu, 1, xt::range(offset_mu, offset_mu + m));
1,658,892✔
138
      auto cs = xt::view(mu, 2, xt::range(offset_mu, offset_mu + m));
1,658,892✔
139

140
      vector<double> x {xs.begin(), xs.end()};
1,658,892✔
141
      vector<double> p {ps.begin(), ps.end()};
1,658,892✔
142
      vector<double> c {cs.begin(), cs.end()};
1,658,892✔
143

144
      // To get answers that match ACE data, for now we still use the tabulated
145
      // CDF values that were passed through to the HDF5 library. At a later
146
      // time, we can remove the CDF values from the HDF5 library and
147
      // reconstruct them using the PDF
148
      Tabular* mudist = new Tabular {x.data(), p.data(), m, interp, c.data()};
1,658,892✔
149

150
      d.angle.emplace_back(mudist);
1,658,892✔
151
    } // outgoing energies
1,658,892✔
152

153
    distribution_.push_back(std::move(d));
72,797✔
154
  } // incoming energies
72,797✔
155
}
3,712✔
156
Distribution& CorrelatedAngleEnergy::sample_dist(
28,243✔
157
  double E_in, double& E_out, uint64_t* seed) const
158
{
159
  // Find energy bin and calculate interpolation factor
160
  int i;
161
  double r;
162
  get_energy_index(energy_, E_in, i, r);
28,243✔
163

164
  // Sample between the ith and [i+1]th bin
165
  int l = r > prn(seed) ? i + 1 : i;
28,243✔
166

167
  // Interpolation for energy E1 and EK
168
  int n_energy_out = distribution_[i].e_out.size();
28,243✔
169
  int n_discrete = distribution_[i].n_discrete;
28,243✔
170
  double E_i_1 = distribution_[i].e_out[n_discrete];
28,243✔
171
  double E_i_K = distribution_[i].e_out[n_energy_out - 1];
28,243✔
172

173
  n_energy_out = distribution_[i + 1].e_out.size();
28,243✔
174
  n_discrete = distribution_[i + 1].n_discrete;
28,243✔
175
  double E_i1_1 = distribution_[i + 1].e_out[n_discrete];
28,243✔
176
  double E_i1_K = distribution_[i + 1].e_out[n_energy_out - 1];
28,243✔
177

178
  double E_1 = E_i_1 + r * (E_i1_1 - E_i_1);
28,243✔
179
  double E_K = E_i_K + r * (E_i1_K - E_i_K);
28,243✔
180

181
  // Determine outgoing energy bin
182
  n_energy_out = distribution_[l].e_out.size();
28,243✔
183
  n_discrete = distribution_[l].n_discrete;
28,243✔
184
  double r1 = prn(seed);
28,243✔
185
  double c_k = distribution_[l].c[0];
28,243✔
186
  int k = 0;
28,243✔
187
  int end = n_energy_out - 2;
28,243✔
188

189
  // Discrete portion
190
  for (int j = 0; j < n_discrete; ++j) {
28,243!
191
    k = j;
×
192
    c_k = distribution_[l].c[k];
×
193
    if (r1 < c_k) {
×
194
      end = j;
×
195
      break;
×
196
    }
197
  }
198

199
  // Continuous portion
200
  double c_k1;
201
  for (int j = n_discrete; j < end; ++j) {
192,660✔
202
    k = j;
192,561✔
203
    c_k1 = distribution_[l].c[k + 1];
192,561✔
204
    if (r1 < c_k1)
192,561✔
205
      break;
28,144✔
206
    k = j + 1;
164,417✔
207
    c_k = c_k1;
164,417✔
208
  }
209

210
  double E_l_k = distribution_[l].e_out[k];
28,243✔
211
  double p_l_k = distribution_[l].p[k];
28,243✔
212
  if (distribution_[l].interpolation == Interpolation::histogram) {
28,243✔
213
    // Histogram interpolation
214
    if (p_l_k > 0.0 && k >= n_discrete) {
2,117!
215
      E_out = E_l_k + (r1 - c_k) / p_l_k;
2,117✔
216
    } else {
217
      E_out = E_l_k;
×
218
    }
219

220
  } else if (distribution_[l].interpolation == Interpolation::lin_lin) {
26,126!
221
    // Linear-linear interpolation
222
    double E_l_k1 = distribution_[l].e_out[k + 1];
26,126✔
223
    double p_l_k1 = distribution_[l].p[k + 1];
26,126✔
224

225
    double frac = (p_l_k1 - p_l_k) / (E_l_k1 - E_l_k);
26,126✔
226
    if (frac == 0.0) {
26,126!
227
      E_out = E_l_k + (r1 - c_k) / p_l_k;
×
228
    } else {
229
      E_out =
26,126✔
230
        E_l_k +
26,126✔
231
        (std::sqrt(std::max(0.0, p_l_k * p_l_k + 2.0 * frac * (r1 - c_k))) -
26,126✔
232
          p_l_k) /
26,126✔
233
          frac;
234
    }
235
  }
236

237
  // Now interpolate between incident energy bins i and i + 1
238
  if (k >= n_discrete) {
28,243!
239
    if (l == i) {
28,243✔
240
      E_out = E_1 + (E_out - E_i_1) * (E_K - E_1) / (E_i_K - E_i_1);
8,445✔
241
    } else {
242
      E_out = E_1 + (E_out - E_i1_1) * (E_K - E_1) / (E_i1_K - E_i1_1);
19,798✔
243
    }
244
  }
245

246
  // Find correlated angular distribution for closest outgoing energy bin
247
  if (r1 - c_k < c_k1 - r1 ||
42,334✔
248
      distribution_[l].interpolation == Interpolation::histogram) {
14,091✔
249
    return *distribution_[l].angle[k];
15,260✔
250
  } else {
251
    return *distribution_[l].angle[k + 1];
12,983✔
252
  }
253
}
254

255
void CorrelatedAngleEnergy::sample(
28,243✔
256
  double E_in, double& E_out, double& mu, uint64_t* seed) const
257
{
258
  mu = sample_dist(E_in, E_out, seed).sample(seed).first;
28,243✔
259
}
28,243✔
260

NEW
261
double CorrelatedAngleEnergy::sample_energy_and_pdf(
×
262
  double E_in, double mu, double& E_out, uint64_t* seed) const
263
{
NEW
264
  return sample_dist(E_in, E_out, seed).evaluate(mu);
×
265
}
266

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