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

openmc-dev / openmc / 15914061027

26 Jun 2025 10:49PM UTC coverage: 85.241% (+0.002%) from 85.239%
15914061027

Pull #3461

github

web-flow
Merge 09b49b487 into 5c1021446
Pull Request #3461: Refactor and Harden Configuration Management

48 of 56 new or added lines in 1 file covered. (85.71%)

280 existing lines in 11 files now uncovered.

52596 of 61703 relevant lines covered (85.24%)

36708109.72 hits per line

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

81.34
/src/weight_windows.cpp
1
#include "openmc/weight_windows.h"
2

3
#include <algorithm>
4
#include <cassert>
5
#include <cmath>
6
#include <set>
7
#include <string>
8

9
#include "xtensor/xdynamic_view.hpp"
10
#include "xtensor/xindex_view.hpp"
11
#include "xtensor/xio.hpp"
12
#include "xtensor/xmasked_view.hpp"
13
#include "xtensor/xnoalias.hpp"
14
#include "xtensor/xview.hpp"
15

16
#include "openmc/error.h"
17
#include "openmc/file_utils.h"
18
#include "openmc/hdf5_interface.h"
19
#include "openmc/mesh.h"
20
#include "openmc/message_passing.h"
21
#include "openmc/nuclide.h"
22
#include "openmc/output.h"
23
#include "openmc/particle.h"
24
#include "openmc/particle_data.h"
25
#include "openmc/physics_common.h"
26
#include "openmc/random_ray/flat_source_domain.h"
27
#include "openmc/search.h"
28
#include "openmc/settings.h"
29
#include "openmc/tallies/filter_energy.h"
30
#include "openmc/tallies/filter_mesh.h"
31
#include "openmc/tallies/filter_particle.h"
32
#include "openmc/tallies/tally.h"
33
#include "openmc/xml_interface.h"
34

35
#include <fmt/core.h>
36

