• 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

55.49
/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/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)
63,343✔
24
{
25
  // Open incoming energy dataset
26
  hid_t dset = open_dataset(group, "energy");
63,343✔
27

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

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

35
  std::copy(temp_b.begin(), temp_b.end(), std::back_inserter(breakpoints_));
63,343✔
36
  for (const auto i : temp_i)
126,686✔
37
    interpolation_.push_back(int2interp(i));
63,343✔
38
  n_region_ = breakpoints_.size();
63,343✔
39

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

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

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

58
  for (int i = 0; i < n_energy; ++i) {
1,291,531✔
59
    // Determine number of outgoing energies
60
    int j = offsets[i];
1,228,188✔
61
    int n;
62
    if (i < n_energy - 1) {
1,228,188✔
63
      n = offsets[i + 1] - j;
1,164,845✔
64
    } else {
65
      n = eout.shape()[1] - j;
63,343✔
66
    }
67

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

73
    // Copy data
74
    d.e_out = xt::view(eout, 0, xt::range(j, j + n));
1,228,188✔
75
    d.p = xt::view(eout, 1, xt::range(j, j + n));
1,228,188✔
76
    d.c = xt::view(eout, 2, xt::range(j, j + n));
1,228,188✔
77
    d.r = xt::view(eout, 3, xt::range(j, j + n));
1,228,188✔
78
    d.a = xt::view(eout, 4, xt::range(j, j + n));
1,228,188✔
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) {
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,228,188✔
114
  } // incoming energies
1,228,188✔
115
}
63,343✔
116

117
void KalbachMann::sample(
3,239,745✔
118
  double E_in, double& E_out, double& mu, uint64_t* seed) const
