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

openmc-dev / openmc / 25458679820

06 May 2026 08:14PM UTC coverage: 81.294% (-0.09%) from 81.381%
25458679820

Pull #3935

github

web-flow
Merge b80303226 into e542b2f03
Pull Request #3935: Implement jacobian calculation and energy and angle transformation

17700 of 25603 branches covered (69.13%)

Branch coverage included in aggregate %.

0 of 90 new or added lines in 9 files covered. (0.0%)

3 existing lines in 3 files now uncovered.

58535 of 68174 relevant lines covered (85.86%)

47318852.11 hits per line

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

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

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

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

35
  std::copy(temp_b.begin(), temp_b.end(), std::back_inserter(breakpoints_));
70,779✔
36
  for (const auto i : temp_i)
141,558✔
37
    interpolation_.push_back(int2interp(i));
70,779✔
38
  n_region_ = breakpoints_.size();
70,779✔
39

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

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

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

58
  for (int i = 0; i < n_energy; ++i) {
1,442,225✔
59
    // Determine number of outgoing energies
60
    int j = offsets[i];
1,371,446✔
61
    int n;
1,371,446✔
62
    if (i < n_energy - 1) {
1,371,446✔
63
      n = offsets[i + 1] - j;
1,300,667✔
64
    } else {
65
      n = eout.shape(1) - j;
141,558!
66
    }
67

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

73
    // Copy data
74
    d.e_out = eout.slice(0, tensor::range(j, j + n));
1,371,446✔
75
    d.p = eout.slice(1, tensor::range(j, j + n));
1,371,446✔
76
    d.c = eout.slice(2, tensor::range(j, j + n));
1,371,446✔
77
    d.r = eout.slice(3, tensor::range(j, j + n));
1,371,446✔
78
    d.a = eout.slice(4, tensor::range(j, j + n));
1,371,446✔
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,371,446✔
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,371,446✔
114
  } // incoming energies
1,371,446✔
115
}
283,116✔
116

117
void KalbachMann::sample_params(
6,796,932✔
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;
6,796,932✔
122
  double r;
6,796,932✔
123
  get_energy_index(energy_, E_in, i, r);
6,796,932✔
124

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

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

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

139
  double E_1 = E_i_1 + r * (E_i1_1 - E_i_1);
6,796,932✔
140
  double E_K = E_i_K + r * (E_i1_K - E_i_K);
6,796,932✔
141

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

150
  // Discrete portion
151
  for (int j = 0; j < n_discrete; ++j) {
6,796,932!
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;
6,796,932✔
162
  for (int j = n_discrete; j < end; ++j) {
190,858,374✔
163
    k = j;
190,627,372✔
164
    c_k1 = distribution_[l].c[k + 1];
190,627,372✔
165
    if (r1 < c_k1)
190,627,372✔
166
      break;
167
    k = j + 1;
168
    c_k = c_k1;
169
  }
170

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

181
    // Determine Kalbach-Mann parameters
182
    km_r = distribution_[l].r[k];
6,777,176✔
183
    km_a = distribution_[l].a[k];
6,777,176✔
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) {
6,796,932!
212
    if (l == i) {
6,796,932✔
213
      E_out = E_1 + (E_out - E_i_1) * (E_K - E_1) / (E_i_K - E_i_1);
3,412,005✔
214
    } else {
215
      E_out = E_1 + (E_out - E_i1_1) * (E_K - E_1) / (E_i1_K - E_i1_1);
3,384,927✔
216
    }
217
  }
218
}
6,796,932✔
219

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

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

NEW
241
  double jac = 1.0;
×
NEW
242
  if (is_com)
×
NEW
243
    jac = get_jac_and_transform(E_in, mu, E_out, seed, awr);
×
244

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

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