37
namespace openmc {
38

39
//==============================================================================
40
// Global variables
41
//==============================================================================
42

43
namespace variance_reduction {
44

45
std::unordered_map<int32_t, int32_t> ww_map;
46
openmc::vector<unique_ptr<WeightWindows>> weight_windows;
47
openmc::vector<unique_ptr<WeightWindowsGenerator>> weight_windows_generators;
48

49
} // namespace variance_reduction
50

51
//==============================================================================
52
// Non-member functions
53
//==============================================================================
54

55
void apply_weight_windows(Particle& p)
2,147,483,647✔
56
{
57
  if (!settings::weight_windows_on)
2,147,483,647✔
58
    return;
2,147,483,647✔
59

60
  // WW on photon and neutron only
61
  if (p.type() != ParticleType::neutron && p.type() != ParticleType::photon)
81,556,950✔
62
    return;
10,809,524✔
63

64
  // skip dead or no energy
65
  if (p.E() <= 0 || !p.alive())
70,747,426✔
66
    return;
4,059,292✔
67

68
  bool in_domain = false;
66,688,134✔
69
  // TODO: this is a linear search - should do something more clever
70
  WeightWindow weight_window;
66,688,134✔
71
  for (const auto& ww : variance_reduction::weight_windows) {
84,133,833✔
72
    weight_window = ww->get_weight_window(p);
70,807,502✔
73
    if (weight_window.is_valid())
70,807,502✔
74
      break;
53,361,803✔
75
  }
76

77
  // If particle has not yet had its birth weight window value set, set it to
78
  // the current weight window (or 1.0 if not born in a weight window).
79
  if (p.wgt_ww_born() == -1.0) {
66,688,134✔
80
    if (weight_window.is_valid()) {
736,500✔
81
      p.wgt_ww_born() =
670,698✔
82
        (weight_window.lower_weight + weight_window.upper_weight) / 2;
670,698✔
83
    } else {
84
      p.wgt_ww_born() = 1.0;
65,802✔
85
    }
86
  }
87

88
  // particle is not in any of the ww domains, do nothing
89
  if (!weight_window.is_valid())
66,688,134✔
90
    return;
13,326,331✔
91

92
  // Normalize weight windows based on particle's starting weight
93
  // and the value of the weight window the particle was born in.
94
  weight_window.scale(p.wgt_born() / p.wgt_ww_born());
53,361,803✔
95

96
  // get the paramters
97
  double weight = p.wgt();
53,361,803✔
98

99
  // first check to see if particle should be killed for weight cutoff
100
  if (p.wgt() < weight_window.weight_cutoff) {
53,361,803✔
UNCOV
101
    p.wgt() = 0.0;
×
UNCOV
102
    return;
×
103
  }
104

105
  // check if particle is far above current weight window
106
  // only do this if the factor is not already set on the particle and a
107
  // maximum lower bound ratio is specified
108
  if (p.ww_factor() == 0.0 && weight_window.max_lb_ratio > 1.0 &&
53,364,641✔
109
      p.wgt() > weight_window.lower_weight * weight_window.max_lb_ratio) {
2,838✔
110
    p.ww_factor() =
2,838✔
111
      p.wgt() / (weight_window.lower_weight * weight_window.max_lb_ratio);
2,838✔
112
  }
113

114
  // move weight window closer to the particle weight if needed
115
  if (p.ww_factor() > 1.0)
53,361,803✔
116
    weight_window.scale(p.ww_factor());
1,437,755✔
117

118
  // if particle's weight is above the weight window split until they are within
119
  // the window
120
  if (weight > weight_window.upper_weight) {
53,361,803✔
121
    // do not further split the particle if above the limit
122
    if (p.n_split() >= settings::max_history_splits)
13,118,038✔
123
      return;
11,812,649✔
124

125
    double n_split = std::ceil(weight / weight_window.upper_weight);
1,305,389✔
126
    double max_split = weight_window.max_split;
1,305,389✔
127
    n_split = std::min(n_split, max_split);
1,305,389✔
128

129
    p.n_split() += n_split;
1,305,389✔
130

131
    // Create secondaries and divide weight among all particles
132
    int i_split = std::round(n_split);
1,305,389✔
133
    for (int l = 0; l < i_split - 1; l++) {
5,391,136✔
134
      p.split(weight / n_split);
4,085,747✔
135
    }
136
    // remaining weight is applied to current particle
137
    p.wgt() = weight / n_split;
1,305,389✔
138

139
  } else if (weight <= weight_window.lower_weight) {
40,243,765✔
140
    // if the particle weight is below the window, play Russian roulette
141
    double weight_survive =
142
      std::min(weight * weight_window.max_split, weight_window.survival_weight);
1,432,322✔
143
    russian_roulette(p, weight_survive);
1,432,322✔
144
  } // else particle is in the window, continue as normal
145
}
146

147
void free_memory_weight_windows()
6,814✔
148
{
149
  variance_reduction::ww_map.clear();
6,814✔
150
  variance_reduction::weight_windows.clear();
6,814✔
151
}
6,814✔
152

153
//==============================================================================
154
// WeightWindowSettings implementation
155
//==============================================================================
156

157
WeightWindows::WeightWindows(int32_t id)
246✔
158
{
159
  index_ = variance_reduction::weight_windows.size();
246✔
160
  set_id(id);
246✔
161
  set_defaults();
246✔
162
}
246✔
163

164
WeightWindows::WeightWindows(pugi::xml_node node)
90✔
165
{
166
  // Make sure required elements are present
167
  const vector<std::string> required_elems {
168
    "id", "particle_type", "lower_ww_bounds", "upper_ww_bounds"};
630✔
169
  for (const auto& elem : required_elems) {
450✔
170
    if (!check_for_node(node, elem.c_str())) {
360✔
UNCOV
171
      fatal_error(fmt::format("Must specify <{}> for weight windows.", elem));
×
172
    }
173
  }
174

175
  // Get weight windows ID
176
  int32_t id = std::stoi(get_node_value(node, "id"));
90✔
177
  this->set_id(id);
90✔
178

179
  // get the particle type
180
  auto particle_type_str = std::string(get_node_value(node, "particle_type"));
90✔
181
  particle_type_ = openmc::str_to_particle_type(particle_type_str);
90✔
182

183
  // Determine associated mesh
184
  int32_t mesh_id = std::stoi(get_node_value(node, "mesh"));
90✔
185
  set_mesh(model::mesh_map.at(mesh_id));
90✔
186

187
  // energy bounds
188
  if (check_for_node(node, "energy_bounds"))
90✔
189
    energy_bounds_ = get_node_array<double>(node, "energy_bounds");
76✔
190

191
  // get the survival value - optional
192
  if (check_for_node(node, "survival_ratio")) {
90✔
193
    survival_ratio_ = std::stod(get_node_value(node, "survival_ratio"));
90✔
194
    if (survival_ratio_ <= 1)
90✔
195
      fatal_error("Survival to lower weight window ratio must bigger than 1 "
×
196
                  "and less than the upper to lower weight window ratio.");
197
  }
198

199
  // get the max lower bound ratio - optional
200
  if (check_for_node(node, "max_lower_bound_ratio")) {
90✔
201
    max_lb_ratio_ = std::stod(get_node_value(node, "max_lower_bound_ratio"));
32✔
202
    if (max_lb_ratio_ < 1.0) {
32✔
UNCOV
203
      fatal_error("Maximum lower bound ratio must be larger than 1");
×
204
    }
205
  }
206

207
  // get the max split - optional
208
  if (check_for_node(node, "max_split")) {
90✔
209
    max_split_ = std::stod(get_node_value(node, "max_split"));
90✔
210
    if (max_split_ <= 1)
90✔
UNCOV
211
      fatal_error("max split must be larger than 1");
×
212
  }
213

214
  // weight cutoff - optional
215
  if (check_for_node(node, "weight_cutoff")) {
90✔
216
    weight_cutoff_ = std::stod(get_node_value(node, "weight_cutoff"));
90✔
217
    if (weight_cutoff_ <= 0)
90✔
UNCOV
218
      fatal_error("weight_cutoff must be larger than 0");
×
219
    if (weight_cutoff_ > 1)
90✔
UNCOV
220
      fatal_error("weight_cutoff must be less than 1");
×
221
  }
222

223
  // read the lower/upper weight bounds
224
  this->set_bounds(get_node_array<double>(node, "lower_ww_bounds"),
90✔
225
    get_node_array<double>(node, "upper_ww_bounds"));
180✔
226

227
  set_defaults();
90✔
228
}
90✔
229

230
WeightWindows::~WeightWindows()
336✔
231
{
232
  variance_reduction::ww_map.erase(id());
336✔
233
}
336✔
234

235
WeightWindows* WeightWindows::create(int32_t id)
92✔
236
{
237
  variance_reduction::weight_windows.push_back(make_unique<WeightWindows>());
92✔
238
  auto wws = variance_reduction::weight_windows.back().get();
92✔
239
  variance_reduction::ww_map[wws->id()] =
92✔
240
    variance_reduction::weight_windows.size() - 1;
92✔
241
  return wws;
92✔
242
}
243

244
WeightWindows* WeightWindows::from_hdf5(
11✔
245
  hid_t wws_group, const std::string& group_name)
246
{
247
  // collect ID from the name of this group
248
  hid_t ww_group = open_group(wws_group, group_name);
11✔
249

250
  auto wws = WeightWindows::create();
11✔
251

252
  std::string particle_type;
11✔
253
  read_dataset(ww_group, "particle_type", particle_type);
11✔
254
  wws->particle_type_ = openmc::str_to_particle_type(particle_type);
11✔
255

256
  read_dataset<double>(ww_group, "energy_bounds", wws->energy_bounds_);
11✔
257

258
  int32_t mesh_id;
259
  read_dataset(ww_group, "mesh", mesh_id);
11✔
260

261
  if (model::mesh_map.count(mesh_id) == 0) {
11✔
UNCOV
262
    fatal_error(
×
UNCOV
263
      fmt::format("Mesh {} used in weight windows does not exist.", mesh_id));
×
264
  }
265
  wws->set_mesh(model::mesh_map[mesh_id]);
11✔
266

267
  wws->lower_ww_ = xt::empty<double>(wws->bounds_size());
11✔
268
  wws->upper_ww_ = xt::empty<double>(wws->bounds_size());
11✔
269

270
  read_dataset<double>(ww_group, "lower_ww_bounds", wws->lower_ww_);
11✔
271
  read_dataset<double>(ww_group, "upper_ww_bounds", wws->upper_ww_);
11✔
272
  read_dataset(ww_group, "survival_ratio", wws->survival_ratio_);
11✔
273
  read_dataset(ww_group, "max_lower_bound_ratio", wws->max_lb_ratio_);
11✔
274
  read_dataset(ww_group, "max_split", wws->max_split_);
11✔
275
  read_dataset(ww_group, "weight_cutoff", wws->weight_cutoff_);
11✔
276

277
  close_group(ww_group);
11✔
278

279
  return wws;
11✔
280
}
11✔
281

282
void WeightWindows::set_defaults()
417✔
283
{
284
  // set energy bounds to the min/max energy supported by the data
285
  if (energy_bounds_.size() == 0) {
417✔
286
    int p_type = static_cast<int>(particle_type_);
260✔
287
    energy_bounds_.push_back(data::energy_min[p_type]);
260✔
288
    energy_bounds_.push_back(data::energy_max[p_type]);
260✔
289
  }
290
}
417✔
291

292
void WeightWindows::allocate_ww_bounds()
549✔
293
{
294
  auto shape = bounds_size();
549✔
295
  if (shape[0] * shape[1] == 0) {
549✔
296
    auto msg = fmt::format(
UNCOV
297
      "Size of weight window bounds is zero for WeightWindows {}", id());
×
UNCOV
298
    warning(msg);
×
299
  }
300
  lower_ww_ = xt::empty<double>(shape);
549✔
301
  lower_ww_.fill(-1);
549✔
302
  upper_ww_ = xt::empty<double>(shape);
549✔
303
  upper_ww_.fill(-1);
549✔
304
}
549✔
305

306
void WeightWindows::set_id(int32_t id)
490✔
307
{
308
  assert(id >= 0 || id == C_NONE);
399✔
309

310
  // Clear entry in mesh map in case one was already assigned
311
  if (id_ != C_NONE) {
490✔
312
    variance_reduction::ww_map.erase(id_);
490✔
313
    id_ = C_NONE;
490✔
314
  }
315

316
  // Ensure no other mesh has the same ID
317
  if (variance_reduction::ww_map.find(id) != variance_reduction::ww_map.end()) {
490✔
UNCOV
318
    throw std::runtime_error {
×
UNCOV
319
      fmt::format("Two weight windows have the same ID: {}", id)};
×
320
  }
321

322
  // If no ID is specified, auto-assign the next ID in the sequence
323
  if (id == C_NONE) {
490✔
324
    id = 0;
246✔
325
    for (const auto& m : variance_reduction::weight_windows) {
268✔
326
      id = std::max(id, m->id_);
22✔
327
    }
328
    ++id;
246✔
329
  }
330

331
  // Update ID and entry in the mesh map
332
  id_ = id;
490✔
333
  variance_reduction::ww_map[id] = index_;
490✔
334
}
490✔
335

336
void WeightWindows::set_energy_bounds(span<const double> bounds)
213✔
337
{
338
  energy_bounds_.clear();
213✔
339
  energy_bounds_.insert(energy_bounds_.begin(), bounds.begin(), bounds.end());
213✔
340
  // if the mesh is set, allocate space for weight window bounds
341
  if (mesh_idx_ != C_NONE)
213✔
342
    allocate_ww_bounds();
213✔
343
}
213✔
344

345
void WeightWindows::set_particle_type(ParticleType p_type)
257✔
346
{
347
  if (p_type != ParticleType::neutron && p_type != ParticleType::photon)
257✔
348
    fatal_error(
×
UNCOV
349
      fmt::format("Particle type '{}' cannot be applied to weight windows.",
×
350
        particle_type_to_str(p_type)));
×
351
  particle_type_ = p_type;
257✔
352
}
257✔
353

354
void WeightWindows::set_mesh(int32_t mesh_idx)
336✔
355
{
356
  if (mesh_idx < 0 || mesh_idx >= model::meshes.size())
336✔
UNCOV
357
    fatal_error(fmt::format("Could not find a mesh for index {}", mesh_idx));
×
358

359
  mesh_idx_ = mesh_idx;
336✔
360
  model::meshes[mesh_idx_]->prepare_for_point_location();
336✔
361
  allocate_ww_bounds();
336✔
362
}
336✔
363

UNCOV
364
void WeightWindows::set_mesh(const std::unique_ptr<Mesh>& mesh)
×
365
{
UNCOV
366
  set_mesh(mesh.get());
×
367
}
368

UNCOV
369
void WeightWindows::set_mesh(const Mesh* mesh)
×
370
{
371
  set_mesh(model::mesh_map[mesh->id_]);
×
372
}
373

374
WeightWindow WeightWindows::get_weight_window(const Particle& p) const
70,807,502✔
375
{
376
  // check for particle type
377
  if (particle_type_ != p.type()) {
70,807,502✔
378
    return {};
3,904,076✔
379
  }
380

381
  // Get mesh index for particle's position
382
  const auto& mesh = this->mesh();
66,903,426✔
383
  int mesh_bin = mesh->get_bin(p.r());
66,903,426✔
384

385
  // particle is outside the weight window mesh
386
  if (mesh_bin < 0)
66,903,426✔
UNCOV
387
    return {};
×
388

389
  // particle energy
390
  double E = p.E();
66,903,426✔
391

392
  // check to make sure energy is in range, expects sorted energy values
393
  if (E < energy_bounds_.front() || E > energy_bounds_.back())
66,903,426✔
394
    return {};
92,598✔
395

396
  // get the mesh bin in energy group
397
  int energy_bin =
398
    lower_bound_index(energy_bounds_.begin(), energy_bounds_.end(), E);
66,810,828✔
399

400
  // mesh_bin += energy_bin * mesh->n_bins();
401
  // Create individual weight window
402
  WeightWindow ww;
66,810,828✔
403
  ww.lower_weight = lower_ww_(energy_bin, mesh_bin);
66,810,828✔
404
  ww.upper_weight = upper_ww_(energy_bin, mesh_bin);
66,810,828✔
405
  ww.survival_weight = ww.lower_weight * survival_ratio_;
66,810,828✔
406
  ww.max_lb_ratio = max_lb_ratio_;
66,810,828✔
407
  ww.max_split = max_split_;
66,810,828✔
408
  ww.weight_cutoff = weight_cutoff_;
66,810,828✔
409
  return ww;
66,810,828✔
410
}
411

412
std::array<int, 2> WeightWindows::bounds_size() const
773✔
413
{
414
  int num_spatial_bins = this->mesh()->n_bins();
773✔
415
  int num_energy_bins =
416
    energy_bounds_.size() > 0 ? energy_bounds_.size() - 1 : 1;
773✔
417
  return {num_energy_bins, num_spatial_bins};
773✔
418
}
419

420
template<class T>
421
void WeightWindows::check_bounds(const T& lower, const T& upper) const
101✔
422
{
423
  // make sure that the upper and lower bounds have the same size
424
  if (lower.size() != upper.size()) {
101✔
UNCOV
425
    auto msg = fmt::format("The upper and lower weight window lengths do not "
×
426
                           "match.\n Lower size: {}\n Upper size: {}",
427
      lower.size(), upper.size());
×
UNCOV
428
    fatal_error(msg);
×
UNCOV
429
  }
×
430
  this->check_bounds(lower);
101✔
431
}
101✔
432

101✔
433
template<class T>
434
void WeightWindows::check_bounds(const T& bounds) const
435
{
101✔
436
  // check that the number of weight window entries is correct
×
437
  auto dims = this->bounds_size();
UNCOV
438
  if (bounds.size() != dims[0] * dims[1]) {
×
UNCOV
439
    auto err_msg =
×
UNCOV
440
      fmt::format("In weight window domain {} the number of spatial "
×
441
                  "energy/spatial bins ({}) does not match the number "
101✔
442
                  "of weight bins ({})",
101✔
UNCOV
443
        id_, dims, bounds.size());
×
444
    fatal_error(err_msg);
445
  }
UNCOV
446
}
×
UNCOV
447

×
448
void WeightWindows::set_bounds(const xt::xtensor<double, 2>& lower_bounds,
449
  const xt::xtensor<double, 2>& upper_bounds)
×
450
{
×
451

×
UNCOV
452
  this->check_bounds(lower_bounds, upper_bounds);
×
453

454
  // set new weight window values
455
  lower_ww_ = lower_bounds;
456
  upper_ww_ = upper_bounds;
101✔
457
}
458

459
void WeightWindows::set_bounds(
101✔
460
  const xt::xtensor<double, 2>& lower_bounds, double ratio)
101✔
UNCOV
461
{
×
462
  this->check_bounds(lower_bounds);
463

464
  // set new weight window values
UNCOV
465
  lower_ww_ = lower_bounds;
×
466
  upper_ww_ = lower_bounds;
×
UNCOV
467
  upper_ww_ *= ratio;
×
468
}
101✔
469

101✔
470
void WeightWindows::set_bounds(
471
  span<const double> lower_bounds, span<const double> upper_bounds)
472
{
101✔
473
  check_bounds(lower_bounds, upper_bounds);
101✔
UNCOV
474
  auto shape = this->bounds_size();
×
475
  lower_ww_ = xt::empty<double>(shape);
476
  upper_ww_ = xt::empty<double>(shape);
477

UNCOV
478
  // set new weight window values
×
UNCOV
479
  xt::view(lower_ww_, xt::all()) =
×
480
    xt::adapt(lower_bounds.data(), lower_ww_.shape());
×
481
  xt::view(upper_ww_, xt::all()) =
101✔
UNCOV
482
    xt::adapt(upper_bounds.data(), upper_ww_.shape());
×
483
}
484

UNCOV
485
void WeightWindows::set_bounds(span<const double> lower_bounds, double ratio)
×
UNCOV
486
{
×
487
  this->check_bounds(lower_bounds);
×
488

489
  auto shape = this->bounds_size();
490
  lower_ww_ = xt::empty<double>(shape);
491
  upper_ww_ = xt::empty<double>(shape);
×
UNCOV
492

×
UNCOV
493
  // set new weight window values
×
494
  xt::view(lower_ww_, xt::all()) =
495
    xt::adapt(lower_bounds.data(), lower_ww_.shape());
UNCOV
496
  xt::view(upper_ww_, xt::all()) =
×
497
    xt::adapt(lower_bounds.data(), upper_ww_.shape());
498
  upper_ww_ *= ratio;
499
}
UNCOV
500

×
501
void WeightWindows::update_weights(const Tally* tally, const std::string& value,
502
  double threshold, double ratio, WeightWindowUpdateMethod method)
UNCOV
503
{
×
UNCOV
504
  ///////////////////////////
×
505
  // Setup and checks
506
  ///////////////////////////
UNCOV
507
  this->check_tally_update_compatibility(tally);
×
508

509
  // Dimensions of weight window arrays
UNCOV
510
  int e_bins = lower_ww_.shape()[0];
×
511
  int64_t mesh_bins = lower_ww_.shape()[1];
512

UNCOV
513
  // Initialize weight window arrays to -1.0 by default
×
UNCOV
514
#pragma omp parallel for collapse(2) schedule(static)
×
UNCOV
515
  for (int e = 0; e < e_bins; e++) {
×
516
    for (int64_t m = 0; m < mesh_bins; m++) {
517
      lower_ww_(e, m) = -1.0;
518
      upper_ww_(e, m) = -1.0;
101✔
519
    }
520
  }
521

101✔
522
  // determine which value to use
101✔
523
  const std::set<std::string> allowed_values = {"mean", "rel_err"};
101✔
524
  if (allowed_values.count(value) == 0) {
101✔
525
    fatal_error(fmt::format("Invalid value '{}' specified for weight window "
526
                            "generation. Must be one of: 'mean' or 'rel_err'",
527
      value));
202✔
528
  }
303✔
529

202✔
530
  // determine the index of the specified score
303✔
531
  int score_index = tally->score_index("flux");
101✔
532
  if (score_index == C_NONE) {
UNCOV
533
    fatal_error(
×
534
      fmt::format("A 'flux' score required for weight window generation "
UNCOV
535
                  "is not present on tally {}.",
×
536
        tally->id()));
UNCOV
537
  }
×
UNCOV
538

×
UNCOV
539
  ///////////////////////////
×
540
  // Extract tally data
541
  //
UNCOV
542
  // At the end of this section, the mean and rel_err array
×
UNCOV
543
  // is a 2D view of tally data (n_e_groups, n_mesh_bins)
×
UNCOV
544
  //
×
UNCOV
545
  ///////////////////////////
×
UNCOV
546

×
547
  // build a shape for a view of the tally results, this will always be
548
  // dimension 5 (3 filter dimensions, 1 score dimension, 1 results dimension)
549
  std::array<int, 5> shape = {
250✔
550
    1, 1, 1, tally->n_scores(), static_cast<int>(TallyResult::SIZE)};
551

552
  // set the shape for the filters applied on the tally
553
  for (int i = 0; i < tally->filters().size(); i++) {
554
    const auto& filter = model::tally_filters[tally->filters(i)];
555
    shape[i] = filter->n_bins();
250✔
556
  }
557

558
  // build the transpose information to re-order data according to filter type
250✔
559
  std::array<int, 5> transpose = {0, 1, 2, 3, 4};
250✔
560

561
  // track our filter types and where we've added new ones
562
  std::vector<FilterType> filter_types = tally->filter_types();
138✔
563

934✔
564
  // assign other filter types to dummy positions if needed
1,190,071✔
565
  if (!tally->has_filter(FilterType::PARTICLE))
1,189,249✔
566
    filter_types.push_back(FilterType::PARTICLE);
1,189,249✔
567

568
  if (!tally->has_filter(FilterType::ENERGY))
569
    filter_types.push_back(FilterType::ENERGY);
570

571
  // particle axis mapping
1,250✔
572
  transpose[0] =
250✔
UNCOV
573
    std::find(filter_types.begin(), filter_types.end(), FilterType::PARTICLE) -
×
574
    filter_types.begin();
575

576
  // energy axis mapping
577
  transpose[1] =
578
    std::find(filter_types.begin(), filter_types.end(), FilterType::ENERGY) -
579
    filter_types.begin();
250✔
580

250✔
UNCOV
581
  // mesh axis mapping
×
UNCOV
582
  transpose[2] =
×
583
    std::find(filter_types.begin(), filter_types.end(), FilterType::MESH) -
UNCOV
584
    filter_types.begin();
×
585

586
  // get a fully reshaped view of the tally according to tally ordering of
587
  // filters
588
  auto tally_values = xt::reshape_view(tally->results(), shape);
589

590
  // get a that is (particle, energy, mesh, scores, values)
591
  auto transposed_view = xt::transpose(tally_values, transpose);
592

593
  // determine the dimension and index of the particle data
594
  int particle_idx = 0;
595
  if (tally->has_filter(FilterType::PARTICLE)) {
596
    // get the particle filter
597
    auto pf = tally->get_filter<ParticleFilter>();
250✔
598
    const auto& particles = pf->particles();
250✔
599

600
    // find the index of the particle that matches these weight windows
601
    auto p_it =
956✔
602
      std::find(particles.begin(), particles.end(), this->particle_type_);
706✔
603
    // if the particle filter doesn't have particle data for the particle
706✔
604
    // used on this weight windows instance, report an error
605
    if (p_it == particles.end()) {
606
      auto msg = fmt::format("Particle type '{}' not present on Filter {} for "
607
                             "Tally {} used to update WeightWindows {}",
250✔
608
        particle_type_to_str(this->particle_type_), pf->id(), tally->id(),
609
        this->id());
610
      fatal_error(msg);
250✔
611
    }
612

613
    // use the index of the particle in the filter to down-select data later
250✔
614
    particle_idx = p_it - particles.begin();
22✔
615
  }
616

250✔
617
  // down-select data based on particle and score
22✔
618
  auto sum = xt::dynamic_view(
619
    transposed_view, {particle_idx, xt::all(), xt::all(), score_index,
620
                       static_cast<int>(TallyResult::SUM)});
250✔
621
  auto sum_sq = xt::dynamic_view(
250✔
622
    transposed_view, {particle_idx, xt::all(), xt::all(), score_index,
250✔
623
                       static_cast<int>(TallyResult::SUM_SQ)});
624
  int n = tally->n_realizations_;
625

250✔
626
  //////////////////////////////////////////////
250✔
627
  //
250✔
628
  // Assign new weight windows
629
  //
630
  // Use references to the existing weight window data
250✔
631
  // to store and update the values
250✔
632
  //
250✔
633
  //////////////////////////////////////////////
634

635
  // up to this point the data arrays are views into the tally results (no
636
  // computation has been performed) now we'll switch references to the tally's
250✔
637
  // bounds to avoid allocating additional memory
638
  auto& new_bounds = this->lower_ww_;
639
  auto& rel_err = this->upper_ww_;
250✔
640

641
  // get mesh volumes
642
  auto mesh_vols = this->mesh()->volumes();
250✔
643

250✔
644
  // Calculate mean (new_bounds) and relative error
645
#pragma omp parallel for collapse(2) schedule(static)
228✔
646
  for (int e = 0; e < e_bins; e++) {
228✔
647
    for (int64_t m = 0; m < mesh_bins; m++) {
648
      // Calculate mean
649
      new_bounds(e, m) = sum(e, m) / n;
650
      // Calculate relative error
228✔
651
      if (sum(e, m) > 0.0) {
652
        double mean_val = new_bounds(e, m);
653
        double variance = (sum_sq(e, m) / n - mean_val * mean_val) / (n - 1);
228✔
654
        rel_err(e, m) = std::sqrt(variance) / mean_val;
655
      } else {
UNCOV
656
        rel_err(e, m) = INFTY;
×
UNCOV
657
      }
×
UNCOV
658
      if (value == "rel_err") {
×
UNCOV
659
        new_bounds(e, m) = 1.0 / rel_err(e, m);
×
660
      }
661
    }
662
  }
228✔
663

664
  // Divide by volume of mesh elements
665
#pragma omp parallel for collapse(2) schedule(static)
666
  for (int e = 0; e < e_bins; e++) {
1,250✔
667
    for (int64_t m = 0; m < mesh_bins; m++) {
500✔
668
      new_bounds(e, m) /= mesh_vols[m];
1,000✔
669
    }
1,250✔
670
  }
500✔
671

1,000✔
672
  if (method == WeightWindowUpdateMethod::MAGIC) {
250✔
673
    // For MAGIC, weight windows are proportional to the forward fluxes.
674
    // We normalize weight windows independently for each energy group.
675

676
    // Find group maximum and normalize (per energy group)
677
    for (int e = 0; e < e_bins; e++) {
678
      double group_max = 0.0;
679

680
      // Find maximum value across all elements in this energy group
681
#pragma omp parallel for schedule(static) reduction(max : group_max)
682
      for (int64_t m = 0; m < mesh_bins; m++) {
683
        if (new_bounds(e, m) > group_max) {
684
          group_max = new_bounds(e, m);
685
        }
686
      }
250✔
687

250✔
688
      // Normalize values in this energy group by the maximum value
689
      if (group_max > 0.0) {
690
        double norm_factor = 1.0 / (2.0 * group_max);
250✔
691
#pragma omp parallel for schedule(static)
692
        for (int64_t m = 0; m < mesh_bins; m++) {
693
          new_bounds(e, m) *= norm_factor;
138✔
694
        }
934✔
695
      }
1,190,071✔
696
    }
697
  } else {
1,189,249✔
698
    // For FW-CADIS, weight windows are inversely proportional to the adjoint
699
    // fluxes. We normalize the weight windows across all energy groups.
1,189,249✔
700
#pragma omp parallel for collapse(2) schedule(static)
101,480✔
701
    for (int e = 0; e < e_bins; e++) {
101,480✔
702
      for (int64_t m = 0; m < mesh_bins; m++) {
101,480✔
703
        // Take the inverse, but are careful not to divide by zero
704
        if (new_bounds(e, m) != 0.0) {
1,087,769✔
705
          new_bounds(e, m) = 1.0 / new_bounds(e, m);
706
        } else {
1,189,249✔
707
          new_bounds(e, m) = 0.0;
345,000✔
708
        }
709
      }
710
    }
711

712
    // Find the maximum value across all elements
713
    double max_val = 0.0;
138✔
714
#pragma omp parallel for collapse(2) schedule(static) reduction(max : max_val)
934✔
715
    for (int e = 0; e < e_bins; e++) {
1,190,071✔
716
      for (int64_t m = 0; m < mesh_bins; m++) {
1,189,249✔
717
        if (new_bounds(e, m) > max_val) {
718
          max_val = new_bounds(e, m);
719
        }
720
      }
250✔
721
    }
722

723
    // Parallel normalization
724
    if (max_val > 0.0) {
725
      double norm_factor = 1.0 / (2.0 * max_val);
1,870✔
726
#pragma omp parallel for collapse(2) schedule(static)
1,716✔
727
      for (int e = 0; e < e_bins; e++) {
728
        for (int64_t m = 0; m < mesh_bins; m++) {
729
          new_bounds(e, m) *= norm_factor;
936✔
730
        }
1,092,505✔
731
      }
1,091,725✔
732
    }
2,520✔
733
  }
734

735
  // Final processing
736
#pragma omp parallel for collapse(2) schedule(static)
737
  for (int e = 0; e < e_bins; e++) {
1,716✔
738
    for (int64_t m = 0; m < mesh_bins; m++) {
1,683✔
739
      // Values where the mean is zero should be ignored
918✔
740
      if (sum(e, m) <= 0.0) {
1,091,590✔
741
        new_bounds(e, m) = -1.0;
1,090,825✔
742
      }
743
      // Values where the relative error is higher than the threshold should be
744
      // ignored
745
      else if (rel_err(e, m) > threshold) {
746
        new_bounds(e, m) = -1.0;
747
      }
748
      // Set the upper bounds
54✔
749
      upper_ww_(e, m) = ratio * lower_ww_(e, m);
84✔
750
    }
97,566✔
751
  }
752
}
97,524✔
753

69,660✔
754
void WeightWindows::check_tally_update_compatibility(const Tally* tally)
755
{
27,864✔
756
  // define the set of allowed filters for the tally
757
  const std::set<FilterType> allowed_filters = {
758
    FilterType::MESH, FilterType::ENERGY, FilterType::PARTICLE};
759

760
  // retrieve a mapping of filter type to filter index for the tally
761
  auto filter_indices = tally->filter_indices();
96✔
762

54✔
763
  // a mesh filter is required for a tally used to update weight windows
84✔
764
  if (!filter_indices.count(FilterType::MESH)) {
97,566✔
765
    fatal_error(
97,524✔
766
      "A mesh filter is required for a tally to update weight window bounds");
405✔
767
  }
768

769
  // ensure the mesh filter is using the same mesh as this weight window object
770
  auto mesh_filter = tally->get_filter<MeshFilter>();
771

772
  // make sure that all of the filters present on the tally are allowed
96✔
773
  for (auto filter_pair : filter_indices) {
66✔
774
    if (allowed_filters.find(filter_pair.first) == allowed_filters.end()) {
36✔
775
      fatal_error(fmt::format("Invalid filter type '{}' found on tally "
60✔
776
                              "used for weight window generation.",
69,690✔
777
        model::tally_filters[tally->filters(filter_pair.second)]->type_str()));
69,660✔
778
    }
779
  }
780

781
  if (mesh_filter->mesh() != mesh_idx_) {
782
    int32_t mesh_filter_id = model::meshes[mesh_filter->mesh()]->id();
783
    int32_t ww_mesh_id = model::meshes[this->mesh_idx_]->id();
784
    fatal_error(fmt::format("Mesh filter {} uses a different mesh ({}) than "
138✔
785
                            "weight window {} mesh ({})",
934✔
786
      mesh_filter->id(), mesh_filter_id, id_, ww_mesh_id));
1,190,071✔
787
  }
788

1,189,249✔
789
  // if an energy filter exists, make sure the energy grid matches that of this
1,087,769✔
790
  // weight window object
791
  if (auto energy_filter = tally->get_filter<EnergyFilter>()) {
792
    std::vector<double> filter_bins = energy_filter->bins();
793
    std::set<double> filter_e_bounds(
101,480✔
794
      energy_filter->bins().begin(), energy_filter->bins().end());
1,420✔
795
    if (filter_e_bounds.size() != energy_bounds().size()) {
796
      fatal_error(
797
        fmt::format("Energy filter {} does not have the same number of energy "
1,189,249✔
798
                    "bounds ({}) as weight window object {} ({})",
799
          energy_filter->id(), filter_e_bounds.size(), id_,
800
          energy_bounds().size()));
250✔
801
    }
802

250✔
803
    for (auto e : energy_bounds()) {
804
      if (filter_e_bounds.count(e) == 0) {
805
        fatal_error(fmt::format(
806
          "Energy bounds of filter {} and weight windows {} do not match",
250✔
807
          energy_filter->id(), id_));
808
      }
809
    }
250✔
810
  }
811
}
812

250✔
UNCOV
813
void WeightWindows::to_hdf5(hid_t group) const
×
814
{
815
  hid_t ww_group = create_group(group, fmt::format("weight_windows_{}", id()));
816

817
  write_dataset(ww_group, "mesh", this->mesh()->id());
818
  write_dataset(
250✔
819
    ww_group, "particle_type", openmc::particle_type_to_str(particle_type_));
820
  write_dataset(ww_group, "energy_bounds", energy_bounds_);
821
  write_dataset(ww_group, "lower_ww_bounds", lower_ww_);
956✔
822
  write_dataset(ww_group, "upper_ww_bounds", upper_ww_);
706✔
UNCOV
823
  write_dataset(ww_group, "survival_ratio", survival_ratio_);
×
824
  write_dataset(ww_group, "max_lower_bound_ratio", max_lb_ratio_);
UNCOV
825
  write_dataset(ww_group, "max_split", max_split_);
×
826
  write_dataset(ww_group, "weight_cutoff", weight_cutoff_);
827

828
  close_group(ww_group);
829
}
250✔
UNCOV
830

×
UNCOV
831
WeightWindowsGenerator::WeightWindowsGenerator(pugi::xml_node node)
×
UNCOV
832
{
×
833
  // read information from the XML node
UNCOV
834
  int32_t mesh_id = std::stoi(get_node_value(node, "mesh"));
×
835
  int32_t mesh_idx = model::mesh_map[mesh_id];
836
  max_realizations_ = std::stoi(get_node_value(node, "max_realizations"));
837

838
  int32_t active_batches = settings::n_batches - settings::n_inactive;
839
  if (max_realizations_ > active_batches) {
250✔
840
    auto msg =
228✔
841
      fmt::format("The maximum number of specified tally realizations ({}) is "
842
                  "greater than the number of active batches ({}).",
228✔
843
        max_realizations_, active_batches);
228✔
UNCOV
844
    warning(msg);
×
UNCOV
845
  }
×
846
  auto tmp_str = get_node_value(node, "particle_type", true, true);
UNCOV
847
  auto particle_type = str_to_particle_type(tmp_str);
×
848

×
849
  update_interval_ = std::stoi(get_node_value(node, "update_interval"));
850
  on_the_fly_ = get_node_value_bool(node, "on_the_fly");
851

2,246✔
852
  std::vector<double> e_bounds;
2,018✔
UNCOV
853
  if (check_for_node(node, "energy_bounds")) {
×
854
    e_bounds = get_node_array<double>(node, "energy_bounds");
UNCOV
855
  } else {
×
856
    int p_type = static_cast<int>(particle_type);
857
    e_bounds.push_back(data::energy_min[p_type]);
858
    e_bounds.push_back(data::energy_max[p_type]);
228✔
859
  }
250✔
860

861
  // set method
132✔
862
  std::string method_string = get_node_value(node, "method");
863
  if (method_string == "magic") {
264✔
864
    method_ = WeightWindowUpdateMethod::MAGIC;
865
    if (settings::solver_type == SolverType::RANDOM_RAY &&
132✔
866
        FlatSourceDomain::adjoint_) {
132✔
867
      fatal_error("Random ray weight window generation with MAGIC cannot be "
264✔
868
                  "done in adjoint mode.");
132✔
869
    }
132✔
870
  } else if (method_string == "fw_cadis") {
132✔
871
    method_ = WeightWindowUpdateMethod::FW_CADIS;
132✔
872
    if (settings::solver_type != SolverType::RANDOM_RAY) {
132✔
873
      fatal_error("FW-CADIS can only be run in random ray solver mode.");
132✔
874
    }
132✔
875
    FlatSourceDomain::adjoint_ = true;
876
  } else {
132✔
877
    fatal_error(fmt::format(
132✔
878
      "Unknown weight window update method '{}' specified", method_string));
879
  }
81✔
880

881
  // parse non-default update parameters if specified
882
  if (check_for_node(node, "update_parameters")) {
81✔
883
    pugi::xml_node params_node = node.child("update_parameters");
81✔
884
    if (check_for_node(params_node, "value"))
81✔
885
      tally_value_ = get_node_value(params_node, "value");
886
    if (check_for_node(params_node, "threshold"))
81✔
887
      threshold_ = std::stod(get_node_value(params_node, "threshold"));
81✔
888
    if (check_for_node(params_node, "ratio")) {
889
      ratio_ = std::stod(get_node_value(params_node, "ratio"));
890
    }
891
  }
29✔
892

16✔
893
  // check update parameter values
16✔
894
  if (tally_value_ != "mean" && tally_value_ != "rel_err") {
81✔
895
    fatal_error(fmt::format("Unsupported tally value '{}' specified for "
81✔
896
                            "weight window generation.",
897
      tally_value_));
81✔
898
  }
81✔
899
  if (threshold_ <= 0.0)
900
    fatal_error(fmt::format("Invalid relative error threshold '{}' (<= 0.0) "
81✔
901
                            "specified for weight window generation",
81✔
902
      ratio_));
22✔
903
  if (ratio_ <= 1.0)
904
    fatal_error(fmt::format("Invalid weight window ratio '{}' (<= 1.0) "
59✔
905
                            "specified for weight window generation"));
59✔
906

59✔
907
  // create a matching weight windows object
908
  auto wws = WeightWindows::create();
909
  ww_idx_ = wws->index();
910
  wws->set_mesh(mesh_idx);
81✔
911
  if (e_bounds.size() > 0)
81✔
912
    wws->set_energy_bounds(e_bounds);
33✔
913
  wws->set_particle_type(particle_type);
33✔
914
  wws->set_defaults();
UNCOV
915
}
×
916

917
void WeightWindowsGenerator::create_tally()
918
{
48✔
919
  const auto& wws = variance_reduction::weight_windows[ww_idx_];
48✔
920

48✔
UNCOV
921
  // create a tally based on the WWG information
×
922
  Tally* ww_tally = Tally::create();
923
  tally_idx_ = model::tally_map[ww_tally->id()];
48✔
924
  ww_tally->set_scores({"flux"});
UNCOV
925

×
926
  int32_t mesh_id = wws->mesh()->id();
927
  int32_t mesh_idx = model::mesh_map.at(mesh_id);
928
  // see if there's already a mesh filter using this mesh
929
  bool found_mesh_filter = false;
930
  for (const auto& f : model::tally_filters) {
81✔
931
    if (f->type() == FilterType::MESH) {
22✔
932
      const auto* mesh_filter = dynamic_cast<MeshFilter*>(f.get());
22✔
933
      if (mesh_filter->mesh() == mesh_idx && !mesh_filter->translated()) {
22✔
934
        ww_tally->add_filter(f.get());
22✔
935
        found_mesh_filter = true;
22✔
936
        break;
22✔
937
      }
22✔
938
    }
939
  }
940

941
  if (!found_mesh_filter) {
942
    auto mesh_filter = Filter::create("mesh");
81✔
UNCOV
943
    openmc_mesh_filter_set_mesh(mesh_filter->index(), model::mesh_map[mesh_id]);
×
944
    ww_tally->add_filter(mesh_filter);
UNCOV
945
  }
×
946

947
  const auto& e_bounds = wws->energy_bounds();
81✔
UNCOV
948
  if (e_bounds.size() > 0) {
×
949
    auto energy_filter = Filter::create("energy");
UNCOV
950
    openmc_energy_filter_set_bins(
×
951
      energy_filter->index(), e_bounds.size(), e_bounds.data());
81✔
UNCOV
952
    ww_tally->add_filter(energy_filter);
×
953
  }
954

955
  // add a particle filter
956
  auto particle_type = wws->particle_type();
81✔
957
  auto particle_filter = Filter::create("particle");
81✔
958
  auto pf = dynamic_cast<ParticleFilter*>(particle_filter);
81✔
959
  pf->set_particles({&particle_type, 1});
81✔
960
  ww_tally->add_filter(particle_filter);
81✔
961
}
81✔
962

81✔
963
void WeightWindowsGenerator::update() const
81✔
964
{
965
  const auto& wws = variance_reduction::weight_windows[ww_idx_];
81✔
966

967
  Tally* tally = model::tallies[tally_idx_].get();
81✔
968

969
  // if we're beyond the number of max realizations or not at the corrrect
970
  // update interval, skip the update
81✔
971
  if (max_realizations_ < tally->n_realizations_ ||
81✔
972
      tally->n_realizations_ % update_interval_ != 0)
162✔
973
    return;
974

81✔
975
  wws->update_weights(tally, tally_value_, threshold_, ratio_, method_);
81✔
976

977
  // if we're not doing on the fly generation, reset the tally results once
81✔
978
  // we're done with the update
258✔
979
  if (!on_the_fly_)
188✔
980
    tally->reset();
11✔
981

11✔
982
  // TODO: deactivate or remove tally once weight window generation is
11✔
983
  // complete
11✔
984
}
11✔
985

986
//==============================================================================
987
// Non-member functions
988
//==============================================================================
989

81✔
990
void finalize_variance_reduction()
70✔
991
{
70✔
992
  for (const auto& wwg : variance_reduction::weight_windows_generators) {
70✔
993
    wwg->create_tally();
994
  }
995
}
81✔
996

81✔
997
//==============================================================================
81✔
998
// C API
162✔
999
//==============================================================================
81✔
1000

81✔
1001
int verify_ww_index(int32_t index)
1002
{
1003
  if (index < 0 || index >= variance_reduction::weight_windows.size()) {
1004
    set_errmsg(fmt::format("Index '{}' for weight windows is invalid", index));
81✔
1005
    return OPENMC_E_OUT_OF_BOUNDS;
81✔
1006
  }
81✔
1007
  return 0;
81✔
1008
}
81✔
1009

81✔
1010
extern "C" int openmc_get_weight_windows_index(int32_t id, int32_t* idx)
1011
{
261✔
1012
  auto it = variance_reduction::ww_map.find(id);
1013
  if (it == variance_reduction::ww_map.end()) {
261✔
1014
    set_errmsg(fmt::format("No weight windows exist with ID={}", id));
1015
    return OPENMC_E_INVALID_ID;
261✔
1016
  }
1017

1018
  *idx = it->second;
1019
  return 0;
261✔
1020
}
129✔
1021

132✔
1022
extern "C" int openmc_weight_windows_get_id(int32_t index, int32_t* id)
1023
{
129✔
1024
  if (int err = verify_ww_index(index))
1025
    return err;
1026

1027
  const auto& wws = variance_reduction::weight_windows.at(index);
129✔
UNCOV
1028
  *id = wws->id();
×
1029
  return 0;
1030
}
1031

1032
extern "C" int openmc_weight_windows_set_id(int32_t index, int32_t id)
1033
{
1034
  if (int err = verify_ww_index(index))
1035
    return err;
1036

1037
  const auto& wws = variance_reduction::weight_windows.at(index);
1038
  wws->set_id(id);
6,680✔
1039
  return 0;
1040
}
6,761✔
1041

81✔
1042
extern "C" int openmc_weight_windows_update_magic(int32_t ww_idx,
1043
  int32_t tally_idx, const char* value, double threshold, double ratio)
6,680✔
1044
{
1045
  if (int err = verify_ww_index(ww_idx))
1046
    return err;
1047

1048
  if (tally_idx < 0 || tally_idx >= model::tallies.size()) {
1049
    set_errmsg(fmt::format("Index '{}' for tally is invalid", tally_idx));
1,991✔
1050
    return OPENMC_E_OUT_OF_BOUNDS;
1051
  }
1,991✔
UNCOV
1052

×
UNCOV
1053
  // get the requested tally
×
1054
  const Tally* tally = model::tallies.at(tally_idx).get();
1055

1,991✔
1056
  // get the WeightWindows object
1057
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
1058

165✔
1059
  wws->update_weights(tally, value, threshold, ratio);
1060

165✔
1061
  return 0;
165✔
UNCOV
1062
}
×
UNCOV
1063

×
1064
extern "C" int openmc_weight_windows_set_mesh(int32_t ww_idx, int32_t mesh_idx)
1065
{
1066
  if (int err = verify_ww_index(ww_idx))
165✔
1067
    return err;
165✔
1068
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
1069
  wws->set_mesh(mesh_idx);
1070
  return 0;
517✔
1071
}
1072

517✔
UNCOV
1073
extern "C" int openmc_weight_windows_get_mesh(int32_t ww_idx, int32_t* mesh_idx)
×
1074
{
1075
  if (int err = verify_ww_index(ww_idx))
517✔
1076
    return err;
517✔
1077
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
517✔
1078
  *mesh_idx = model::mesh_map.at(wws->mesh()->id());
1079
  return 0;
1080
}
154✔
1081

1082
extern "C" int openmc_weight_windows_set_energy_bounds(
154✔
UNCOV
1083
  int32_t ww_idx, double* e_bounds, size_t e_bounds_size)
×
1084
{
1085
  if (int err = verify_ww_index(ww_idx))
154✔
1086
    return err;
154✔
1087
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
154✔
1088
  wws->set_energy_bounds({e_bounds, e_bounds_size});
1089
  return 0;
1090
}
121✔
1091

1092
extern "C" int openmc_weight_windows_get_energy_bounds(
1093
  int32_t ww_idx, const double** e_bounds, size_t* e_bounds_size)
121✔
UNCOV
1094
{
×
1095
  if (int err = verify_ww_index(ww_idx))
1096
    return err;
121✔
1097
  const auto& wws = variance_reduction::weight_windows[ww_idx].get();
×
UNCOV
1098
  *e_bounds = wws->energy_bounds().data();
×
1099
  *e_bounds_size = wws->energy_bounds().size();
1100
  return 0;
1101
}
1102

121✔
1103
extern "C" int openmc_weight_windows_set_particle(int32_t index, int particle)
1104
{
1105
  if (int err = verify_ww_index(index))
121✔
1106
    return err;
1107

121✔
1108
  const auto& wws = variance_reduction::weight_windows.at(index);
1109
  wws->set_particle_type(static_cast<ParticleType>(particle));
121✔
1110
  return 0;
1111
}
1112

154✔
1113
extern "C" int openmc_weight_windows_get_particle(int32_t index, int* particle)
1114
{
154✔
UNCOV
1115
  if (int err = verify_ww_index(index))
×
1116
    return err;
154✔
1117

154✔
1118
  const auto& wws = variance_reduction::weight_windows.at(index);
154✔
1119
  *particle = static_cast<int>(wws->particle_type());
1120
  return 0;
1121
}
11✔
1122

1123
extern "C" int openmc_weight_windows_get_bounds(int32_t index,
11✔
UNCOV
1124
  const double** lower_bounds, const double** upper_bounds, size_t* size)
×
1125
{
11✔
1126
  if (int err = verify_ww_index(index))
11✔
1127
    return err;
11✔
1128

1129
  const auto& wws = variance_reduction::weight_windows[index];
1130
  *size = wws->lower_ww_bounds().size();
132✔
1131
  *lower_bounds = wws->lower_ww_bounds().data();
1132
  *upper_bounds = wws->upper_ww_bounds().data();
1133
  return 0;
132✔
UNCOV
1134
}
×
1135

132✔
1136
extern "C" int openmc_weight_windows_set_bounds(int32_t index,
132✔
1137
  const double* lower_bounds, const double* upper_bounds, size_t size)
132✔
1138
{
1139
  if (int err = verify_ww_index(index))
1140
    return err;
11✔
1141

1142
  const auto& wws = variance_reduction::weight_windows[index];
1143
  wws->set_bounds({lower_bounds, size}, {upper_bounds, size});
11✔
UNCOV
1144
  return 0;
×
1145
}
11✔
1146

11✔
1147
extern "C" int openmc_weight_windows_get_survival_ratio(
11✔
1148
  int32_t index, double* ratio)
11✔
1149
{
1150
  if (int err = verify_ww_index(index))
1151
    return err;
176✔
1152
  const auto& wws = variance_reduction::weight_windows[index];
1153
  *ratio = wws->survival_ratio();
176✔
UNCOV
1154
  return 0;
×
1155
}
1156

176✔
1157
extern "C" int openmc_weight_windows_set_survival_ratio(
176✔
1158
  int32_t index, double ratio)
176✔
1159
{
1160
  if (int err = verify_ww_index(index))
1161
    return err;
44✔
1162
  const auto& wws = variance_reduction::weight_windows[index];
1163
  wws->survival_ratio() = ratio;
44✔
UNCOV
1164
  std::cout << "Survival ratio: " << wws->survival_ratio() << std::endl;
×
1165
  return 0;
1166
}
44✔
1167

44✔
1168
extern "C" int openmc_weight_windows_get_max_lower_bound_ratio(
44✔
1169
  int32_t index, double* lb_ratio)
1170
{
1171
  if (int err = verify_ww_index(index))
484✔
1172
    return err;
1173
  const auto& wws = variance_reduction::weight_windows[index];
1174
  *lb_ratio = wws->max_lower_bound_ratio();
484✔
UNCOV
1175
  return 0;
×
1176
}
1177

484✔
1178
extern "C" int openmc_weight_windows_set_max_lower_bound_ratio(
484✔
1179
  int32_t index, double lb_ratio)
484✔
1180
{
484✔
1181
  if (int err = verify_ww_index(index))
484✔
1182
    return err;
1183
  const auto& wws = variance_reduction::weight_windows[index];
1184
  wws->max_lower_bound_ratio() = lb_ratio;
11✔
1185
  return 0;
1186
}
1187

11✔
UNCOV
1188
extern "C" int openmc_weight_windows_get_weight_cutoff(
×
1189
  int32_t index, double* cutoff)
1190
{
11✔
1191
  if (int err = verify_ww_index(index))
11✔
1192
    return err;
11✔
1193
  const auto& wws = variance_reduction::weight_windows[index];
1194
  *cutoff = wws->weight_cutoff();
1195
  return 0;
33✔
1196
}
1197

1198
extern "C" int openmc_weight_windows_set_weight_cutoff(
33✔
UNCOV
1199
  int32_t index, double cutoff)
×
1200
{
33✔
1201
  if (int err = verify_ww_index(index))
33✔
1202
    return err;
33✔
1203
  const auto& wws = variance_reduction::weight_windows[index];
1204
  wws->weight_cutoff() = cutoff;
1205
  return 0;
11✔
1206
}
1207

1208
extern "C" int openmc_weight_windows_get_max_split(
11✔
UNCOV
1209
  int32_t index, int* max_split)
×
1210
{
11✔
1211
  if (int err = verify_ww_index(index))
11✔
1212
    return err;
11✔
1213
  const auto& wws = variance_reduction::weight_windows[index];
11✔
1214
  *max_split = wws->max_split();
1215
  return 0;
1216
}
33✔
1217

1218
extern "C" int openmc_weight_windows_set_max_split(int32_t index, int max_split)
1219
{
33✔
UNCOV
1220
  if (int err = verify_ww_index(index))
×
1221
    return err;
33✔
1222
  const auto& wws = variance_reduction::weight_windows[index];
33✔
1223
  wws->max_split() = max_split;
33✔
1224
  return 0;
1225
}
1226

11✔
1227
extern "C" int openmc_extend_weight_windows(
1228
  int32_t n, int32_t* index_start, int32_t* index_end)
1229
{
11✔
UNCOV
1230
  if (index_start)
×
1231
    *index_start = variance_reduction::weight_windows.size();
11✔
1232
  if (index_end)
11✔
1233
    *index_end = variance_reduction::weight_windows.size() + n - 1;
11✔
1234
  for (int i = 0; i < n; ++i)
1235
    variance_reduction::weight_windows.push_back(make_unique<WeightWindows>());
1236
  return 0;
33✔
1237
}
1238

1239
extern "C" size_t openmc_weight_windows_size()
33✔
UNCOV
1240
{
×
1241
  return variance_reduction::weight_windows.size();
33✔
1242
}
33✔
1243

33✔
1244
extern "C" int openmc_weight_windows_export(const char* filename)
1245
{
1246

11✔
1247
  if (!mpi::master)
1248
    return 0;
1249

11✔
UNCOV
1250
  std::string name = filename ? filename : "weight_windows.h5";
×
1251

11✔
1252
  write_message(fmt::format("Exporting weight windows to {}...", name), 5);
11✔
1253

11✔
1254
  hid_t ww_file = file_open(name, 'w');
1255

1256
  // Write file type
33✔
1257
  write_attribute(ww_file, "filetype", "weight_windows");
1258

1259
  // Write revisiion number for state point file
33✔
UNCOV
1260
  write_attribute(ww_file, "version", VERSION_WEIGHT_WINDOWS);
×
1261

33✔
1262
  hid_t weight_windows_group = create_group(ww_file, "weight_windows");
33✔
1263

33✔
1264
  hid_t mesh_group = create_group(ww_file, "meshes");
1265

1266
  std::vector<int32_t> mesh_ids;
11✔
1267
  std::vector<int32_t> ww_ids;
1268
  for (const auto& ww : variance_reduction::weight_windows) {
11✔
UNCOV
1269

×
1270
    ww->to_hdf5(weight_windows_group);
11✔
1271
    ww_ids.push_back(ww->id());
11✔
1272

11✔
1273
    // if the mesh has already been written, move on
1274
    int32_t mesh_id = ww->mesh()->id();
1275
    if (std::find(mesh_ids.begin(), mesh_ids.end(), mesh_id) != mesh_ids.end())
154✔
1276
      continue;
1277

1278
    mesh_ids.push_back(mesh_id);
154✔
1279
    ww->mesh()->to_hdf5(mesh_group);
154✔
1280
  }
154✔
UNCOV
1281

×
1282
  write_attribute(mesh_group, "n_meshes", mesh_ids.size());
308✔
1283
  write_attribute(mesh_group, "ids", mesh_ids);
154✔
1284
  close_group(mesh_group);
154✔
1285

1286
  write_attribute(weight_windows_group, "n_weight_windows", ww_ids.size());
1287
  write_attribute(weight_windows_group, "ids", ww_ids);
154✔
1288
  close_group(weight_windows_group);
1289

154✔
1290
  file_close(ww_file);
1291

1292
  return 0;
162✔
1293
}
1294

1295
extern "C" int openmc_weight_windows_import(const char* filename)
162✔
1296
{
30✔
1297
  std::string name = filename ? filename : "weight_windows.h5";
1298

264✔
1299
  if (mpi::master)
1300
    write_message(fmt::format("Importing weight windows from {}...", name), 5);
132✔
1301

1302
  if (!file_exists(name)) {
132✔
1303
    set_errmsg(fmt::format("File '{}' does not exist", name));
1304
  }
1305

132✔
1306
  hid_t ww_file = file_open(name, 'r');
1307

1308
  // Check that filetype is correct
132✔
1309
  std::string filetype;
1310
  read_attribute(ww_file, "filetype", filetype);
132✔
1311
  if (filetype != "weight_windows") {
1312
    file_close(ww_file);
132✔
1313
    set_errmsg(fmt::format("File '{}' is not a weight windows file.", name));
1314
    return OPENMC_E_INVALID_ARGUMENT;
132✔
1315
  }
132✔
1316

264✔
1317
  // Check that the file version is compatible
1318
  std::array<int, 2> file_version;
132✔
1319
  read_attribute(ww_file, "version", file_version);
132✔
1320
  if (file_version[0] != VERSION_WEIGHT_WINDOWS[0]) {
1321
    std::string err_msg =
1322
      fmt::format("File '{}' has version {} which is incompatible with the "
132✔
1323
                  "expected version ({}).",
132✔
UNCOV
1324
        name, file_version, VERSION_WEIGHT_WINDOWS);
×
1325
    set_errmsg(err_msg);
1326
    return OPENMC_E_INVALID_ARGUMENT;
132✔
1327
  }
132✔
1328

1329
  hid_t weight_windows_group = open_group(ww_file, "weight_windows");
1330

132✔
1331
  std::vector<std::string> names = group_names(weight_windows_group);
132✔
1332

132✔
1333
  for (const auto& name : names) {
1334
    WeightWindows::from_hdf5(weight_windows_group, name);
132✔
1335
  }
132✔
1336

132✔
1337
  close_group(weight_windows_group);
1338

132✔
1339
  file_close(ww_file);
1340

132✔
1341
  return 0;
132✔
1342
}
1343

11✔
1344
} // 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