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

openmc-dev / openmc / 22593927413

02 Mar 2026 08:16PM UTC coverage: 81.339% (-0.2%) from 81.508%
22593927413

Pull #3550

github

web-flow
Merge 1f5e24e59 into 823b4c96c
Pull Request #3550: [Point Detector] Add distribution get_pdf functionality

17498 of 25284 branches covered (69.21%)

Branch coverage included in aggregate %.

64 of 180 new or added lines in 12 files covered. (35.56%)

20 existing lines in 2 files now uncovered.

57713 of 67182 relevant lines covered (85.91%)

44906521.62 hits per line

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

88.48
/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 "openmc/tensor.h"
9

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

17
namespace openmc {
18

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

23
KalbachMann::KalbachMann(hid_t group)
68,447✔
24
{
25
  // Open incoming energy dataset
26
  hid_t dset = open_dataset(group, "energy");
68,447✔
27

28
  // Get interpolation parameters
29
  tensor::Tensor<int> temp;
68,447✔
30
  read_attribute(dset, "interpolation", temp);
68,447✔
31

32
  tensor::View<int> temp_b = temp.slice(0); // breakpoints
68,447✔
33
  tensor::View<int> temp_i = temp.slice(1); // interpolation parameters
68,447✔
34

35
  std::copy(temp_b.begin(), temp_b.end(), std::back_inserter(breakpoints_));
68,447✔
36
  for (const auto i : temp_i)
136,894✔
37
    interpolation_.push_back(int2interp(i));
68,447✔
38
  n_region_ = breakpoints_.size();
68,447✔
39

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

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

54
  tensor::Tensor<double> eout;
68,447✔
55
  read_dataset(dset, eout);
68,447✔
56
  close_dataset(dset);
68,447✔
57

58
  for (int i = 0; i < n_energy; ++i) {
1,394,136✔
59
    // Determine number of outgoing energies
60
    int j = offsets[i];
1,325,689✔
61
    int n;
1,325,689✔
62
    if (i < n_energy - 1) {
1,325,689✔
63
      n = offsets[i + 1] - j;
1,257,242✔
64
    } else {
65
      n = eout.shape(1) - j;
136,894!
66
    }
67

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

73
    // Copy data
74
    d.e_out = eout.slice(0, tensor::range(j, j + n));
1,325,689✔
75
    d.p = eout.slice(1, tensor::range(j, j + n));
1,325,689✔
76
    d.c = eout.slice(2, tensor::range(j, j + n));
1,325,689✔
77
    d.r = eout.slice(3, tensor::range(j, j + n));
1,325,689✔
78
    d.a = eout.slice(4, tensor::range(j, j + n));
1,325,689✔
79

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

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

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

113
    distribution_.push_back(std::move(d));
1,325,689✔
114
  } // incoming energies
1,325,689✔
115
}
273,788✔
116

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

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

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

134
  n_energy_out = distribution_[i + 1].e_out.size();
5,262,347✔
135
  n_discrete = distribution_[i + 1].n_discrete;
5,262,347✔
136
  double E_i1_1 = distribution_[i + 1].e_out[n_discrete];
5,262,347✔
137
  double E_i1_K = distribution_[i + 1].e_out[n_energy_out - 1];
5,262,347✔
138

139
  double E_1 = E_i_1 + r * (E_i1_1 - E_i_1);
5,262,347✔
140
  double E_K = E_i_K + r * (E_i1_K - E_i_K);
5,262,347✔
141

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

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

160
  // Continuous portion
161
  double c_k1;
5,262,347✔
162
  for (int j = n_discrete; j < end; ++j) {
134,889,073✔
163
    k = j;
134,688,670✔
164
    c_k1 = distribution_[l].c[k + 1];
134,688,670✔
165
    if (r1 < c_k1)
134,688,670✔
166
      break;
167
    k = j + 1;
168
    c_k = c_k1;
169
  }
170

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

181
    // Determine Kalbach-Mann parameters
182
    km_r = distribution_[l].r[k];
5,242,591✔
183
    km_a = distribution_[l].a[k];
5,242,591✔
184

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

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

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

210
  // Now interpolate between incident energy bins i and i + 1
211
  if (k >= n_discrete) {
5,262,347!
212
    if (l == i) {
5,262,347✔
213
      E_out = E_1 + (E_out - E_i_1) * (E_K - E_1) / (E_i_K - E_i_1);
2,622,423✔
214
    } else {
215
      E_out = E_1 + (E_out - E_i1_1) * (E_K - E_1) / (E_i1_K - E_i1_1);
2,639,924✔
216
    }
217
  }
218
}
5,262,347✔
219

220
void KalbachMann::sample(
5,262,347✔
221
  double E_in, double& E_out, double& mu, uint64_t* seed) const
222
{
223
  double km_r, km_a;
5,262,347✔
224
  sample_params(E_in, E_out, km_a, km_r, seed);
5,262,347✔
225

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

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

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