119
{
120
  // Find energy bin and calculate interpolation factor -- if the energy is
121
  // outside the range of the tabulated energies, choose the first or last bins
122
  auto n_energy_in = energy_.size();
3,239,745✔
123
  int i;
124
  double r;
125
  if (E_in < energy_[0]) {
3,239,745✔
126
    i = 0;
×
127
    r = 0.0;
×
128
  } else if (E_in > energy_[n_energy_in - 1]) {
3,239,745✔
129
    i = n_energy_in - 2;
×
130
    r = 1.0;
×
131
  } else {
132
    i = lower_bound_index(energy_.begin(), energy_.end(), E_in);
3,239,745✔
133
    r = (E_in - energy_[i]) / (energy_[i + 1] - energy_[i]);
3,239,745✔
134
  }
135

136
  // Sample between the ith and [i+1]th bin
137
  int l = r > prn(seed) ? i + 1 : i;
3,239,745✔
138

139
  // Interpolation for energy E1 and EK
140
  int n_energy_out = distribution_[i].e_out.size();
3,239,745✔
141
  int n_discrete = distribution_[i].n_discrete;
3,239,745✔
142
  double E_i_1 = distribution_[i].e_out[n_discrete];
3,239,745✔
143
  double E_i_K = distribution_[i].e_out[n_energy_out - 1];
3,239,745✔
144

145
  n_energy_out = distribution_[i + 1].e_out.size();
3,239,745✔
146
  n_discrete = distribution_[i + 1].n_discrete;
3,239,745✔
147
  double E_i1_1 = distribution_[i + 1].e_out[n_discrete];
3,239,745✔
148
  double E_i1_K = distribution_[i + 1].e_out[n_energy_out - 1];
3,239,745✔
149

150
  double E_1 = E_i_1 + r * (E_i1_1 - E_i_1);
3,239,745✔
151
  double E_K = E_i_K + r * (E_i1_K - E_i_K);
3,239,745✔
152

153
  // Determine outgoing energy bin
154
  n_energy_out = distribution_[l].e_out.size();
3,239,745✔
155
  n_discrete = distribution_[l].n_discrete;
3,239,745✔
156
  double r1 = prn(seed);
3,239,745✔
157
  double c_k = distribution_[l].c[0];
3,239,745✔
158
  int k = 0;
3,239,745✔
159
  int end = n_energy_out - 2;
3,239,745✔
160

161
  // Discrete portion
162
  for (int j = 0; j < n_discrete; ++j) {
3,239,745✔
163
    k = j;
×
164
    c_k = distribution_[l].c[k];
×
165
    if (r1 < c_k) {
×
166
      end = j;
×
167
      break;
×
168
    }
169
  }
170

171
  // Continuous portion
172
  double c_k1;
173
  for (int j = n_discrete; j < end; ++j) {
89,162,654✔
174
    k = j;
89,053,956✔
175
    c_k1 = distribution_[l].c[k + 1];
89,053,956✔
176
    if (r1 < c_k1)
89,053,956✔
177
      break;
3,131,047✔
178
    k = j + 1;
85,922,909✔
179
    c_k = c_k1;
85,922,909✔
180
  }
181

182
  double E_l_k = distribution_[l].e_out[k];
3,239,745✔
183
  double p_l_k = distribution_[l].p[k];
3,239,745✔
184
  double km_r, km_a;
185
  if (distribution_[l].interpolation == Interpolation::histogram) {
3,239,745✔
186
    // Histogram interpolation
187
    if (p_l_k > 0.0 && k >= n_discrete) {
3,219,417✔
188
      E_out = E_l_k + (r1 - c_k) / p_l_k;
3,219,417✔
189
    } else {
190
      E_out = E_l_k;
×
191
    }
192

193
    // Determine Kalbach-Mann parameters
194
    km_r = distribution_[l].r[k];
3,219,417✔
195
    km_a = distribution_[l].a[k];
3,219,417✔
196

197
  } else {
198
    // Linear-linear interpolation
199
    double E_l_k1 = distribution_[l].e_out[k + 1];
20,328✔
200
    double p_l_k1 = distribution_[l].p[k + 1];
20,328✔
201

202
    double frac = (p_l_k1 - p_l_k) / (E_l_k1 - E_l_k);
20,328✔
203
    if (frac == 0.0) {
20,328✔
204
      E_out = E_l_k + (r1 - c_k) / p_l_k;
×
205
    } else {
206
      E_out =
20,328✔
207
        E_l_k +
20,328✔
208
        (std::sqrt(std::max(0.0, p_l_k * p_l_k + 2.0 * frac * (r1 - c_k))) -
20,328✔
209
          p_l_k) /
20,328✔
210
          frac;
211
    }
212

213
    // Determine Kalbach-Mann parameters
214
    km_r = distribution_[l].r[k] +
20,328✔
215
           (E_out - E_l_k) / (E_l_k1 - E_l_k) *
40,656✔
216
             (distribution_[l].r[k + 1] - distribution_[l].r[k]);
20,328✔
217
    km_a = distribution_[l].a[k] +
20,328✔
218
           (E_out - E_l_k) / (E_l_k1 - E_l_k) *
40,656✔
219
             (distribution_[l].a[k + 1] - distribution_[l].a[k]);
20,328✔
220
  }
221

222
  // Now interpolate between incident energy bins i and i + 1
223
  if (k >= n_discrete) {
3,239,745✔
224
    if (l == i) {
3,239,745✔
225
      E_out = E_1 + (E_out - E_i_1) * (E_K - E_1) / (E_i_K - E_i_1);
1,618,810✔
226
    } else {
227
      E_out = E_1 + (E_out - E_i1_1) * (E_K - E_1) / (E_i1_K - E_i1_1);
1,620,935✔
228
    }
229
  }
230

231
  // Sampled correlated angle from Kalbach-Mann parameters
232
  if (prn(seed) > km_r) {
3,239,745✔
233
    double T = uniform_distribution(-1., 1., seed) * std::sinh(km_a);
3,170,588✔
234
    mu = std::log(T + std::sqrt(T * T + 1.0)) / km_a;
3,170,588✔
235
  } else {
236
    double r1 = prn(seed);
69,157✔
237
    mu = std::log(r1 * std::exp(km_a) + (1.0 - r1) * std::exp(-km_a)) / km_a;
69,157✔
238
  }
239
}
3,239,745✔
NEW
240
double KalbachMann::get_pdf(
×
241
  double E_in, double mu, double& E_out, uint64_t* seed) const
