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

openmc-dev / openmc / 12776996362

14 Jan 2025 09:49PM UTC coverage: 84.938% (+0.2%) from 84.729%
12776996362

Pull #3133

github

web-flow
Merge 0495246d9 into 549cc0973
Pull Request #3133: Kinetics parameters using Iterated Fission Probability

318 of 330 new or added lines in 10 files covered. (96.36%)

1658 existing lines in 66 files now uncovered.

50402 of 59340 relevant lines covered (84.94%)

33987813.96 hits per line

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

86.24
/src/distribution.cpp
1
#include "openmc/distribution.h"
2

3
#include <algorithm> // for copy
4
#include <array>
5
#include <cmath>     // for sqrt, floor, max
6
#include <iterator>  // for back_inserter
7
#include <numeric>   // for accumulate
8
#include <stdexcept> // for runtime_error
9
#include <string>    // for string, stod
10

11
#include <gsl/gsl-lite.hpp>
12

13
#include "openmc/error.h"
14
#include "openmc/math_functions.h"
15
#include "openmc/random_dist.h"
16
#include "openmc/random_lcg.h"
17
#include "openmc/xml_interface.h"
18

19
namespace openmc {
20

21
//==============================================================================
22
// DiscreteIndex implementation
23
//==============================================================================
24

25
DiscreteIndex::DiscreteIndex(pugi::xml_node node)
4,215✔
26
{
27
  auto params = get_node_array<double>(node, "parameters");
4,215✔
28
  std::size_t n = params.size() / 2;
4,215✔
29

30
  assign({params.data() + n, n});
4,215✔
31
}
4,215✔
32

33
DiscreteIndex::DiscreteIndex(gsl::span<const double> p)
45,874✔
34
{
35
  assign(p);
45,874✔
36
}
45,874✔
37

38
void DiscreteIndex::assign(gsl::span<const double> p)
57,183✔
39
{
40
  prob_.assign(p.begin(), p.end());
57,183✔
41

42
  this->init_alias();
57,183✔
43
}
57,183✔
44

45
void DiscreteIndex::init_alias()
57,183✔
46
{
47
  normalize();
57,183✔
48

49
  // The initialization and sampling method is based on Vose
50
  // (DOI: 10.1109/32.92917)
51
  // Vectors for large and small probabilities based on 1/n
52
  vector<size_t> large;
57,183✔
53
  vector<size_t> small;
57,183✔
54

55
  size_t n = prob_.size();
57,183✔
56

57
  // Set and allocate memory
58
  alias_.assign(n, 0);
57,183✔
59

60
  // Fill large and small vectors based on 1/n
61
  for (size_t i = 0; i < n; i++) {
223,869✔
62
    prob_[i] *= n;
166,686✔
63
    if (prob_[i] > 1.0) {
166,686✔
64
      large.push_back(i);
18,595✔
65
    } else {
66
      small.push_back(i);
148,091✔
67
    }
68
  }
69

70
  while (!large.empty() && !small.empty()) {
94,899✔
71
    int j = small.back();
37,716✔
72
    int k = large.back();
37,716✔
73

74
    // Remove last element of small
75
    small.pop_back();
37,716✔
76

77
    // Update probability and alias based on Vose's algorithm
78
    prob_[k] += prob_[j] - 1.0;
37,716✔
79
    alias_[j] = k;
37,716✔
80

81
    // Move large index to small vector, if it is no longer large
82
    if (prob_[k] < 1.0) {
37,716✔
83
      small.push_back(k);
18,042✔
84
      large.pop_back();
18,042✔
85
    }
86
  }
87
}
57,183✔
88

89
size_t DiscreteIndex::sample(uint64_t* seed) const
35,900,327✔
90
{
91
  // Alias sampling of discrete distribution
92
  size_t n = prob_.size();
35,900,327✔
93
  if (n > 1) {
35,900,327✔
94
    size_t u = prn(seed) * n;
13,570,698✔
95
    if (prn(seed) < prob_[u]) {
13,570,698✔
96
      return u;
7,737,332✔
97
    } else {
98
      return alias_[u];
5,833,366✔
99
    }
100
  } else {
101
    return 0;
22,329,629✔
102
  }
103
}
104

105
void DiscreteIndex::normalize()
57,183✔
106
{
107
  // Renormalize density function so that it sums to unity. Note that we save
108
  // the integral of the distribution so that if it is used as part of another
109
  // distribution (e.g., Mixture), we know its relative strength.
110
  integral_ = std::accumulate(prob_.begin(), prob_.end(), 0.0);
57,183✔
111
  for (auto& p_i : prob_) {
223,869✔
112
    p_i /= integral_;
166,686✔
113
  }
114
}
57,183✔
115

116
//==============================================================================
117
// Discrete implementation
118
//==============================================================================
119

120
Discrete::Discrete(pugi::xml_node node) : di_(node)
4,215✔
121
{
122
  auto params = get_node_array<double>(node, "parameters");
4,215✔
123

124
  std::size_t n = params.size() / 2;
4,215✔
125

126
  x_.assign(params.begin(), params.begin() + n);
4,215✔
127
}
4,215✔
128

129
Discrete::Discrete(const double* x, const double* p, size_t n) : di_({p, n})
45,874✔
130
{
131

132
  x_.assign(x, x + n);
45,874✔
133
}
45,874✔
134

135
double Discrete::sample(uint64_t* seed) const
34,338,977✔
136
{
137
  return x_[di_.sample(seed)];
34,338,977✔
138
}
139

140
//==============================================================================
141
// Uniform implementation
142
//==============================================================================
143

144
Uniform::Uniform(pugi::xml_node node)
259✔
145
{
146
  auto params = get_node_array<double>(node, "parameters");
259✔
147
  if (params.size() != 2) {
259✔
148
    fatal_error("Uniform distribution must have two "
×
149
                "parameters specified.");
150
  }
151

152
  a_ = params.at(0);
259✔
153
  b_ = params.at(1);
259✔
154
}
259✔
155

156
double Uniform::sample(uint64_t* seed) const
234,852✔
157
{
158
  return a_ + prn(seed) * (b_ - a_);
234,852✔
159
}
160

161
//==============================================================================
162
// PowerLaw implementation
163
//==============================================================================
164

165
PowerLaw::PowerLaw(pugi::xml_node node)
34✔
166
{
167
  auto params = get_node_array<double>(node, "parameters");
34✔
168
  if (params.size() != 3) {
34✔
169
    fatal_error("PowerLaw distribution must have three "
×
170
                "parameters specified.");
171
  }
172

173
  const double a = params.at(0);
34✔
174
  const double b = params.at(1);
34✔
175
  const double n = params.at(2);
34✔
176

177
  offset_ = std::pow(a, n + 1);
34✔
178
  span_ = std::pow(b, n + 1) - offset_;
34✔
179
  ninv_ = 1 / (n + 1);
34✔
180
}
34✔
181

182
double PowerLaw::sample(uint64_t* seed) const
2,424✔
183
{
184
  return std::pow(offset_ + prn(seed) * span_, ninv_);
2,424✔
185
}
186

187
//==============================================================================
188
// Maxwell implementation
189
//==============================================================================
190

191
Maxwell::Maxwell(pugi::xml_node node)
68✔
192
{
193
  theta_ = std::stod(get_node_value(node, "parameters"));
68✔
194
}
68✔
195

196
double Maxwell::sample(uint64_t* seed) const
4,236✔
197
{
198
  return maxwell_spectrum(theta_, seed);
4,236✔
199
}
200

201
//==============================================================================
202
// Watt implementation
203
//==============================================================================
204

205
Watt::Watt(pugi::xml_node node)
102✔
206
{
207
  auto params = get_node_array<double>(node, "parameters");
102✔
208
  if (params.size() != 2)
102✔
209
    openmc::fatal_error("Watt energy distribution must have two "
×
210
                        "parameters specified.");
211

212
  a_ = params.at(0);
102✔
213
  b_ = params.at(1);
102✔
214
}
102✔
215

216
double Watt::sample(uint64_t* seed) const
10,156,829✔
217
{
218
  return watt_spectrum(a_, b_, seed);
10,156,829✔
219
}
220

221
//==============================================================================
222
// Normal implementation
223
//==============================================================================
224
Normal::Normal(pugi::xml_node node)
×
225
{
226
  auto params = get_node_array<double>(node, "parameters");
×
227
  if (params.size() != 2) {
×
228
    openmc::fatal_error("Normal energy distribution must have two "
×
229
                        "parameters specified.");
230
  }
231

232
  mean_value_ = params.at(0);
×
233
  std_dev_ = params.at(1);
×
234
}
235

236
double Normal::sample(uint64_t* seed) const
×
237
{
238
  return normal_variate(mean_value_, std_dev_, seed);
×
239
}
240

241
//==============================================================================
242
// Tabular implementation
243
//==============================================================================
244

245
Tabular::Tabular(pugi::xml_node node)
170✔
246
{
247
  if (check_for_node(node, "interpolation")) {
170✔
248
    std::string temp = get_node_value(node, "interpolation");
170✔
249
    if (temp == "histogram") {
170✔
250
      interp_ = Interpolation::histogram;
102✔
251
    } else if (temp == "linear-linear") {
68✔
252
      interp_ = Interpolation::lin_lin;
68✔
253
    } else {
254
      openmc::fatal_error(
×
255
        "Unsupported interpolation type for distribution: " + temp);
×
256
    }
257
  } else {
170✔
258
    interp_ = Interpolation::histogram;
×
259
  }
260

261
  // Read and initialize tabular distribution
262
  auto params = get_node_array<double>(node, "parameters");
170✔
263
  std::size_t n = params.size() / 2;
170✔
264
  const double* x = params.data();
170✔
265
  const double* p = x + n;
170✔
266
  init(x, p, n);
170✔
267
}
170✔
268

269
Tabular::Tabular(const double* x, const double* p, int n, Interpolation interp,
48,289,627✔
270
  const double* c)
48,289,627✔
271
  : interp_ {interp}
48,289,627✔
272
{
273
  init(x, p, n, c);
48,289,627✔
274
}
48,289,627✔
275

276
void Tabular::init(
48,289,797✔
277
  const double* x, const double* p, std::size_t n, const double* c)
278
{
279
  // Copy x/p arrays into vectors
280
  std::copy(x, x + n, std::back_inserter(x_));
48,289,797✔
281
  std::copy(p, p + n, std::back_inserter(p_));
48,289,797✔
282

283
  // Check interpolation parameter
284
  if (interp_ != Interpolation::histogram &&
48,289,797✔
285
      interp_ != Interpolation::lin_lin) {
40,399,501✔
286
    openmc::fatal_error("Only histogram and linear-linear interpolation "
×
287
                        "for tabular distribution is supported.");
288
  }
289

290
  // Calculate cumulative distribution function
291
  if (c) {
48,289,797✔
292
    std::copy(c, c + n, std::back_inserter(c_));
48,289,627✔
293
  } else {
294
    c_.resize(n);
170✔
295
    c_[0] = 0.0;
170✔
296
    for (int i = 1; i < n; ++i) {
5,304✔
297
      if (interp_ == Interpolation::histogram) {
5,134✔
298
        c_[i] = c_[i - 1] + p_[i - 1] * (x_[i] - x_[i - 1]);
4,998✔
299
      } else if (interp_ == Interpolation::lin_lin) {
136✔
300
        c_[i] = c_[i - 1] + 0.5 * (p_[i - 1] + p_[i]) * (x_[i] - x_[i - 1]);
136✔
301
      }
302
    }
303
  }
304

305
  // Normalize density and distribution functions. Note that we save the
306
  // integral of the distribution so that if it is used as part of another
307
  // distribution (e.g., Mixture), we know its relative strength.
308
  integral_ = c_[n - 1];
48,289,797✔
309
  for (int i = 0; i < n; ++i) {
686,767,569✔
310
    p_[i] = p_[i] / integral_;
638,477,772✔
311
    c_[i] = c_[i] / integral_;
638,477,772✔
312
  }
313
}
48,289,797✔
314

315
double Tabular::sample(uint64_t* seed) const
649,837,810✔
316
{
317
  // Sample value of CDF
318
  double c = prn(seed);
649,837,810✔
319

320
  // Find first CDF bin which is above the sampled value
321
  double c_i = c_[0];
649,837,810✔
322
  int i;
323
  std::size_t n = c_.size();
649,837,810✔
324
  for (i = 0; i < n - 1; ++i) {
2,147,483,647✔
325
    if (c <= c_[i + 1])
2,147,483,647✔
326
      break;
649,837,810✔
327
    c_i = c_[i + 1];
1,728,778,686✔
328
  }
329

330
  // Determine bounding PDF values
331
  double x_i = x_[i];
649,837,810✔
332
  double p_i = p_[i];
649,837,810✔
333

334
  if (interp_ == Interpolation::histogram) {
649,837,810✔
335
    // Histogram interpolation
336
    if (p_i > 0.0) {
3,735,144✔
337
      return x_i + (c - c_i) / p_i;
3,735,144✔
338
    } else {
339
      return x_i;
×
340
    }
341
  } else {
342
    // Linear-linear interpolation
343
    double x_i1 = x_[i + 1];
646,102,666✔
344
    double p_i1 = p_[i + 1];
646,102,666✔
345

346
    double m = (p_i1 - p_i) / (x_i1 - x_i);
646,102,666✔
347
    if (m == 0.0) {
646,102,666✔
348
      return x_i + (c - c_i) / p_i;
330,203,231✔
349
    } else {
350
      return x_i +
351
             (std::sqrt(std::max(0.0, p_i * p_i + 2 * m * (c - c_i))) - p_i) /
315,899,435✔
352
               m;
315,899,435✔
353
    }
354
  }
355
}
356

357
//==============================================================================
358
// Equiprobable implementation
359
//==============================================================================
360

361
double Equiprobable::sample(uint64_t* seed) const
×
362
{
363
  std::size_t n = x_.size();
×
364

365
  double r = prn(seed);
×
366
  int i = std::floor((n - 1) * r);
×
367

368
  double xl = x_[i];
×
369
  double xr = x_[i + i];
×
370
  return xl + ((n - 1) * r - i) * (xr - xl);
×
371
}
372

373
//==============================================================================
374
// Mixture implementation
375
//==============================================================================
376

377
Mixture::Mixture(pugi::xml_node node)
51✔
378
{
379
  double cumsum = 0.0;
51✔
380
  for (pugi::xml_node pair : node.children("pair")) {
204✔
381
    // Check that required data exists
382
    if (!pair.attribute("probability"))
153✔
383
      fatal_error("Mixture pair element does not have probability.");
×
384
    if (!pair.child("dist"))
153✔
385
      fatal_error("Mixture pair element does not have a distribution.");
×
386

387
    // cummulative sum of probabilities
388
    double p = std::stod(pair.attribute("probability").value());
153✔
389

390
    // Save cummulative probability and distribution
391
    auto dist = distribution_from_xml(pair.child("dist"));
153✔
392
    cumsum += p * dist->integral();
153✔
393

394
    distribution_.push_back(std::make_pair(cumsum, std::move(dist)));
153✔
395
  }
153✔
396

397
  // Save integral of distribution
398
  integral_ = cumsum;
51✔
399

400
  // Normalize cummulative probabilities to 1
401
  for (auto& pair : distribution_) {
204✔
402
    pair.first /= cumsum;
153✔
403
  }
404
}
51✔
405

406
double Mixture::sample(uint64_t* seed) const
3,888✔
407
{
408
  // Sample value of CDF
409
  const double p = prn(seed);
3,888✔
410

411
  // find matching distribution
412
  const auto it = std::lower_bound(distribution_.cbegin(), distribution_.cend(),
3,888✔
413
    p, [](const DistPair& pair, double p) { return pair.first < p; });
7,776✔
414

415
  // This should not happen. Catch it
416
  Ensures(it != distribution_.cend());
3,888✔
417

418
  // Sample the chosen distribution
419
  return it->second->sample(seed);
7,776✔
420
}
421

422
//==============================================================================
423
// Helper function
424
//==============================================================================
425

426
UPtrDist distribution_from_xml(pugi::xml_node node)
4,887✔
427
{
428
  if (!check_for_node(node, "type"))
4,887✔
UNCOV
429
    openmc::fatal_error("Distribution type must be specified.");
×
430

431
  // Determine type of distribution
432
  std::string type = get_node_value(node, "type", true, true);
4,887✔
433

434
  // Allocate extension of Distribution
435
  UPtrDist dist;
4,887✔
436
  if (type == "uniform") {
4,887✔
437
    dist = UPtrDist {new Uniform(node)};
259✔
438
  } else if (type == "powerlaw") {
4,628✔
439
    dist = UPtrDist {new PowerLaw(node)};
34✔
440
  } else if (type == "maxwell") {
4,594✔
441
    dist = UPtrDist {new Maxwell(node)};
68✔
442
  } else if (type == "watt") {
4,526✔
443
    dist = UPtrDist {new Watt(node)};
102✔
444
  } else if (type == "normal") {
4,424✔
UNCOV
445
    dist = UPtrDist {new Normal(node)};
×
446
  } else if (type == "discrete") {
4,424✔
447
    dist = UPtrDist {new Discrete(node)};
4,203✔
448
  } else if (type == "tabular") {
221✔
449
    dist = UPtrDist {new Tabular(node)};
170✔
450
  } else if (type == "mixture") {
51✔
451
    dist = UPtrDist {new Mixture(node)};
51✔
UNCOV
452
  } else if (type == "muir") {
×
UNCOV
453
    openmc::fatal_error(
×
454
      "'muir' distributions are now specified using the openmc.stats.muir() "
455
      "function in Python. Please regenerate your XML files.");
456
  } else {
UNCOV
457
    openmc::fatal_error("Invalid distribution type: " + type);
×
458
  }
459
  return dist;
9,774✔
460
}
4,887✔
461

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

© 2025 Coveralls, Inc