• 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

89.32
/src/secondary_kalbach.cpp
1
#include "openmc/secondary_kalbach.h"
2

3
#include <algorithm> // for copy, move
4
#include <cmath>     // for log, sqrt, sinh
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/hdf5_interface.h"
12
#include "openmc/math_functions.h"
13
#include "openmc/random_dist.h"
14
#include "openmc/random_lcg.h"
15
#include "openmc/search.h"
16
#include "openmc/vector.h"
17

18
namespace openmc {
19

20
//==============================================================================
21
//! KalbachMann implementation
22
//==============================================================================
23

24
KalbachMann::KalbachMann(hid_t group)
5,392✔
25
{
26
  // Open incoming energy dataset
27
  hid_t dset = open_dataset(group, "energy");
5,392✔
28

29
  // Get interpolation parameters
30
  xt::xarray<int> temp;
5,392✔
31
  read_attribute(dset, "interpolation", temp);
5,392✔
32

33
  auto temp_b = xt::view(temp, 0); // view of breakpoints
5,392✔
34
  auto temp_i = xt::view(temp, 1); // view of interpolation parameters
5,392✔
35

36
  std::copy(temp_b.begin(), temp_b.end(), std::back_inserter(breakpoints_));
5,392✔
37
  for (const auto i : temp_i)
10,784✔
38
    interpolation_.push_back(int2interp(i));
5,392✔
39
  n_region_ = breakpoints_.size();
5,392✔
40

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

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

55
  xt::xarray<double> eout;
5,392✔
56
  read_dataset(dset, eout);
5,392✔
57
  close_dataset(dset);
5,392✔
58

59
  for (int i = 0; i < n_energy; ++i) {
109,790✔
60
    // Determine number of outgoing energies
61
    int j = offsets[i];
104,398✔
62
    int n;
63
    if (i < n_energy - 1) {
104,398✔
64
      n = offsets[i + 1] - j;
99,006✔
65
    } else {
66
      n = eout.shape()[1] - j;
5,392✔
67
    }
68

69
    // Assign interpolation scheme and number of discrete lines
70
    KMTable d;
104,398✔
71
    d.interpolation = int2interp(interp[i]);
104,398✔
72
    d.n_discrete = n_discrete[i];
104,398✔
73

74
    // Copy data
75
    d.e_out = xt::view(eout, 0, xt::range(j, j + n));
104,398✔
76
    d.p = xt::view(eout, 1, xt::range(j, j + n));
104,398✔
77
    d.c = xt::view(eout, 2, xt::range(j, j + n));
104,398✔
78
    d.r = xt::view(eout, 3, xt::range(j, j + n));
104,398✔
79
    d.a = xt::view(eout, 4, xt::range(j, j + n));
104,398✔
80

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

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

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

114
    distribution_.push_back(std::move(d));
104,398✔
115
  } // incoming energies
104,398✔
116
}
5,392✔
117

118
void KalbachMann::sample_params(
458,447✔
119
  double E_in, double& E_out, double& km_a, double& km_r, uint64_t* seed) const
120
{
121
  // Find energy bin and calculate interpolation factor
122
  int i;
123
  double r;
124
  get_energy_index(energy_, E_in, i, r);
458,447✔
125

126
  // Sample between the ith and [i+1]th bin
127
  int l = r > prn(seed) ? i + 1 : i;
458,447✔
128

129
  // Interpolation for energy E1 and EK
130
  int n_energy_out = distribution_[i].e_out.size();
458,447✔
131
  int n_discrete = distribution_[i].n_discrete;
458,447✔
132
  double E_i_1 = distribution_[i].e_out[n_discrete];
458,447✔
133
  double E_i_K = distribution_[i].e_out[n_energy_out - 1];
458,447✔
134

135
  n_energy_out = distribution_[i + 1].e_out.size();
458,447✔
136
  n_discrete = distribution_[i + 1].n_discrete;
458,447✔
137
  double E_i1_1 = distribution_[i + 1].e_out[n_discrete];
458,447✔
138
  double E_i1_K = distribution_[i + 1].e_out[n_energy_out - 1];
458,447✔
139

140
  double E_1 = E_i_1 + r * (E_i1_1 - E_i_1);
458,447✔
141
  double E_K = E_i_K + r * (E_i1_K - E_i_K);
458,447✔
142

143
  // Determine outgoing energy bin
144
  n_energy_out = distribution_[l].e_out.size();
458,447✔
145
  n_discrete = distribution_[l].n_discrete;
458,447✔
146
  double r1 = prn(seed);
458,447✔
147
  double c_k = distribution_[l].c[0];
458,447✔
148
  int k = 0;
458,447✔
149
  int end = n_energy_out - 2;
458,447✔
150

151
  // Discrete portion
152
  for (int j = 0; j < n_discrete; ++j) {
458,447!
153
    k = j;
×
154
    c_k = distribution_[l].c[k];
×
155
    if (r1 < c_k) {
×
156
      end = j;
×
157
      break;
×
158
    }
159
  }
160

161
  // Continuous portion
162
  double c_k1;
163
  for (int j = n_discrete; j < end; ++j) {
11,603,902✔
164
    k = j;
11,586,216✔
165
    c_k1 = distribution_[l].c[k + 1];
11,586,216✔
166
    if (r1 < c_k1)
11,586,216✔
167
      break;
440,761✔
168
    k = j + 1;
11,145,455✔
169
    c_k = c_k1;
11,145,455✔
170
  }
171

172
  double E_l_k = distribution_[l].e_out[k];
458,447✔
173
  double p_l_k = distribution_[l].p[k];
458,447✔
174
  if (distribution_[l].interpolation == Interpolation::histogram) {
458,447✔
175
    // Histogram interpolation
176
    if (p_l_k > 0.0 && k >= n_discrete) {
456,651!
177
      E_out = E_l_k + (r1 - c_k) / p_l_k;
456,651✔
178
    } else {
179
      E_out = E_l_k;
×
180
    }
181

182
    // Determine Kalbach-Mann parameters
183
    km_r = distribution_[l].r[k];
456,651✔
184
    km_a = distribution_[l].a[k];
456,651✔
185

186
  } else {
187
    // Linear-linear interpolation
188
    double E_l_k1 = distribution_[l].e_out[k + 1];
1,796✔
189
    double p_l_k1 = distribution_[l].p[k + 1];
1,796✔
190

191
    double frac = (p_l_k1 - p_l_k) / (E_l_k1 - E_l_k);
1,796✔
192
    if (frac == 0.0) {
1,796!
193
      E_out = E_l_k + (r1 - c_k) / p_l_k;
×
194
    } else {
195
      E_out =
1,796✔
196
        E_l_k +
1,796✔
197
        (std::sqrt(std::max(0.0, p_l_k * p_l_k + 2.0 * frac * (r1 - c_k))) -
1,796✔
198
          p_l_k) /
1,796✔
199
          frac;
200
    }
201

202
    // Determine Kalbach-Mann parameters
203
    km_r = distribution_[l].r[k] +
1,796✔
204
           (E_out - E_l_k) / (E_l_k1 - E_l_k) *
3,592✔
205
             (distribution_[l].r[k + 1] - distribution_[l].r[k]);
1,796✔
206
    km_a = distribution_[l].a[k] +
1,796✔
207
           (E_out - E_l_k) / (E_l_k1 - E_l_k) *
3,592✔
208
             (distribution_[l].a[k + 1] - distribution_[l].a[k]);
1,796✔
209
  }
210

211
  // Now interpolate between incident energy bins i and i + 1
212
  if (k >= n_discrete) {
458,447!
213
    if (l == i) {
458,447✔
214
      E_out = E_1 + (E_out - E_i_1) * (E_K - E_1) / (E_i_K - E_i_1);
228,695✔
215
    } else {
216
      E_out = E_1 + (E_out - E_i1_1) * (E_K - E_1) / (E_i1_K - E_i1_1);
229,752✔
217
    }
218
  }
219
}
458,447✔
220

221
void KalbachMann::sample(
458,447✔
222
  double E_in, double& E_out, double& mu, uint64_t* seed) const
223
{
224
  double km_r, km_a;
225
  sample_params(E_in, E_out, km_a, km_r, seed);
458,447✔
226

227
  // Sampled correlated angle from Kalbach-Mann parameters
228
  if (prn(seed) > km_r) {
458,447✔
229
    double T = uniform_distribution(-1., 1., seed) * std::sinh(km_a);
451,565✔
230
    mu = std::log(T + std::sqrt(T * T + 1.0)) / km_a;
451,565✔
231
  } else {
232
    double r1 = prn(seed);
6,882✔
233
    mu = std::log(r1 * std::exp(km_a) + (1.0 - r1) * std::exp(-km_a)) / km_a;
6,882✔
234
  }
235
}
458,447✔
NEW
236
double KalbachMann::sample_energy_and_pdf(
×
237
  double E_in, double mu, double& E_out, uint64_t* seed) const
238
{
239
  double km_r, km_a;
NEW
240
  sample_params(E_in, E_out, km_a, km_r, seed);
×
241

242
  // https://docs.openmc.org/en/latest/methods/neutron_physics.html#equation-KM-pdf-angle
NEW
243
  return km_a / (2 * std::sinh(km_a)) *
×
NEW
244
         (std::cosh(km_a * mu) + km_r * std::sinh(km_a * mu));
×
245
}
246

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