242
{
243
  // Find energy bin and calculate interpolation factor -- if the energy is
244
  // outside the range of the tabulated energies, choose the first or last bins
245

246
  // double E_out;
NEW
247
  auto n_energy_in = energy_.size();
×
248
  int i;
249
  double r;
NEW
250
  if (E_in < energy_[0]) {
×
NEW
251
    i = 0;
×
NEW
252
    r = 0.0;
×
NEW
253
  } else if (E_in > energy_[n_energy_in - 1]) {
×
NEW
254
    i = n_energy_in - 2;
×
NEW
255
    r = 1.0;
×
256
  } else {
NEW
257
    i = lower_bound_index(energy_.begin(), energy_.end(), E_in);
×
NEW
258
    r = (E_in - energy_[i]) / (energy_[i + 1] - energy_[i]);
×
259
  }
260

261
  // Sample between the ith and [i+1]th bin
NEW
262
  int l = r > prn(seed) ? i + 1 : i;
×
263

264
  // Interpolation for energy E1 and EK
NEW
265
  int n_energy_out = distribution_[i].e_out.size();
×
NEW
266
  int n_discrete = distribution_[i].n_discrete;
×
NEW
267
  double E_i_1 = distribution_[i].e_out[n_discrete];
×
NEW
268
  double E_i_K = distribution_[i].e_out[n_energy_out - 1];
×
269

NEW
270
  n_energy_out = distribution_[i + 1].e_out.size();
×
NEW
271
  n_discrete = distribution_[i + 1].n_discrete;
×
NEW
272
  double E_i1_1 = distribution_[i + 1].e_out[n_discrete];
×
NEW
273
  double E_i1_K = distribution_[i + 1].e_out[n_energy_out - 1];
×
274

NEW
275
  double E_1 = E_i_1 + r * (E_i1_1 - E_i_1);
×
NEW
276
  double E_K = E_i_K + r * (E_i1_K - E_i_K);
×
277

278
  // Determine outgoing energy bin
NEW
279
  n_energy_out = distribution_[l].e_out.size();
×
NEW
280
  n_discrete = distribution_[l].n_discrete;
×
NEW
281
  double r1 = prn(seed);
×
NEW
282
  double c_k = distribution_[l].c[0];
×
NEW
283
  int k = 0;
×
NEW
284
  int end = n_energy_out - 2;
×
285

286
  // Discrete portion
NEW
287
  for (int j = 0; j < n_discrete; ++j) {
×
NEW
288
    k = j;
×
NEW
289
    c_k = distribution_[l].c[k];
×
NEW
290
    if (r1 < c_k) {
×
NEW
291
      end = j;
×
NEW
292
      break;
×
293
    }
294
  }
295

296
  // Continuous portion
297
  double c_k1;
NEW
298
  for (int j = n_discrete; j < end; ++j) {
×
NEW
299
    k = j;
×
NEW
300
    c_k1 = distribution_[l].c[k + 1];
×
NEW
301
    if (r1 < c_k1)
×
NEW
302
      break;
×
NEW
303
    k = j + 1;
×
NEW
304
    c_k = c_k1;
×
305
  }
306

NEW
307
  double E_l_k = distribution_[l].e_out[k];
×
NEW
308
  double p_l_k = distribution_[l].p[k];
×
309
  double km_r, km_a;
NEW
310
  if (distribution_[l].interpolation == Interpolation::histogram) {
×
311
    // Histogram interpolation
NEW
312
    if (p_l_k > 0.0 && k >= n_discrete) {
×
NEW
313
      E_out = E_l_k + (r1 - c_k) / p_l_k;
×
314
    } else {
NEW
315
      E_out = E_l_k;
×
316
    }
317
    // Determine Kalbach-Mann parameters
NEW
318
    km_r = distribution_[l].r[k];
×
NEW
319
    km_a = distribution_[l].a[k];
×
320

321
  } else {
322
    // Linear-linear interpolation
NEW
323
    double E_l_k1 = distribution_[l].e_out[k + 1];
×
NEW
324
    double p_l_k1 = distribution_[l].p[k + 1];
×
325

NEW
326
    double frac = (p_l_k1 - p_l_k) / (E_l_k1 - E_l_k);
×
NEW
327
    if (frac == 0.0) {
×
NEW
328
      E_out = E_l_k + (r1 - c_k) / p_l_k;
×
329
    } else {
NEW
330
      E_out =
×
NEW
331
        E_l_k +
×
NEW
332
        (std::sqrt(std::max(0.0, p_l_k * p_l_k + 2.0 * frac * (r1 - c_k))) -
×
NEW
333
          p_l_k) /
×
334
          frac;
335
    }
336
    //  Linear-linear interpolation
337
    // Determine Kalbach-Mann parameters
NEW
338
    km_r = distribution_[l].r[k] +
×
NEW
339
           (E_out - E_l_k) / (E_l_k1 - E_l_k) *
×
NEW
340
             (distribution_[l].r[k + 1] - distribution_[l].r[k]);
×
NEW
341
    km_a = distribution_[l].a[k] +
×
NEW
342
           (E_out - E_l_k) / (E_l_k1 - E_l_k) *
×
NEW
343
             (distribution_[l].a[k + 1] - distribution_[l].a[k]);
×
344
  }
345

346
  // Now interpolate between incident energy bins i and i + 1
NEW
347
  if (k >= n_discrete) {
×
NEW
348
    if (l == i) {
×
NEW
349
      E_out = E_1 + (E_out - E_i_1) * (E_K - E_1) / (E_i_K - E_i_1);
×
350
    } else {
NEW
351
      E_out = E_1 + (E_out - E_i1_1) * (E_K - E_1) / (E_i1_K - E_i1_1);
×
352
    }
353
  }
354

355
  // https://docs.openmc.org/en/latest/methods/neutron_physics.html#equation-KM-pdf-angle
NEW
356
  double pdf = km_a / (2 * std::sinh(km_a)) *
×
NEW
357
               (std::cosh(km_a * mu) + km_r * std::sinh(km_a * mu));
×
358

NEW
359
  return pdf;
×
360
}
361

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