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

openmc-dev / openmc / 20925680857

12 Jan 2026 03:51PM UTC coverage: 82.058% (-0.1%) from 82.198%
20925680857

push

github

web-flow
Source biasing capabilities (#3460)

Co-authored-by: Paul Romano <paul.k.romano@gmail.com>

17208 of 23885 branches covered (72.05%)

Branch coverage included in aggregate %.

479 of 626 new or added lines in 12 files covered. (76.52%)

8 existing lines in 3 files now uncovered.

55640 of 64891 relevant lines covered (85.74%)

43208543.16 hits per line

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

75.12
/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 "openmc/constants.h"
12
#include "openmc/error.h"
13
#include "openmc/math_functions.h"
14
#include "openmc/random_dist.h"
15
#include "openmc/random_lcg.h"
16
#include "openmc/xml_interface.h"
17

18
namespace openmc {
19

20
//==============================================================================
21
// Helper function for computing importance weights from biased sampling
22
//==============================================================================
23

24
vector<double> compute_importance_weights(
11✔
25
  const vector<double>& p, const vector<double>& b)
26
{
27
  std::size_t n = p.size();
11✔
28

29
  // Normalize original probabilities
30
  double sum_p = std::accumulate(p.begin(), p.end(), 0.0);
11✔
31
  vector<double> p_norm(n);
11✔
32
  for (std::size_t i = 0; i < n; ++i) {
44✔
33
    p_norm[i] = p[i] / sum_p;
33✔
34
  }
35

36
  // Normalize bias probabilities
37
  double sum_b = std::accumulate(b.begin(), b.end(), 0.0);
11✔
38
  vector<double> b_norm(n);
11✔
39
  for (std::size_t i = 0; i < n; ++i) {
44✔
40
    b_norm[i] = b[i] / sum_b;
33✔
41
  }
42

43
  // Compute importance weights
44
  vector<double> weights(n);
11✔
45
  for (std::size_t i = 0; i < n; ++i) {
44✔
46
    weights[i] = (b_norm[i] == 0.0) ? INFTY : p_norm[i] / b_norm[i];
33!
47
  }
48
  return weights;
22✔
49
}
11✔
50

51
std::pair<double, double> Distribution::sample(uint64_t* seed) const
707,138,357✔
52
{
53
  if (bias_) {
707,138,357✔
54
    // Sample from the bias distribution and compute importance weight
55
    double val = bias_->sample_unbiased(seed);
715,248✔
56
    double wgt = this->evaluate(val) / bias_->evaluate(val);
715,248✔
57
    return {val, wgt};
715,248✔
58
  } else {
59
    // Unbiased sampling: return sampled value with weight 1.0
60
    double val = sample_unbiased(seed);
706,423,109✔
61
    return {val, 1.0};
706,423,109✔
62
  }
63
}
64

65
// PDF evaluation not supported for all distribution types
NEW
66
double Distribution::evaluate(double x) const
×
67
{
NEW
68
  throw std::runtime_error(
×
NEW
69
    "PDF evaluation not implemented for this distribution type.");
×
70
}
71

72
void Distribution::read_bias_from_xml(pugi::xml_node node)
8,316✔
73
{
74
  if (check_for_node(node, "bias")) {
8,316✔
75
    pugi::xml_node bias_node = node.child("bias");
77✔
76

77
    if (check_for_node(bias_node, "bias")) {
77!
NEW
78
      openmc::fatal_error(
×
79
        "Distribution has a bias distribution with its own bias distribution. "
80
        "Please ensure bias distributions do not have their own bias.");
81
    }
82

83
    UPtrDist bias = distribution_from_xml(bias_node);
77✔
84
    this->set_bias(std::move(bias));
77✔
85
  }
77✔
86
}
8,316✔
87

88
//==============================================================================
89
// DiscreteIndex implementation
90
//==============================================================================
91

UNCOV
92
DiscreteIndex::DiscreteIndex(pugi::xml_node node)
×
93
{
UNCOV
94
  auto params = get_node_array<double>(node, "parameters");
×
UNCOV
95
  std::size_t n = params.size() / 2;
×
96

UNCOV
97
  assign({params.data() + n, n});
×
UNCOV
98
}
×
99

100
DiscreteIndex::DiscreteIndex(span<const double> p)
47,163✔
101
{
102
  assign(p);
47,163✔
103
}
47,163✔
104

105
void DiscreteIndex::assign(span<const double> p)
82,375✔
106
{
107
  prob_.assign(p.begin(), p.end());
82,375✔
108
  this->init_alias();
82,375✔
109
}
82,375✔
110

111
void DiscreteIndex::init_alias()
82,375✔
112
{
113
  normalize();
82,375✔
114

115
  // The initialization and sampling method is based on Vose
116
  // (DOI: 10.1109/32.92917)
117
  // Vectors for large and small probabilities based on 1/n
118
  vector<size_t> large;
82,375✔
119
  vector<size_t> small;
82,375✔
120

121
  size_t n = prob_.size();
82,375✔
122

123
  // Set and allocate memory
124
  alias_.assign(n, 0);
82,375✔
125

126
  // Fill large and small vectors based on 1/n
127
  for (size_t i = 0; i < n; i++) {
1,607,024✔
128
    prob_[i] *= n;
1,524,649✔
129
    if (prob_[i] > 1.0) {
1,524,649✔
130
      large.push_back(i);
228,317✔
131
    } else {
132
      small.push_back(i);
1,296,332✔
133
    }
134
  }
135

136
  while (!large.empty() && !small.empty()) {
1,450,132✔
137
    int j = small.back();
1,367,757✔
138
    int k = large.back();
1,367,757✔
139

140
    // Remove last element of small
141
    small.pop_back();
1,367,757✔
142

143
    // Update probability and alias based on Vose's algorithm
144
    prob_[k] += prob_[j] - 1.0;
1,367,757✔
145
    alias_[j] = k;
1,367,757✔
146

147
    // Move large index to small vector, if it is no longer large
148
    if (prob_[k] < 1.0) {
1,367,757✔
149
      small.push_back(k);
220,058✔
150
      large.pop_back();
220,058✔
151
    }
152
  }
153
}
82,375✔
154

155
size_t DiscreteIndex::sample(uint64_t* seed) const
66,577,148✔
156
{
157
  // Alias sampling of discrete distribution
158
  size_t n = prob_.size();
66,577,148✔
159
  if (n > 1) {
66,577,148✔
160
    size_t u = prn(seed) * n;
16,190,432✔
161
    if (prn(seed) < prob_[u]) {
16,190,432✔
162
      return u;
9,676,277✔
163
    } else {
164
      return alias_[u];
6,514,155✔
165
    }
166
  } else {
167
    return 0;
50,386,716✔
168
  }
169
}
170

171
void DiscreteIndex::normalize()
82,375✔
172
{
173
  // Renormalize density function so that it sums to unity. Note that we save
174
  // the integral of the distribution so that if it is used as part of another
175
  // distribution (e.g., Mixture), we know its relative strength.
176
  integral_ = std::accumulate(prob_.begin(), prob_.end(), 0.0);
82,375✔
177
  for (auto& p_i : prob_) {
1,607,024✔
178
    p_i /= integral_;
1,524,649✔
179
  }
180
}
82,375✔
181

182
//==============================================================================
183
// Discrete implementation
184
//==============================================================================
185

186
Discrete::Discrete(pugi::xml_node node)
26,945✔
187
{
188
  auto params = get_node_array<double>(node, "parameters");
26,945✔
189
  std::size_t n = params.size() / 2;
26,945✔
190

191
  // First half is x values, second half is probabilities
192
  x_.assign(params.begin(), params.begin() + n);
26,945✔
193
  const double* p = params.data() + n;
26,945✔
194

195
  // Check for bias
196
  if (check_for_node(node, "bias")) {
26,945✔
197
    // Get bias probabilities
198
    auto bias_params = get_node_array<double>(node, "bias");
11✔
199
    if (bias_params.size() != n) {
11!
NEW
200
      openmc::fatal_error(
×
NEW
201
        "Size mismatch: Attempted to bias Discrete distribution with " +
×
NEW
202
        std::to_string(n) + " probability entries using a bias with " +
×
NEW
203
        std::to_string(bias_params.size()) +
×
204
        " entries. Please ensure distributions have the same size.");
205
    }
206

207
    // Compute importance weights
208
    vector<double> p_vec(p, p + n);
11✔
209
    weight_ = compute_importance_weights(p_vec, bias_params);
11✔
210

211
    // Initialize DiscreteIndex with bias probabilities for sampling
212
    di_.assign(bias_params);
11✔
213
  } else {
11✔
214
    // Unbiased case: weight_ stays empty
215
    di_.assign({p, n});
26,934✔
216
  }
217
}
26,945✔
218

219
Discrete::Discrete(const double* x, const double* p, size_t n) : di_({p, n})
47,163✔
220
{
221
  x_.assign(x, x + n);
47,163✔
222
}
47,163✔
223

224
std::pair<double, double> Discrete::sample(uint64_t* seed) const
61,574,199✔
225
{
226
  size_t idx = di_.sample(seed);
61,574,199✔
227
  double wgt = weight_.empty() ? 1.0 : weight_[idx];
61,574,199✔
228
  return {x_[idx], wgt};
61,574,199✔
229
}
230

NEW
231
double Discrete::sample_unbiased(uint64_t* seed) const
×
232
{
NEW
233
  size_t idx = di_.sample(seed);
×
NEW
234
  return x_[idx];
×
235
}
236

237
//==============================================================================
238
// Uniform implementation
239
//==============================================================================
240

241
Uniform::Uniform(pugi::xml_node node)
344✔
242
{
243
  auto params = get_node_array<double>(node, "parameters");
344✔
244
  if (params.size() != 2) {
344!
245
    fatal_error("Uniform distribution must have two "
×
246
                "parameters specified.");
247
  }
248

249
  a_ = params.at(0);
344✔
250
  b_ = params.at(1);
344✔
251

252
  read_bias_from_xml(node);
344✔
253
}
344✔
254

255
double Uniform::sample_unbiased(uint64_t* seed) const
780,627✔
256
{
257
  return a_ + prn(seed) * (b_ - a_);
780,627✔
258
}
259

260
double Uniform::evaluate(double x) const
385,248✔
261
{
262
  if (x <= a()) {
385,248!
NEW
263
    return 0.0;
×
264
  } else if (x >= b()) {
385,248!
NEW
265
    return 0.0;
×
266
  } else {
267
    return 1 / (b() - a());
385,248✔
268
  }
269
}
270

271
//==============================================================================
272
// PowerLaw implementation
273
//==============================================================================
274

275
PowerLaw::PowerLaw(pugi::xml_node node)
76✔
276
{
277
  auto params = get_node_array<double>(node, "parameters");
76✔
278
  if (params.size() != 3) {
76!
279
    fatal_error("PowerLaw distribution must have three "
×
280
                "parameters specified.");
281
  }
282

283
  const double a = params.at(0);
76✔
284
  const double b = params.at(1);
76✔
285
  const double n = params.at(2);
76✔
286

287
  offset_ = std::pow(a, n + 1);
76✔
288
  span_ = std::pow(b, n + 1) - offset_;
76✔
289
  ninv_ = 1 / (n + 1);
76✔
290

291
  read_bias_from_xml(node);
76✔
292
}
76✔
293

294
double PowerLaw::evaluate(double x) const
275,248✔
295
{
296
  if (x <= a()) {
275,248!
NEW
297
    return 0.0;
×
298
  } else if (x >= b()) {
275,248!
NEW
299
    return 0.0;
×
300
  } else {
301
    int pwr = n() + 1;
275,248✔
302
    double norm = pwr / span_;
275,248✔
303
    return norm * std::pow(std::fabs(x), n());
275,248✔
304
  }
305
}
306

307
double PowerLaw::sample_unbiased(uint64_t* seed) const
277,470✔
308
{
309
  return std::pow(offset_ + prn(seed) * span_, ninv_);
277,470✔
310
}
311

312
//==============================================================================
313
// Maxwell implementation
314
//==============================================================================
315

316
Maxwell::Maxwell(pugi::xml_node node)
97✔
317
{
318
  theta_ = std::stod(get_node_value(node, "parameters"));
97✔
319

320
  read_bias_from_xml(node);
97✔
321
}
97✔
322

323
double Maxwell::sample_unbiased(uint64_t* seed) const
223,872✔
324
{
325
  return maxwell_spectrum(theta_, seed);
223,872✔
326
}
327

328
double Maxwell::evaluate(double x) const
220,000✔
329
{
330
  double c = (2.0 / SQRT_PI) * std::pow(theta_, -1.5);
220,000✔
331
  return c * std::sqrt(x) * std::exp(-x / theta_);
220,000✔
332
}
333

334
//==============================================================================
335
// Watt implementation
336
//==============================================================================
337

338
Watt::Watt(pugi::xml_node node)
129✔
339
{
340
  auto params = get_node_array<double>(node, "parameters");
129✔
341
  if (params.size() != 2)
129!
342
    openmc::fatal_error("Watt energy distribution must have two "
×
343
                        "parameters specified.");
344

345
  a_ = params.at(0);
129✔
346
  b_ = params.at(1);
129✔
347

348
  read_bias_from_xml(node);
129✔
349
}
129✔
350

351
double Watt::sample_unbiased(uint64_t* seed) const
12,572,337✔
352
{
353
  return watt_spectrum(a_, b_, seed);
12,572,337✔
354
}
355

356
double Watt::evaluate(double x) const
220,000✔
357
{
358
  double c =
359
    2.0 / (std::sqrt(PI * b_) * std::pow(a_, 1.5) * std::exp(a_ * b_ / 4.0));
220,000✔
360
  return c * std::exp(-x / a_) * std::sinh(std::sqrt(b_ * x));
220,000✔
361
}
362

363
//==============================================================================
364
// Normal implementation
365
//==============================================================================
366
Normal::Normal(pugi::xml_node node)
33✔
367
{
368
  auto params = get_node_array<double>(node, "parameters");
33✔
369
  if (params.size() != 2) {
33!
370
    openmc::fatal_error("Normal energy distribution must have two "
×
371
                        "parameters specified.");
372
  }
373

374
  mean_value_ = params.at(0);
33✔
375
  std_dev_ = params.at(1);
33✔
376

377
  read_bias_from_xml(node);
33✔
378
}
33✔
379

380
double Normal::sample_unbiased(uint64_t* seed) const
220,000✔
381
{
382
  return normal_variate(mean_value_, std_dev_, seed);
220,000✔
383
}
384

385
double Normal::evaluate(double x) const
220,000✔
386
{
387
  return (1.0 / (std::sqrt(2.0 / PI) * std_dev_)) *
220,000✔
388
         std::exp(-(std::pow((x - mean_value_), 2.0)) /
220,000✔
389
                  (2.0 * std::pow(std_dev_, 2.0)));
220,000✔
390
}
391

392
//==============================================================================
393
// Tabular implementation
394
//==============================================================================
395

396
Tabular::Tabular(pugi::xml_node node)
7,637✔
397
{
398
  if (check_for_node(node, "interpolation")) {
7,637!
399
    std::string temp = get_node_value(node, "interpolation");
7,637✔
400
    if (temp == "histogram") {
7,637✔
401
      interp_ = Interpolation::histogram;
7,551✔
402
    } else if (temp == "linear-linear") {
86!
403
      interp_ = Interpolation::lin_lin;
86✔
404
    } else {
405
      openmc::fatal_error(
×
406
        "Unsupported interpolation type for distribution: " + temp);
×
407
    }
408
  } else {
7,637✔
409
    interp_ = Interpolation::histogram;
×
410
  }
411

412
  // Read and initialize tabular distribution. If number of parameters is odd,
413
  // add an extra zero for the 'p' array.
414
  auto params = get_node_array<double>(node, "parameters");
7,637✔
415
  if (params.size() % 2 != 0) {
7,637!
416
    params.push_back(0.0);
×
417
  }
418
  std::size_t n = params.size() / 2;
7,637✔
419
  const double* x = params.data();
7,637✔
420
  const double* p = x + n;
7,637✔
421
  init(x, p, n);
7,637✔
422

423
  read_bias_from_xml(node);
7,637✔
424
}
7,637✔
425

426
Tabular::Tabular(const double* x, const double* p, int n, Interpolation interp,
46,581,537✔
427
  const double* c)
46,581,537✔
428
  : interp_ {interp}
46,581,537✔
429
{
430
  init(x, p, n, c);
46,581,537✔
431
}
46,581,537✔
432

433
void Tabular::init(
46,589,174✔
434
  const double* x, const double* p, std::size_t n, const double* c)
435
{
436
  // Copy x/p arrays into vectors
437
  std::copy(x, x + n, std::back_inserter(x_));
46,589,174✔
438
  std::copy(p, p + n, std::back_inserter(p_));
46,589,174✔
439

440
  // Check interpolation parameter
441
  if (interp_ != Interpolation::histogram &&
46,589,174✔
442
      interp_ != Interpolation::lin_lin) {
38,834,635!
443
    openmc::fatal_error("Only histogram and linear-linear interpolation "
×
444
                        "for tabular distribution is supported.");
445
  }
446

447
  // Calculate cumulative distribution function
448
  if (c) {
46,589,174✔
449
    std::copy(c, c + n, std::back_inserter(c_));
46,581,537✔
450
  } else {
451
    c_.resize(n);
7,637✔
452
    c_[0] = 0.0;
7,637✔
453
    for (int i = 1; i < n; ++i) {
94,540✔
454
      if (interp_ == Interpolation::histogram) {
86,903✔
455
        c_[i] = c_[i - 1] + p_[i - 1] * (x_[i] - x_[i - 1]);
86,709✔
456
      } else if (interp_ == Interpolation::lin_lin) {
194!
457
        c_[i] = c_[i - 1] + 0.5 * (p_[i - 1] + p_[i]) * (x_[i] - x_[i - 1]);
194✔
458
      }
459
    }
460
  }
461

462
  // Normalize density and distribution functions. Note that we save the
463
  // integral of the distribution so that if it is used as part of another
464
  // distribution (e.g., Mixture), we know its relative strength.
465
  integral_ = c_[n - 1];
46,589,174✔
466
  for (int i = 0; i < n; ++i) {
672,128,441✔
467
    p_[i] = p_[i] / integral_;
625,539,267✔
468
    c_[i] = c_[i] / integral_;
625,539,267✔
469
  }
470
}
46,589,174✔
471

472
double Tabular::sample_unbiased(uint64_t* seed) const
693,064,051✔
473
{
474
  // Sample value of CDF
475
  double c = prn(seed);
693,064,051✔
476

477
  // Find first CDF bin which is above the sampled value
478
  double c_i = c_[0];
693,064,051✔
479
  int i;
480
  std::size_t n = c_.size();
693,064,051✔
481
  for (i = 0; i < n - 1; ++i) {
2,147,483,647!
482
    if (c <= c_[i + 1])
2,147,483,647✔
483
      break;
693,064,051✔
484
    c_i = c_[i + 1];
1,912,041,538✔
485
  }
486

487
  // Determine bounding PDF values
488
  double x_i = x_[i];
693,064,051✔
489
  double p_i = p_[i];
693,064,051✔
490

491
  if (interp_ == Interpolation::histogram) {
693,064,051✔
492
    // Histogram interpolation
493
    if (p_i > 0.0) {
3,421,781!
494
      return x_i + (c - c_i) / p_i;
3,421,781✔
495
    } else {
496
      return x_i;
×
497
    }
498
  } else {
499
    // Linear-linear interpolation
500
    double x_i1 = x_[i + 1];
689,642,270✔
501
    double p_i1 = p_[i + 1];
689,642,270✔
502

503
    double m = (p_i1 - p_i) / (x_i1 - x_i);
689,642,270✔
504
    if (m == 0.0) {
689,642,270✔
505
      return x_i + (c - c_i) / p_i;
322,157,966✔
506
    } else {
507
      return x_i +
508
             (std::sqrt(std::max(0.0, p_i * p_i + 2 * m * (c - c_i))) - p_i) /
367,484,304✔
509
               m;
367,484,304✔
510
    }
511
  }
512
}
513

514
double Tabular::evaluate(double x) const
110,000✔
515
{
516
  int i;
517

518
  if (interp_ == Interpolation::histogram) {
110,000!
NEW
519
    i = std::upper_bound(x_.begin(), x_.end(), x) - x_.begin() - 1;
×
NEW
520
    if (i < 0 || i >= static_cast<int>(p_.size())) {
×
NEW
521
      return 0.0;
×
522
    } else {
NEW
523
      return p_[i];
×
524
    }
525
  } else {
526
    i = std::lower_bound(x_.begin(), x_.end(), x) - x_.begin() - 1;
110,000✔
527

528
    if (i < 0 || i >= static_cast<int>(p_.size()) - 1) {
110,000!
NEW
529
      return 0.0;
×
530
    } else {
531
      double x0 = x_[i];
110,000✔
532
      double x1 = x_[i + 1];
110,000✔
533
      double p0 = p_[i];
110,000✔
534
      double p1 = p_[i + 1];
110,000✔
535

536
      double t = (x - x0) / (x1 - x0);
110,000✔
537
      return (1 - t) * p0 + t * p1;
110,000✔
538
    }
539
  }
540
}
541

542
//==============================================================================
543
// Equiprobable implementation
544
//==============================================================================
545

NEW
546
double Equiprobable::sample_unbiased(uint64_t* seed) const
×
547
{
548
  std::size_t n = x_.size();
×
549

550
  double r = prn(seed);
×
551
  int i = std::floor((n - 1) * r);
×
552

553
  double xl = x_[i];
×
554
  double xr = x_[i + i];
×
555
  return xl + ((n - 1) * r - i) * (xr - xl);
×
556
}
557

NEW
558
double Equiprobable::evaluate(double x) const
×
559
{
NEW
560
  double x_min = *std::min_element(x_.begin(), x_.end());
×
NEW
561
  double x_max = *std::max_element(x_.begin(), x_.end());
×
562

NEW
563
  if (x < x_min || x > x_max) {
×
NEW
564
    return 0.0;
×
565
  } else {
NEW
566
    return 1.0 / (x_max - x_min);
×
567
  }
568
}
569

570
//==============================================================================
571
// Mixture implementation
572
//==============================================================================
573

574
Mixture::Mixture(pugi::xml_node node)
70✔
575
{
576
  vector<double> probabilities;
70✔
577

578
  // First pass: collect distributions and their probabilities
579
  for (pugi::xml_node pair : node.children("pair")) {
258✔
580
    // Check that required data exists
581
    if (!pair.attribute("probability"))
188!
582
      fatal_error("Mixture pair element does not have probability.");
×
583
    if (!pair.child("dist"))
188!
584
      fatal_error("Mixture pair element does not have a distribution.");
×
585

586
    // Get probability and distribution
587
    double p = std::stod(pair.attribute("probability").value());
188✔
588
    auto dist = distribution_from_xml(pair.child("dist"));
188✔
589

590
    // Weight probability by the distribution's integral
591
    double weighted_prob = p * dist->integral();
188✔
592
    probabilities.push_back(weighted_prob);
188✔
593
    distribution_.push_back(std::move(dist));
188✔
594
  }
188✔
595

596
  // Save sum of weighted probabilities
597
  integral_ = std::accumulate(probabilities.begin(), probabilities.end(), 0.0);
70✔
598

599
  std::size_t n = probabilities.size();
70✔
600

601
  // Check for bias
602
  if (check_for_node(node, "bias")) {
70!
603
    // Get bias probabilities
NEW
604
    auto bias_params = get_node_array<double>(node, "bias");
×
NEW
605
    if (bias_params.size() != n) {
×
NEW
606
      openmc::fatal_error(
×
NEW
607
        "Size mismatch: Attempted to bias Mixture distribution with " +
×
NEW
608
        std::to_string(n) + " components using a bias with " +
×
NEW
609
        std::to_string(bias_params.size()) +
×
610
        " entries. Please ensure distributions have the same size.");
611
    }
612

613
    // Compute importance weights
NEW
614
    weight_ = compute_importance_weights(probabilities, bias_params);
×
615

616
    // Initialize DiscreteIndex with bias probabilities for sampling
NEW
617
    di_.assign(bias_params);
×
NEW
618
  } else {
×
619
    // Unbiased case: weight_ stays empty
620
    di_.assign(probabilities);
70✔
621
  }
622
}
70✔
623

624
std::pair<double, double> Mixture::sample(uint64_t* seed) const
223,564✔
625
{
626
  size_t idx = di_.sample(seed);
223,564✔
627

628
  // Sample the chosen distribution
629
  auto [val, sub_wgt] = distribution_[idx]->sample(seed);
223,564✔
630

631
  // Multiply by component selection weight
632
  double mix_wgt = weight_.empty() ? 1.0 : weight_[idx];
223,564!
633
  return {val, mix_wgt * sub_wgt};
223,564✔
634
}
635

NEW
636
double Mixture::sample_unbiased(uint64_t* seed) const
×
637
{
NEW
638
  size_t idx = di_.sample(seed);
×
NEW
639
  return distribution_[idx]->sample(seed).first;
×
640
}
641

642
//==============================================================================
643
// Helper function
644
//==============================================================================
645

646
UPtrDist distribution_from_xml(pugi::xml_node node)
35,320✔
647
{
648
  if (!check_for_node(node, "type"))
35,320!
649
    openmc::fatal_error("Distribution type must be specified.");
×
650

651
  // Determine type of distribution
652
  std::string type = get_node_value(node, "type", true, true);
35,320✔
653

654
  // Allocate extension of Distribution
655
  UPtrDist dist;
35,320✔
656
  if (type == "uniform") {
35,320✔
657
    dist = UPtrDist {new Uniform(node)};
344✔
658
  } else if (type == "powerlaw") {
34,976✔
659
    dist = UPtrDist {new PowerLaw(node)};
76✔
660
  } else if (type == "maxwell") {
34,900✔
661
    dist = UPtrDist {new Maxwell(node)};
97✔
662
  } else if (type == "watt") {
34,803✔
663
    dist = UPtrDist {new Watt(node)};
129✔
664
  } else if (type == "normal") {
34,674✔
665
    dist = UPtrDist {new Normal(node)};
33✔
666
  } else if (type == "discrete") {
34,641✔
667
    dist = UPtrDist {new Discrete(node)};
26,934✔
668
  } else if (type == "tabular") {
7,707✔
669
    dist = UPtrDist {new Tabular(node)};
7,637✔
670
  } else if (type == "mixture") {
70!
671
    dist = UPtrDist {new Mixture(node)};
70✔
672
  } else if (type == "muir") {
×
673
    openmc::fatal_error(
×
674
      "'muir' distributions are now specified using the openmc.stats.muir() "
675
      "function in Python. Please regenerate your XML files.");
676
  } else {
677
    openmc::fatal_error("Invalid distribution type: " + type);
×
678
  }
679
  return dist;
70,640✔
680
}
35,320✔
681

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