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

openmc-dev / openmc / 21597126053

02 Feb 2026 03:54PM UTC coverage: 81.836% (-0.1%) from 81.968%
21597126053

Pull #3751

github

web-flow
Merge cb1ee5c95 into 6041ee6ae
Pull Request #3751: Resolve conflict with weight windows and global russian roulette

17239 of 24046 branches covered (71.69%)

Branch coverage included in aggregate %.

77 of 84 new or added lines in 5 files covered. (91.67%)

164 existing lines in 7 files now uncovered.

55736 of 65126 relevant lines covered (85.58%)

34235017.69 hits per line

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

78.72
/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/simulation.h"
30
#include "openmc/tallies/filter_energy.h"
31
#include "openmc/tallies/filter_mesh.h"
32
#include "openmc/tallies/filter_particle.h"
33
#include "openmc/tallies/tally.h"
34
#include "openmc/xml_interface.h"
35

36
#include <fmt/core.h>
37

38
namespace openmc {
39

40
//==============================================================================
41
// Global variables
42
//==============================================================================
43

44
namespace variance_reduction {
45

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

50
} // namespace variance_reduction
51

52
//==============================================================================
53
// WeightWindowSettings implementation
54
//==============================================================================
55

56
WeightWindows::WeightWindows(int32_t id)
160✔
57
{
58
  index_ = variance_reduction::weight_windows.size();
160✔
59
  set_id(id);
160✔
60
  set_defaults();
160✔
61
}
160✔
62

63
WeightWindows::WeightWindows(pugi::xml_node node)
61✔
64
{
65
  // Make sure required elements are present
66
  const vector<std::string> required_elems {
67
    "id", "particle_type", "lower_ww_bounds", "upper_ww_bounds"};
427✔
68
  for (const auto& elem : required_elems) {
305✔
69
    if (!check_for_node(node, elem.c_str())) {
244!
70
      fatal_error(fmt::format("Must specify <{}> for weight windows.", elem));
×
71
    }
72
  }
73

74
  // Get weight windows ID
75
  int32_t id = std::stoi(get_node_value(node, "id"));
61✔
76
  this->set_id(id);
61✔
77

78
  // get the particle type
79
  auto particle_type_str = std::string(get_node_value(node, "particle_type"));
61✔
80
  particle_type_ = openmc::str_to_particle_type(particle_type_str);
61✔
81

82
  // Determine associated mesh
83
  int32_t mesh_id = std::stoi(get_node_value(node, "mesh"));
61✔
84
  set_mesh(model::mesh_map.at(mesh_id));
61✔
85

86
  // energy bounds
87
  if (check_for_node(node, "energy_bounds"))
61✔
88
    energy_bounds_ = get_node_array<double>(node, "energy_bounds");
51✔
89

90
  // get the survival value - optional
91
  if (check_for_node(node, "survival_ratio")) {
61!
92
    survival_ratio_ = std::stod(get_node_value(node, "survival_ratio"));
61✔
93
    if (survival_ratio_ <= 1)
61!
94
      fatal_error("Survival to lower weight window ratio must bigger than 1 "
×
95
                  "and less than the upper to lower weight window ratio.");
96
  }
97

98
  // get the max lower bound ratio - optional
99
  if (check_for_node(node, "max_lower_bound_ratio")) {
61✔
100
    max_lb_ratio_ = std::stod(get_node_value(node, "max_lower_bound_ratio"));
24✔
101
    if (max_lb_ratio_ < 1.0) {
24!
102
      fatal_error("Maximum lower bound ratio must be larger than 1");
×
103
    }
104
  }
105

106
  // get the max split - optional
107
  if (check_for_node(node, "max_split")) {
61!
108
    max_split_ = std::stod(get_node_value(node, "max_split"));
61✔
109
    if (max_split_ <= 1)
61!
110
      fatal_error("max split must be larger than 1");
×
111
  }
112

113
  // weight cutoff - optional
114
  if (check_for_node(node, "weight_cutoff")) {
61!
115
    weight_cutoff_ = std::stod(get_node_value(node, "weight_cutoff"));
61✔
116
    if (weight_cutoff_ <= 0)
61!
117
      fatal_error("weight_cutoff must be larger than 0");
×
118
    if (weight_cutoff_ > 1)
61!
119
      fatal_error("weight_cutoff must be less than 1");
×
120
  }
121

122
  // read the lower/upper weight bounds
123
  this->set_bounds(get_node_array<double>(node, "lower_ww_bounds"),
61✔
124
    get_node_array<double>(node, "upper_ww_bounds"));
122✔
125

126
  set_defaults();
61✔
127
}
61✔
128

129
WeightWindows::~WeightWindows()
221✔
130
{
131
  variance_reduction::ww_map.erase(id());
221✔
132
}
221✔
133

134
WeightWindows* WeightWindows::create(int32_t id)
62✔
135
{
136
  variance_reduction::weight_windows.push_back(make_unique<WeightWindows>());
62✔
137
  auto wws = variance_reduction::weight_windows.back().get();
62✔
138
  variance_reduction::ww_map[wws->id()] =
62✔
139
    variance_reduction::weight_windows.size() - 1;
62✔
140
  return wws;
62✔
141
}
142

143
WeightWindows* WeightWindows::from_hdf5(
7✔
144
  hid_t wws_group, const std::string& group_name)
145
{
146
  // collect ID from the name of this group
147
  hid_t ww_group = open_group(wws_group, group_name);
7✔
148

149
  auto wws = WeightWindows::create();
7✔
150

151
  std::string particle_type;
7✔
152
  read_dataset(ww_group, "particle_type", particle_type);
7✔
153
  wws->particle_type_ = openmc::str_to_particle_type(particle_type);
7✔
154

155
  read_dataset<double>(ww_group, "energy_bounds", wws->energy_bounds_);
7✔
156

157
  int32_t mesh_id;
158
  read_dataset(ww_group, "mesh", mesh_id);
7✔
159

160
  if (model::mesh_map.count(mesh_id) == 0) {
7!
161
    fatal_error(
×
162
      fmt::format("Mesh {} used in weight windows does not exist.", mesh_id));
×
163
  }
164
  wws->set_mesh(model::mesh_map[mesh_id]);
7✔
165

166
  wws->lower_ww_ = xt::empty<double>(wws->bounds_size());
7✔
167
  wws->upper_ww_ = xt::empty<double>(wws->bounds_size());
7✔
168

169
  read_dataset<double>(ww_group, "lower_ww_bounds", wws->lower_ww_);
7✔
170
  read_dataset<double>(ww_group, "upper_ww_bounds", wws->upper_ww_);
7✔
171
  read_dataset(ww_group, "survival_ratio", wws->survival_ratio_);
7✔
172
  read_dataset(ww_group, "max_lower_bound_ratio", wws->max_lb_ratio_);
7✔
173
  read_dataset(ww_group, "max_split", wws->max_split_);
7✔
174
  read_dataset(ww_group, "weight_cutoff", wws->weight_cutoff_);
7✔
175

176
  close_group(ww_group);
7✔
177

178
  return wws;
7✔
179
}
7✔
180

181
void WeightWindows::set_defaults()
276✔
182
{
183
  // set energy bounds to the min/max energy supported by the data
184
  if (energy_bounds_.size() == 0) {
276✔
185
    int p_type = static_cast<int>(particle_type_);
170✔
186
    energy_bounds_.push_back(data::energy_min[p_type]);
170✔
187
    energy_bounds_.push_back(data::energy_max[p_type]);
170✔
188
  }
189
}
276✔
190

191
void WeightWindows::allocate_ww_bounds()
360✔
192
{
193
  auto shape = bounds_size();
360✔
194
  if (shape[0] * shape[1] == 0) {
360!
195
    auto msg = fmt::format(
196
      "Size of weight window bounds is zero for WeightWindows {}", id());
×
197
    warning(msg);
×
198
  }
×
199
  lower_ww_ = xt::empty<double>(shape);
360✔
200
  lower_ww_.fill(-1);
360✔
201
  upper_ww_ = xt::empty<double>(shape);
360✔
202
  upper_ww_.fill(-1);
360✔
203
}
360✔
204

205
void WeightWindows::set_id(int32_t id)
319✔
206
{
207
  assert(id >= 0 || id == C_NONE);
271!
208

209
  // Clear entry in mesh map in case one was already assigned
210
  if (id_ != C_NONE) {
319!
211
    variance_reduction::ww_map.erase(id_);
319✔
212
    id_ = C_NONE;
319✔
213
  }
214

215
  // Ensure no other mesh has the same ID
216
  if (variance_reduction::ww_map.find(id) != variance_reduction::ww_map.end()) {
319!
217
    throw std::runtime_error {
×
218
      fmt::format("Two weight windows have the same ID: {}", id)};
×
219
  }
220

221
  // If no ID is specified, auto-assign the next ID in the sequence
222
  if (id == C_NONE) {
319✔
223
    id = 0;
160✔
224
    for (const auto& m : variance_reduction::weight_windows) {
174✔
225
      id = std::max(id, m->id_);
14✔
226
    }
227
    ++id;
160✔
228
  }
229

230
  // Update ID and entry in the mesh map
231
  id_ = id;
319✔
232
  variance_reduction::ww_map[id] = index_;
319✔
233
}
319✔
234

235
void WeightWindows::set_energy_bounds(span<const double> bounds)
139✔
236
{
237
  energy_bounds_.clear();
139✔
238
  energy_bounds_.insert(energy_bounds_.begin(), bounds.begin(), bounds.end());
139✔
239
  // if the mesh is set, allocate space for weight window bounds
240
  if (mesh_idx_ != C_NONE)
139!
241
    allocate_ww_bounds();
139✔
242
}
139✔
243

244
void WeightWindows::set_particle_type(ParticleType p_type)
167✔
245
{
246
  if (p_type != ParticleType::neutron && p_type != ParticleType::photon)
167!
247
    fatal_error(
×
248
      fmt::format("Particle type '{}' cannot be applied to weight windows.",
×
249
        particle_type_to_str(p_type)));
×
250
  particle_type_ = p_type;
167✔
251
}
167✔
252

253
void WeightWindows::set_mesh(int32_t mesh_idx)
221✔
254
{
255
  if (mesh_idx < 0 || mesh_idx >= model::meshes.size())
221!
256
    fatal_error(fmt::format("Could not find a mesh for index {}", mesh_idx));
×
257

258
  mesh_idx_ = mesh_idx;
221✔
259
  model::meshes[mesh_idx_]->prepare_for_point_location();
221✔
260
  allocate_ww_bounds();
221✔
261
}
221✔
262

263
void WeightWindows::set_mesh(const std::unique_ptr<Mesh>& mesh)
×
264
{
265
  set_mesh(mesh.get());
×
266
}
×
267

268
void WeightWindows::set_mesh(const Mesh* mesh)
×
269
{
270
  set_mesh(model::mesh_map[mesh->id_]);
×
271
}
×
272

273
const int WeightWindows::get_mesh_bin(const Particle& p) const
57,902,647✔
274
{
275
  // check for particle type
276
  if (particle_type_ != p.type())
57,902,647✔
277
    return C_NONE;
12,542,407✔
278

279
  // particle energy
280
  double E = p.E();
45,360,240✔
281

282
  // check to make sure energy is in range, expects sorted energy values
283
  if (E < energy_bounds_.front() || E > energy_bounds_.back())
45,360,240!
284
    return C_NONE;
59,066✔
285

286
  // Get mesh index for particle's position
287
  const auto& mesh = this->mesh();
45,301,174✔
288
  return mesh->get_bin(p.r());
45,301,174✔
289
}
290

UNCOV
291
WeightWindow WeightWindows::get_weight_window(const Particle& p) const
×
292
{
UNCOV
293
  return get_weight_window(p.E(), get_mesh_bin(p));
×
294
}
295

296
WeightWindow WeightWindows::get_weight_window(
45,286,340✔
297
  double E, const int mesh_bin) const
298
{
299
  // particle is outside the weight window mesh
300
  if (mesh_bin < 0)
45,286,340!
UNCOV
301
    return {};
×
302

303
  // get the mesh bin in energy group
304
  int energy_bin =
305
    lower_bound_index(energy_bounds_.begin(), energy_bounds_.end(), E);
45,286,340✔
306

307
  // mesh_bin += energy_bin * mesh->n_bins();
308
  // Create individual weight window
309
  WeightWindow ww;
45,286,340✔
310
  ww.lower_weight = lower_ww_(energy_bin, mesh_bin);
45,286,340✔
311
  ww.upper_weight = upper_ww_(energy_bin, mesh_bin);
45,286,340✔
312
  ww.survival_weight = ww.lower_weight * survival_ratio_;
45,286,340✔
313
  ww.max_lb_ratio = max_lb_ratio_;
45,286,340✔
314
  ww.max_split = max_split_;
45,286,340✔
315
  ww.weight_cutoff = weight_cutoff_;
45,286,340✔
316
  return ww;
45,286,340✔
317
}
318

319
std::array<int, 2> WeightWindows::bounds_size() const
510✔
320
{
321
  int num_spatial_bins = this->mesh()->n_bins();
510✔
322
  int num_energy_bins =
323
    energy_bounds_.size() > 0 ? energy_bounds_.size() - 1 : 1;
510✔
324
  return {num_energy_bins, num_spatial_bins};
510✔
325
}
326

327
template<class T>
328
void WeightWindows::check_bounds(const T& lower, const T& upper) const
68✔
329
{
330
  // make sure that the upper and lower bounds have the same size
331
  if (lower.size() != upper.size()) {
68!
UNCOV
332
    auto msg = fmt::format("The upper and lower weight window lengths do not "
×
333
                           "match.\n Lower size: {}\n Upper size: {}",
UNCOV
334
      lower.size(), upper.size());
×
UNCOV
335
    fatal_error(msg);
×
UNCOV
336
  }
×
337
  this->check_bounds(lower);
68✔
338
}
68✔
339

340
template<class T>
341
void WeightWindows::check_bounds(const T& bounds) const
68✔
342
{
343
  // check that the number of weight window entries is correct
344
  auto dims = this->bounds_size();
68✔
345
  if (bounds.size() != dims[0] * dims[1]) {
68!
UNCOV
346
    auto err_msg =
×
347
      fmt::format("In weight window domain {} the number of spatial "
348
                  "energy/spatial bins ({}) does not match the number "
349
                  "of weight bins ({})",
UNCOV
350
        id_, dims, bounds.size());
×
351
    fatal_error(err_msg);
×
UNCOV
352
  }
×
353
}
68✔
354

355
void WeightWindows::set_bounds(const xt::xtensor<double, 2>& lower_bounds,
×
356
  const xt::xtensor<double, 2>& upper_bounds)
357
{
358

UNCOV
359
  this->check_bounds(lower_bounds, upper_bounds);
×
360

361
  // set new weight window values
UNCOV
362
  lower_ww_ = lower_bounds;
×
UNCOV
363
  upper_ww_ = upper_bounds;
×
364
}
×
365

366
void WeightWindows::set_bounds(
×
367
  const xt::xtensor<double, 2>& lower_bounds, double ratio)
368
{
UNCOV
369
  this->check_bounds(lower_bounds);
×
370

371
  // set new weight window values
UNCOV
372
  lower_ww_ = lower_bounds;
×
UNCOV
373
  upper_ww_ = lower_bounds;
×
UNCOV
374
  upper_ww_ *= ratio;
×
UNCOV
375
}
×
376

377
void WeightWindows::set_bounds(
68✔
378
  span<const double> lower_bounds, span<const double> upper_bounds)
379
{
380
  check_bounds(lower_bounds, upper_bounds);
68✔
381
  auto shape = this->bounds_size();
68✔
382
  lower_ww_ = xt::empty<double>(shape);
68✔
383
  upper_ww_ = xt::empty<double>(shape);
68✔
384

385
  // set new weight window values
386
  xt::view(lower_ww_, xt::all()) =
136✔
387
    xt::adapt(lower_bounds.data(), lower_ww_.shape());
204✔
388
  xt::view(upper_ww_, xt::all()) =
136✔
389
    xt::adapt(upper_bounds.data(), upper_ww_.shape());
204✔
390
}
68✔
391

UNCOV
392
void WeightWindows::set_bounds(span<const double> lower_bounds, double ratio)
×
393
{
394
  this->check_bounds(lower_bounds);
×
395

396
  auto shape = this->bounds_size();
×
397
  lower_ww_ = xt::empty<double>(shape);
×
398
  upper_ww_ = xt::empty<double>(shape);
×
399

400
  // set new weight window values
UNCOV
401
  xt::view(lower_ww_, xt::all()) =
×
UNCOV
402
    xt::adapt(lower_bounds.data(), lower_ww_.shape());
×
UNCOV
403
  xt::view(upper_ww_, xt::all()) =
×
UNCOV
404
    xt::adapt(lower_bounds.data(), upper_ww_.shape());
×
UNCOV
405
  upper_ww_ *= ratio;
×
UNCOV
406
}
×
407

408
void WeightWindows::update_weights(const Tally* tally, const std::string& value,
166✔
409
  double threshold, double ratio, WeightWindowUpdateMethod method)
410
{
411
  ///////////////////////////
412
  // Setup and checks
413
  ///////////////////////////
414
  this->check_tally_update_compatibility(tally);
166✔
415

416
  // Dimensions of weight window arrays
417
  int e_bins = lower_ww_.shape()[0];
166✔
418
  int64_t mesh_bins = lower_ww_.shape()[1];
166✔
419

420
  // Initialize weight window arrays to -1.0 by default
421
#pragma omp parallel for collapse(2) schedule(static)
74✔
422
  for (int e = 0; e < e_bins; e++) {
752✔
423
    for (int64_t m = 0; m < mesh_bins; m++) {
957,632✔
424
      lower_ww_(e, m) = -1.0;
956,972✔
425
      upper_ww_(e, m) = -1.0;
956,972✔
426
    }
427
  }
428

429
  // determine which value to use
430
  const std::set<std::string> allowed_values = {"mean", "rel_err"};
830✔
431
  if (allowed_values.count(value) == 0) {
166!
432
    fatal_error(fmt::format("Invalid value '{}' specified for weight window "
×
433
                            "generation. Must be one of: 'mean' or 'rel_err'",
434
      value));
435
  }
436

437
  // determine the index of the specified score
438
  int score_index = tally->score_index("flux");
166✔
439
  if (score_index == C_NONE) {
166!
UNCOV
440
    fatal_error(
×
UNCOV
441
      fmt::format("A 'flux' score required for weight window generation "
×
442
                  "is not present on tally {}.",
UNCOV
443
        tally->id()));
×
444
  }
445

446
  ///////////////////////////
447
  // Extract tally data
448
  //
449
  // At the end of this section, the mean and rel_err array
450
  // is a 2D view of tally data (n_e_groups, n_mesh_bins)
451
  //
452
  ///////////////////////////
453

454
  // build a shape for a view of the tally results, this will always be
455
  // dimension 5 (3 filter dimensions, 1 score dimension, 1 results dimension)
456
  // Look for the size of the last dimension of the results array
457
  const auto& results_arr = tally->results();
166✔
458
  const int results_dim = static_cast<int>(results_arr.shape()[2]);
166✔
459
  std::array<int, 5> shape = {1, 1, 1, tally->n_scores(), results_dim};
166✔
460

461
  // set the shape for the filters applied on the tally
462
  for (int i = 0; i < tally->filters().size(); i++) {
636✔
463
    const auto& filter = model::tally_filters[tally->filters(i)];
470✔
464
    shape[i] = filter->n_bins();
470✔
465
  }
466

467
  // build the transpose information to re-order data according to filter type
468
  std::array<int, 5> transpose = {0, 1, 2, 3, 4};
166✔
469

470
  // track our filter types and where we've added new ones
471
  std::vector<FilterType> filter_types = tally->filter_types();
166✔
472

473
  // assign other filter types to dummy positions if needed
474
  if (!tally->has_filter(FilterType::PARTICLE))
166✔
475
    filter_types.push_back(FilterType::PARTICLE);
14✔
476

477
  if (!tally->has_filter(FilterType::ENERGY))
166✔
478
    filter_types.push_back(FilterType::ENERGY);
14✔
479

480
  // particle axis mapping
481
  transpose[0] =
166✔
482
    std::find(filter_types.begin(), filter_types.end(), FilterType::PARTICLE) -
166✔
483
    filter_types.begin();
166✔
484

485
  // energy axis mapping
486
  transpose[1] =
166✔
487
    std::find(filter_types.begin(), filter_types.end(), FilterType::ENERGY) -
166✔
488
    filter_types.begin();
166✔
489

490
  // mesh axis mapping
491
  transpose[2] =
166✔
492
    std::find(filter_types.begin(), filter_types.end(), FilterType::MESH) -
166✔
493
    filter_types.begin();
166✔
494

495
  // get a fully reshaped view of the tally according to tally ordering of
496
  // filters
497
  auto tally_values = xt::reshape_view(results_arr, shape);
166✔
498

499
  // get a that is (particle, energy, mesh, scores, values)
500
  auto transposed_view = xt::transpose(tally_values, transpose);
166✔
501

502
  // determine the dimension and index of the particle data
503
  int particle_idx = 0;
166✔
504
  if (tally->has_filter(FilterType::PARTICLE)) {
166✔
505
    // get the particle filter
506
    auto pf = tally->get_filter<ParticleFilter>();
152✔
507
    const auto& particles = pf->particles();
152✔
508

509
    // find the index of the particle that matches these weight windows
510
    auto p_it =
511
      std::find(particles.begin(), particles.end(), this->particle_type_);
152✔
512
    // if the particle filter doesn't have particle data for the particle
513
    // used on this weight windows instance, report an error
514
    if (p_it == particles.end()) {
152!
515
      auto msg = fmt::format("Particle type '{}' not present on Filter {} for "
516
                             "Tally {} used to update WeightWindows {}",
UNCOV
517
        particle_type_to_str(this->particle_type_), pf->id(), tally->id(),
×
UNCOV
518
        this->id());
×
UNCOV
519
      fatal_error(msg);
×
UNCOV
520
    }
×
521

522
    // use the index of the particle in the filter to down-select data later
523
    particle_idx = p_it - particles.begin();
152✔
524
  }
525

526
  // down-select data based on particle and score
527
  auto sum = xt::dynamic_view(
830✔
528
    transposed_view, {particle_idx, xt::all(), xt::all(), score_index,
332✔
529
                       static_cast<int>(TallyResult::SUM)});
664✔
530
  auto sum_sq = xt::dynamic_view(
830✔
531
    transposed_view, {particle_idx, xt::all(), xt::all(), score_index,
332✔
532
                       static_cast<int>(TallyResult::SUM_SQ)});
664✔
533
  int n = tally->n_realizations_;
166✔
534

535
  //////////////////////////////////////////////
536
  //
537
  // Assign new weight windows
538
  //
539
  // Use references to the existing weight window data
540
  // to store and update the values
541
  //
542
  //////////////////////////////////////////////
543

544
  // up to this point the data arrays are views into the tally results (no
545
  // computation has been performed) now we'll switch references to the tally's
546
  // bounds to avoid allocating additional memory
547
  auto& new_bounds = this->lower_ww_;
166✔
548
  auto& rel_err = this->upper_ww_;
166✔
549

550
  // get mesh volumes
551
  auto mesh_vols = this->mesh()->volumes();
166✔
552

553
  // Calculate mean (new_bounds) and relative error
554
#pragma omp parallel for collapse(2) schedule(static)
74✔
555
  for (int e = 0; e < e_bins; e++) {
752✔
556
    for (int64_t m = 0; m < mesh_bins; m++) {
957,632✔
557
      // Calculate mean
558
      new_bounds(e, m) = sum(e, m) / n;
956,972✔
559
      // Calculate relative error
560
      if (sum(e, m) > 0.0) {
956,972✔
561
        double mean_val = new_bounds(e, m);
81,184✔
562
        double variance = (sum_sq(e, m) / n - mean_val * mean_val) / (n - 1);
81,184✔
563
        rel_err(e, m) = std::sqrt(variance) / mean_val;
81,184✔
564
      } else {
565
        rel_err(e, m) = INFTY;
875,788✔
566
      }
567
      if (value == "rel_err") {
956,972✔
568
        new_bounds(e, m) = 1.0 / rel_err(e, m);
276,000✔
569
      }
570
    }
571
  }
572

573
  // Divide by volume of mesh elements
574
#pragma omp parallel for collapse(2) schedule(static)
74✔
575
  for (int e = 0; e < e_bins; e++) {
752✔
576
    for (int64_t m = 0; m < mesh_bins; m++) {
957,632✔
577
      new_bounds(e, m) /= mesh_vols[m];
956,972✔
578
    }
579
  }
580

581
  if (method == WeightWindowUpdateMethod::MAGIC) {
166✔
582
    // For MAGIC, weight windows are proportional to the forward fluxes.
583
    // We normalize weight windows independently for each energy group.
584

585
    // Find group maximum and normalize (per energy group)
586
    for (int e = 0; e < e_bins; e++) {
1,190✔
587
      double group_max = 0.0;
1,092✔
588

589
      // Find maximum value across all elements in this energy group
590
#pragma omp parallel for schedule(static) reduction(max : group_max)
468✔
591
      for (int64_t m = 0; m < mesh_bins; m++) {
874,004✔
592
        if (new_bounds(e, m) > group_max) {
873,380✔
593
          group_max = new_bounds(e, m);
2,016✔
594
        }
595
      }
596

597
      // Normalize values in this energy group by the maximum value
598
      if (group_max > 0.0) {
1,092✔
599
        double norm_factor = 1.0 / (2.0 * group_max);
1,071✔
600
#pragma omp parallel for schedule(static)
459✔
601
        for (int64_t m = 0; m < mesh_bins; m++) {
873,272✔
602
          new_bounds(e, m) *= norm_factor;
872,660✔
603
        }
604
      }
605
    }
606
  } else {
607
    // For FW-CADIS, weight windows are inversely proportional to the adjoint
608
    // fluxes. We normalize the weight windows across all energy groups.
609
#pragma omp parallel for collapse(2) schedule(static)
32✔
610
    for (int e = 0; e < e_bins; e++) {
72✔
611
      for (int64_t m = 0; m < mesh_bins; m++) {
83,628✔
612
        // Take the inverse, but are careful not to divide by zero
613
        if (new_bounds(e, m) != 0.0) {
83,592✔
614
          new_bounds(e, m) = 1.0 / new_bounds(e, m);
55,728✔
615
        } else {
616
          new_bounds(e, m) = 0.0;
27,864!
617
        }
618
      }
619
    }
620

621
    // Find the maximum value across all elements
622
    double max_val = 0.0;
68✔
623
#pragma omp parallel for collapse(2) schedule(static) reduction(max : max_val)
32✔
624
    for (int e = 0; e < e_bins; e++) {
72✔
625
      for (int64_t m = 0; m < mesh_bins; m++) {
83,628✔
626
        if (new_bounds(e, m) > max_val) {
83,592✔
627
          max_val = new_bounds(e, m);
324✔
628
        }
629
      }
630
    }
631

632
    // Parallel normalization
633
    if (max_val > 0.0) {
68✔
634
      double norm_factor = 1.0 / (2.0 * max_val);
44✔
635
#pragma omp parallel for collapse(2) schedule(static)
20✔
636
      for (int e = 0; e < e_bins; e++) {
48✔
637
        for (int64_t m = 0; m < mesh_bins; m++) {
55,752✔
638
          new_bounds(e, m) *= norm_factor;
55,728✔
639
        }
640
      }
641
    }
642
  }
643

644
  // Final processing
645
#pragma omp parallel for collapse(2) schedule(static)
74✔
646
  for (int e = 0; e < e_bins; e++) {
752✔
647
    for (int64_t m = 0; m < mesh_bins; m++) {
957,632✔
648
      // Values where the mean is zero should be ignored
649
      if (sum(e, m) <= 0.0) {
956,972✔
650
        new_bounds(e, m) = -1.0;
875,788✔
651
      }
652
      // Values where the relative error is higher than the threshold should be
653
      // ignored
654
      else if (rel_err(e, m) > threshold) {
81,184✔
655
        new_bounds(e, m) = -1.0;
1,136✔
656
      }
657
      // Set the upper bounds
658
      upper_ww_(e, m) = ratio * lower_ww_(e, m);
956,972✔
659
    }
660
  }
661
}
166✔
662

663
void WeightWindows::check_tally_update_compatibility(const Tally* tally)
166✔
664
{
665
  // define the set of allowed filters for the tally
666
  const std::set<FilterType> allowed_filters = {
667
    FilterType::MESH, FilterType::ENERGY, FilterType::PARTICLE};
166✔
668

669
  // retrieve a mapping of filter type to filter index for the tally
670
  auto filter_indices = tally->filter_indices();
166✔
671

672
  // a mesh filter is required for a tally used to update weight windows
673
  if (!filter_indices.count(FilterType::MESH)) {
166!
UNCOV
674
    fatal_error(
×
675
      "A mesh filter is required for a tally to update weight window bounds");
676
  }
677

678
  // ensure the mesh filter is using the same mesh as this weight window object
679
  auto mesh_filter = tally->get_filter<MeshFilter>();
166✔
680

681
  // make sure that all of the filters present on the tally are allowed
682
  for (auto filter_pair : filter_indices) {
636✔
683
    if (allowed_filters.find(filter_pair.first) == allowed_filters.end()) {
470!
684
      fatal_error(fmt::format("Invalid filter type '{}' found on tally "
×
685
                              "used for weight window generation.",
UNCOV
686
        model::tally_filters[tally->filters(filter_pair.second)]->type_str()));
×
687
    }
688
  }
689

690
  if (mesh_filter->mesh() != mesh_idx_) {
166!
UNCOV
691
    int32_t mesh_filter_id = model::meshes[mesh_filter->mesh()]->id();
×
UNCOV
692
    int32_t ww_mesh_id = model::meshes[this->mesh_idx_]->id();
×
UNCOV
693
    fatal_error(fmt::format("Mesh filter {} uses a different mesh ({}) than "
×
694
                            "weight window {} mesh ({})",
UNCOV
695
      mesh_filter->id(), mesh_filter_id, id_, ww_mesh_id));
×
696
  }
697

698
  // if an energy filter exists, make sure the energy grid matches that of this
699
  // weight window object
700
  if (auto energy_filter = tally->get_filter<EnergyFilter>()) {
166✔
701
    std::vector<double> filter_bins = energy_filter->bins();
152✔
702
    std::set<double> filter_e_bounds(
703
      energy_filter->bins().begin(), energy_filter->bins().end());
152✔
704
    if (filter_e_bounds.size() != energy_bounds().size()) {
152!
UNCOV
705
      fatal_error(
×
706
        fmt::format("Energy filter {} does not have the same number of energy "
×
707
                    "bounds ({}) as weight window object {} ({})",
708
          energy_filter->id(), filter_e_bounds.size(), id_,
×
UNCOV
709
          energy_bounds().size()));
×
710
    }
711

712
    for (auto e : energy_bounds()) {
1,450✔
713
      if (filter_e_bounds.count(e) == 0) {
1,298!
UNCOV
714
        fatal_error(fmt::format(
×
715
          "Energy bounds of filter {} and weight windows {} do not match",
UNCOV
716
          energy_filter->id(), id_));
×
717
      }
718
    }
719
  }
152✔
720
}
166✔
721

722
void WeightWindows::to_hdf5(hid_t group) const
86✔
723
{
724
  hid_t ww_group = create_group(group, fmt::format("weight_windows_{}", id()));
172✔
725

726
  write_dataset(ww_group, "mesh", this->mesh()->id());
86✔
727
  write_dataset(
86✔
728
    ww_group, "particle_type", openmc::particle_type_to_str(particle_type_));
172✔
729
  write_dataset(ww_group, "energy_bounds", energy_bounds_);
86✔
730
  write_dataset(ww_group, "lower_ww_bounds", lower_ww_);
86✔
731
  write_dataset(ww_group, "upper_ww_bounds", upper_ww_);
86✔
732
  write_dataset(ww_group, "survival_ratio", survival_ratio_);
86✔
733
  write_dataset(ww_group, "max_lower_bound_ratio", max_lb_ratio_);
86✔
734
  write_dataset(ww_group, "max_split", max_split_);
86✔
735
  write_dataset(ww_group, "weight_cutoff", weight_cutoff_);
86✔
736

737
  close_group(ww_group);
86✔
738
}
86✔
739

740
WeightWindowsGenerator::WeightWindowsGenerator(pugi::xml_node node)
55✔
741
{
742
  // read information from the XML node
743
  int32_t mesh_id = std::stoi(get_node_value(node, "mesh"));
55✔
744
  int32_t mesh_idx = model::mesh_map[mesh_id];
55✔
745
  max_realizations_ = std::stoi(get_node_value(node, "max_realizations"));
55✔
746

747
  int32_t active_batches = settings::n_batches - settings::n_inactive;
55✔
748
  if (max_realizations_ > active_batches) {
55✔
749
    auto msg =
750
      fmt::format("The maximum number of specified tally realizations ({}) is "
751
                  "greater than the number of active batches ({}).",
752
        max_realizations_, active_batches);
22✔
753
    warning(msg);
12✔
754
  }
12✔
755
  auto tmp_str = get_node_value(node, "particle_type", true, true);
55✔
756
  auto particle_type = str_to_particle_type(tmp_str);
55✔
757

758
  update_interval_ = std::stoi(get_node_value(node, "update_interval"));
55✔
759
  on_the_fly_ = get_node_value_bool(node, "on_the_fly");
55✔
760

761
  std::vector<double> e_bounds;
55✔
762
  if (check_for_node(node, "energy_bounds")) {
55✔
763
    e_bounds = get_node_array<double>(node, "energy_bounds");
15✔
764
  } else {
765
    int p_type = static_cast<int>(particle_type);
40✔
766
    e_bounds.push_back(data::energy_min[p_type]);
40✔
767
    e_bounds.push_back(data::energy_max[p_type]);
40✔
768
  }
769

770
  // set method
771
  std::string method_string = get_node_value(node, "method");
55✔
772
  if (method_string == "magic") {
55✔
773
    method_ = WeightWindowUpdateMethod::MAGIC;
21✔
774
    if (settings::solver_type == SolverType::RANDOM_RAY &&
21!
775
        FlatSourceDomain::adjoint_) {
UNCOV
776
      fatal_error("Random ray weight window generation with MAGIC cannot be "
×
777
                  "done in adjoint mode.");
778
    }
779
  } else if (method_string == "fw_cadis") {
34!
780
    method_ = WeightWindowUpdateMethod::FW_CADIS;
34✔
781
    if (settings::solver_type != SolverType::RANDOM_RAY) {
34!
UNCOV
782
      fatal_error("FW-CADIS can only be run in random ray solver mode.");
×
783
    }
784
    FlatSourceDomain::adjoint_ = true;
34✔
785
  } else {
UNCOV
786
    fatal_error(fmt::format(
×
787
      "Unknown weight window update method '{}' specified", method_string));
788
  }
789

790
  // parse non-default update parameters if specified
791
  if (check_for_node(node, "update_parameters")) {
55✔
792
    pugi::xml_node params_node = node.child("update_parameters");
14✔
793
    if (check_for_node(params_node, "value"))
14!
794
      tally_value_ = get_node_value(params_node, "value");
14✔
795
    if (check_for_node(params_node, "threshold"))
14!
796
      threshold_ = std::stod(get_node_value(params_node, "threshold"));
14✔
797
    if (check_for_node(params_node, "ratio")) {
14!
798
      ratio_ = std::stod(get_node_value(params_node, "ratio"));
14✔
799
    }
800
  }
801

802
  // check update parameter values
803
  if (tally_value_ != "mean" && tally_value_ != "rel_err") {
55!
UNCOV
804
    fatal_error(fmt::format("Unsupported tally value '{}' specified for "
×
805
                            "weight window generation.",
UNCOV
806
      tally_value_));
×
807
  }
808
  if (threshold_ <= 0.0)
55!
UNCOV
809
    fatal_error(fmt::format("Invalid relative error threshold '{}' (<= 0.0) "
×
810
                            "specified for weight window generation",
UNCOV
811
      ratio_));
×
812
  if (ratio_ <= 1.0)
55!
UNCOV
813
    fatal_error(fmt::format("Invalid weight window ratio '{}' (<= 1.0) "
×
814
                            "specified for weight window generation"));
815

816
  // create a matching weight windows object
817
  auto wws = WeightWindows::create();
55✔
818
  ww_idx_ = wws->index();
55✔
819
  wws->set_mesh(mesh_idx);
55✔
820
  if (e_bounds.size() > 0)
55!
821
    wws->set_energy_bounds(e_bounds);
55✔
822
  wws->set_particle_type(particle_type);
55✔
823
  wws->set_defaults();
55✔
824
}
55✔
825

826
void WeightWindowsGenerator::create_tally()
55✔
827
{
828
  const auto& wws = variance_reduction::weight_windows[ww_idx_];
55✔
829

830
  // create a tally based on the WWG information
831
  Tally* ww_tally = Tally::create();
55✔
832
  tally_idx_ = model::tally_map[ww_tally->id()];
55✔
833
  ww_tally->set_scores({"flux"});
110!
834

835
  int32_t mesh_id = wws->mesh()->id();
55✔
836
  int32_t mesh_idx = model::mesh_map.at(mesh_id);
55✔
837
  // see if there's already a mesh filter using this mesh
838
  bool found_mesh_filter = false;
55✔
839
  for (const auto& f : model::tally_filters) {
175✔
840
    if (f->type() == FilterType::MESH) {
127✔
841
      const auto* mesh_filter = dynamic_cast<MeshFilter*>(f.get());
7!
842
      if (mesh_filter->mesh() == mesh_idx && !mesh_filter->translated() &&
14!
843
          !mesh_filter->rotated()) {
7!
844
        ww_tally->add_filter(f.get());
7✔
845
        found_mesh_filter = true;
7✔
846
        break;
7✔
847
      }
848
    }
849
  }
850

851
  if (!found_mesh_filter) {
55✔
852
    auto mesh_filter = Filter::create("mesh");
48✔
853
    openmc_mesh_filter_set_mesh(mesh_filter->index(), model::mesh_map[mesh_id]);
48✔
854
    ww_tally->add_filter(mesh_filter);
48✔
855
  }
856

857
  const auto& e_bounds = wws->energy_bounds();
55✔
858
  if (e_bounds.size() > 0) {
55!
859
    auto energy_filter = Filter::create("energy");
55✔
860
    openmc_energy_filter_set_bins(
110✔
861
      energy_filter->index(), e_bounds.size(), e_bounds.data());
55✔
862
    ww_tally->add_filter(energy_filter);
55✔
863
  }
864

865
  // add a particle filter
866
  auto particle_type = wws->particle_type();
55✔
867
  auto particle_filter = Filter::create("particle");
55✔
868
  auto pf = dynamic_cast<ParticleFilter*>(particle_filter);
55!
869
  pf->set_particles({&particle_type, 1});
55✔
870
  ww_tally->add_filter(particle_filter);
55✔
871
}
55✔
872

873
void WeightWindowsGenerator::update() const
1,657✔
874
{
875
  const auto& wws = variance_reduction::weight_windows[ww_idx_];
1,657✔
876

877
  Tally* tally = model::tallies[tally_idx_].get();
1,657✔
878

879
  // If in random ray mode, only update on the last batch
880
  if (settings::solver_type == SolverType::RANDOM_RAY) {
1,657✔
881
    if (simulation::current_batch != settings::n_batches) {
1,552✔
882
      return;
1,484✔
883
    }
884
    // If in Monte Carlo mode and beyond the number of max realizations or
885
    // not at the correct update interval, skip the update
886
  } else if (max_realizations_ < tally->n_realizations_ ||
105✔
887
             tally->n_realizations_ % update_interval_ != 0) {
21!
888
    return;
84✔
889
  }
890

891
  wws->update_weights(tally, tally_value_, threshold_, ratio_, method_);
89✔
892

893
  // if we're not doing on the fly generation, reset the tally results once
894
  // we're done with the update
895
  if (!on_the_fly_)
89!
UNCOV
896
    tally->reset();
×
897

898
  // TODO: deactivate or remove tally once weight window generation is
899
  // complete
900
}
901

902
//==============================================================================
903
// Non-member functions
904
//==============================================================================
905

906
WeightWindow search_weight_window(const Particle& p)
52,398,337✔
907
{
908
  // TODO: this is a linear search - should do something more clever
909
  int mesh_bin;
910
  WeightWindow weight_window;
52,398,337✔
911
  for (const auto& ww : variance_reduction::weight_windows) {
73,665,504✔
912
    mesh_bin = ww->get_mesh_bin(p);
57,902,647✔
913
    if (mesh_bin < 0)
57,902,647✔
914
      continue;
12,616,307✔
915
    weight_window = ww->get_weight_window(p.E(), mesh_bin);
45,286,340✔
916
    if (weight_window.is_valid())
45,286,340✔
917
      return weight_window;
36,635,480✔
918
  }
919
  return {};
15,762,857✔
920
}
921

922
void apply_weight_windows(Particle& p)
106,385,536✔
923
{
924
  if (!settings::weight_windows_on)
106,385,536✔
925
    return;
105,908,595✔
926

927
  // WW on photon and neutron only
928
  if (p.type() != ParticleType::neutron && p.type() != ParticleType::photon)
480,618!
NEW
929
    return;
×
930

931
  // skip dead or no energy
932
  if (p.E() <= 0 || !p.alive())
480,618!
933
    return;
3,677✔
934

935
  auto ww = search_weight_window(p);
476,941✔
936
  if (ww.is_valid()) {
476,941✔
937
    apply_weight_window(p, ww);
433,636✔
938
  } else {
939
    if (p.wgt_ww_born() == -1.0)
43,305✔
940
      p.wgt_ww_born() = 1.0;
41,874✔
941
  }
942
}
943

944
void apply_weight_window(Particle& p, WeightWindow weight_window)
45,278,559✔
945
{
946
  if (!weight_window.is_valid())
45,278,559✔
947
    return;
8,643,079✔
948

949
  // skip dead or no energy
950
  if (p.E() <= 0 || !p.alive())
36,635,480✔
951
    return;
2,289,299✔
952

953
  // If particle has not yet had its birth weight window value set, set it to
954
  // the current weight window.
955
  if (p.wgt_ww_born() == -1.0)
34,346,181✔
956
    p.wgt_ww_born() =
428,006✔
957
      (weight_window.lower_weight + weight_window.upper_weight) / 2;
428,006✔
958

959
  // Normalize weight windows based on particle's starting weight
960
  // and the value of the weight window the particle was born in.
961
  weight_window.scale(p.wgt_born() / p.wgt_ww_born());
34,346,181✔
962

963
  // get the paramters
964
  double weight = p.wgt();
34,346,181✔
965

966
  // first check to see if particle should be killed for weight cutoff
967
  if (p.wgt() < weight_window.weight_cutoff) {
34,346,181!
NEW
968
    p.wgt() = 0.0;
×
NEW
969
    return;
×
970
  }
971

972
  // check if particle is far above current weight window
973
  // only do this if the factor is not already set on the particle and a
974
  // maximum lower bound ratio is specified
975
  if (p.ww_factor() == 0.0 && weight_window.max_lb_ratio > 1.0 &&
34,347,987✔
976
      p.wgt() > weight_window.lower_weight * weight_window.max_lb_ratio) {
1,806!
977
    p.ww_factor() =
1,806✔
978
      p.wgt() / (weight_window.lower_weight * weight_window.max_lb_ratio);
1,806✔
979
  }
980

981
  // move weight window closer to the particle weight if needed
982
  if (p.ww_factor() > 1.0)
34,346,181✔
983
    weight_window.scale(p.ww_factor());
863,191✔
984

985
  // if particle's weight is above the weight window split until they are within
986
  // the window
987
  if (weight > weight_window.upper_weight) {
34,346,181✔
988
    // do not further split the particle if above the limit
989
    if (p.n_split() >= settings::max_history_splits)
8,556,019✔
990
      return;
7,719,030✔
991

992
    double n_split = std::ceil(weight / weight_window.upper_weight);
836,989✔
993
    double max_split = weight_window.max_split;
836,989✔
994
    n_split = std::min(n_split, max_split);
836,989✔
995

996
    p.n_split() += n_split;
836,989✔
997

998
    // Create secondaries and divide weight among all particles
999
    int i_split = std::round(n_split);
836,989✔
1000
    for (int l = 0; l < i_split - 1; l++) {
3,440,340✔
1001
      p.split(weight / n_split);
2,603,351✔
1002
    }
1003
    // remaining weight is applied to current particle
1004
    p.wgt() = weight / n_split;
836,989✔
1005

1006
  } else if (weight <= weight_window.lower_weight) {
25,790,162✔
1007
    // if the particle weight is below the window, play Russian roulette
1008
    double weight_survive =
1009
      std::min(weight * weight_window.max_split, weight_window.survival_weight);
807,775✔
1010
    russian_roulette(p, weight_survive);
807,775✔
1011
  } // else particle is in the window, continue as normal
1012
}
1013

1014
void free_memory_weight_windows()
5,431✔
1015
{
1016
  variance_reduction::ww_map.clear();
5,431✔
1017
  variance_reduction::weight_windows.clear();
5,431✔
1018
}
5,431✔
1019

1020
void finalize_variance_reduction()
5,335✔
1021
{
1022
  for (const auto& wwg : variance_reduction::weight_windows_generators) {
5,390✔
1023
    wwg->create_tally();
55✔
1024
  }
1025
}
5,335✔
1026

1027
//==============================================================================
1028
// C API
1029
//==============================================================================
1030

1031
int verify_ww_index(int32_t index)
1,267✔
1032
{
1033
  if (index < 0 || index >= variance_reduction::weight_windows.size()) {
1,267!
1034
    set_errmsg(fmt::format("Index '{}' for weight windows is invalid", index));
×
UNCOV
1035
    return OPENMC_E_OUT_OF_BOUNDS;
×
1036
  }
1037
  return 0;
1,267✔
1038
}
1039

1040
extern "C" int openmc_get_weight_windows_index(int32_t id, int32_t* idx)
105✔
1041
{
1042
  auto it = variance_reduction::ww_map.find(id);
105✔
1043
  if (it == variance_reduction::ww_map.end()) {
105!
1044
    set_errmsg(fmt::format("No weight windows exist with ID={}", id));
×
UNCOV
1045
    return OPENMC_E_INVALID_ID;
×
1046
  }
1047

1048
  *idx = it->second;
105✔
1049
  return 0;
105✔
1050
}
1051

1052
extern "C" int openmc_weight_windows_get_id(int32_t index, int32_t* id)
329✔
1053
{
1054
  if (int err = verify_ww_index(index))
329!
UNCOV
1055
    return err;
×
1056

1057
  const auto& wws = variance_reduction::weight_windows.at(index);
329✔
1058
  *id = wws->id();
329✔
1059
  return 0;
329✔
1060
}
1061

1062
extern "C" int openmc_weight_windows_set_id(int32_t index, int32_t id)
98✔
1063
{
1064
  if (int err = verify_ww_index(index))
98!
1065
    return err;
×
1066

1067
  const auto& wws = variance_reduction::weight_windows.at(index);
98✔
1068
  wws->set_id(id);
98✔
1069
  return 0;
98✔
1070
}
1071

1072
extern "C" int openmc_weight_windows_update_magic(int32_t ww_idx,
77✔
1073
  int32_t tally_idx, const char* value, double threshold, double ratio)
1074
{
1075
  if (int err = verify_ww_index(ww_idx))
77!
UNCOV
1076
    return err;
×
1077

1078
  if (tally_idx < 0 || tally_idx >= model::tallies.size()) {
77!
UNCOV
1079
    set_errmsg(fmt::format("Index '{}' for tally is invalid", tally_idx));
×
UNCOV
1080
    return OPENMC_E_OUT_OF_BOUNDS;
×
1081
  }
1082

1083
  // get the requested tally
1084
  const Tally* tally = model::tallies.at(tally_idx).get();
77✔
1085

1086
  // get the WeightWindows object
1087
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
77✔
1088

1089
  wws->update_weights(tally, value, threshold, ratio);
77✔
1090

1091
  return 0;
77✔
1092
}
1093

1094
extern "C" int openmc_weight_windows_set_mesh(int32_t ww_idx, int32_t mesh_idx)
98✔
1095
{
1096
  if (int err = verify_ww_index(ww_idx))
98!
UNCOV
1097
    return err;
×
1098
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
98✔
1099
  wws->set_mesh(mesh_idx);
98✔
1100
  return 0;
98✔
1101
}
1102

1103
extern "C" int openmc_weight_windows_get_mesh(int32_t ww_idx, int32_t* mesh_idx)
7✔
1104
{
1105
  if (int err = verify_ww_index(ww_idx))
7!
UNCOV
1106
    return err;
×
1107
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
7✔
1108
  *mesh_idx = model::mesh_map.at(wws->mesh()->id());
7✔
1109
  return 0;
7✔
1110
}
1111

1112
extern "C" int openmc_weight_windows_set_energy_bounds(
84✔
1113
  int32_t ww_idx, double* e_bounds, size_t e_bounds_size)
1114
{
1115
  if (int err = verify_ww_index(ww_idx))
84!
UNCOV
1116
    return err;
×
1117
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
84✔
1118
  wws->set_energy_bounds({e_bounds, e_bounds_size});
84✔
1119
  return 0;
84✔
1120
}
1121

1122
extern "C" int openmc_weight_windows_get_energy_bounds(
7✔
1123
  int32_t ww_idx, const double** e_bounds, size_t* e_bounds_size)
1124
{
1125
  if (int err = verify_ww_index(ww_idx))
7!
UNCOV
1126
    return err;
×
1127
  const auto& wws = variance_reduction::weight_windows[ww_idx].get();
7✔
1128
  *e_bounds = wws->energy_bounds().data();
7✔
1129
  *e_bounds_size = wws->energy_bounds().size();
7✔
1130
  return 0;
7✔
1131
}
1132

1133
extern "C" int openmc_weight_windows_set_particle(int32_t index, int particle)
112✔
1134
{
1135
  if (int err = verify_ww_index(index))
112!
UNCOV
1136
    return err;
×
1137

1138
  const auto& wws = variance_reduction::weight_windows.at(index);
112✔
1139
  wws->set_particle_type(static_cast<ParticleType>(particle));
112✔
1140
  return 0;
112✔
1141
}
1142

1143
extern "C" int openmc_weight_windows_get_particle(int32_t index, int* particle)
28✔
1144
{
1145
  if (int err = verify_ww_index(index))
28!
1146
    return err;
×
1147

1148
  const auto& wws = variance_reduction::weight_windows.at(index);
28✔
1149
  *particle = static_cast<int>(wws->particle_type());
28✔
1150
  return 0;
28✔
1151
}
1152

1153
extern "C" int openmc_weight_windows_get_bounds(int32_t index,
308✔
1154
  const double** lower_bounds, const double** upper_bounds, size_t* size)
1155
{
1156
  if (int err = verify_ww_index(index))
308!
UNCOV
1157
    return err;
×
1158

1159
  const auto& wws = variance_reduction::weight_windows[index];
308✔
1160
  *size = wws->lower_ww_bounds().size();
308✔
1161
  *lower_bounds = wws->lower_ww_bounds().data();
308✔
1162
  *upper_bounds = wws->upper_ww_bounds().data();
308✔
1163
  return 0;
308✔
1164
}
1165

1166
extern "C" int openmc_weight_windows_set_bounds(int32_t index,
7✔
1167
  const double* lower_bounds, const double* upper_bounds, size_t size)
1168
{
1169
  if (int err = verify_ww_index(index))
7!
1170
    return err;
×
1171

1172
  const auto& wws = variance_reduction::weight_windows[index];
7✔
1173
  wws->set_bounds({lower_bounds, size}, {upper_bounds, size});
7✔
1174
  return 0;
7✔
1175
}
1176

1177
extern "C" int openmc_weight_windows_get_survival_ratio(
21✔
1178
  int32_t index, double* ratio)
1179
{
1180
  if (int err = verify_ww_index(index))
21!
UNCOV
1181
    return err;
×
1182
  const auto& wws = variance_reduction::weight_windows[index];
21✔
1183
  *ratio = wws->survival_ratio();
21✔
1184
  return 0;
21✔
1185
}
1186

1187
extern "C" int openmc_weight_windows_set_survival_ratio(
7✔
1188
  int32_t index, double ratio)
1189
{
1190
  if (int err = verify_ww_index(index))
7!
1191
    return err;
×
1192
  const auto& wws = variance_reduction::weight_windows[index];
7✔
1193
  wws->survival_ratio() = ratio;
7✔
1194
  std::cout << "Survival ratio: " << wws->survival_ratio() << std::endl;
7✔
1195
  return 0;
7✔
1196
}
1197

1198
extern "C" int openmc_weight_windows_get_max_lower_bound_ratio(
21✔
1199
  int32_t index, double* lb_ratio)
1200
{
1201
  if (int err = verify_ww_index(index))
21!
UNCOV
1202
    return err;
×
1203
  const auto& wws = variance_reduction::weight_windows[index];
21✔
1204
  *lb_ratio = wws->max_lower_bound_ratio();
21✔
1205
  return 0;
21✔
1206
}
1207

1208
extern "C" int openmc_weight_windows_set_max_lower_bound_ratio(
7✔
1209
  int32_t index, double lb_ratio)
1210
{
1211
  if (int err = verify_ww_index(index))
7!
UNCOV
1212
    return err;
×
1213
  const auto& wws = variance_reduction::weight_windows[index];
7✔
1214
  wws->max_lower_bound_ratio() = lb_ratio;
7✔
1215
  return 0;
7✔
1216
}
1217

1218
extern "C" int openmc_weight_windows_get_weight_cutoff(
21✔
1219
  int32_t index, double* cutoff)
1220
{
1221
  if (int err = verify_ww_index(index))
21!
UNCOV
1222
    return err;
×
1223
  const auto& wws = variance_reduction::weight_windows[index];
21✔
1224
  *cutoff = wws->weight_cutoff();
21✔
1225
  return 0;
21✔
1226
}
1227

1228
extern "C" int openmc_weight_windows_set_weight_cutoff(
7✔
1229
  int32_t index, double cutoff)
1230
{
1231
  if (int err = verify_ww_index(index))
7!
UNCOV
1232
    return err;
×
1233
  const auto& wws = variance_reduction::weight_windows[index];
7✔
1234
  wws->weight_cutoff() = cutoff;
7✔
1235
  return 0;
7✔
1236
}
1237

1238
extern "C" int openmc_weight_windows_get_max_split(
21✔
1239
  int32_t index, int* max_split)
1240
{
1241
  if (int err = verify_ww_index(index))
21!
UNCOV
1242
    return err;
×
1243
  const auto& wws = variance_reduction::weight_windows[index];
21✔
1244
  *max_split = wws->max_split();
21✔
1245
  return 0;
21✔
1246
}
1247

1248
extern "C" int openmc_weight_windows_set_max_split(int32_t index, int max_split)
7✔
1249
{
1250
  if (int err = verify_ww_index(index))
7!
UNCOV
1251
    return err;
×
1252
  const auto& wws = variance_reduction::weight_windows[index];
7✔
1253
  wws->max_split() = max_split;
7✔
1254
  return 0;
7✔
1255
}
1256

1257
extern "C" int openmc_extend_weight_windows(
98✔
1258
  int32_t n, int32_t* index_start, int32_t* index_end)
1259
{
1260
  if (index_start)
98!
1261
    *index_start = variance_reduction::weight_windows.size();
98✔
1262
  if (index_end)
98!
UNCOV
1263
    *index_end = variance_reduction::weight_windows.size() + n - 1;
×
1264
  for (int i = 0; i < n; ++i)
196✔
1265
    variance_reduction::weight_windows.push_back(make_unique<WeightWindows>());
98✔
1266
  return 0;
98✔
1267
}
1268

1269
extern "C" size_t openmc_weight_windows_size()
98✔
1270
{
1271
  return variance_reduction::weight_windows.size();
98✔
1272
}
1273

1274
extern "C" int openmc_weight_windows_export(const char* filename)
110✔
1275
{
1276

1277
  if (!mpi::master)
110✔
1278
    return 0;
24✔
1279

1280
  std::string name = filename ? filename : "weight_windows.h5";
172✔
1281

1282
  write_message(fmt::format("Exporting weight windows to {}...", name), 5);
86✔
1283

1284
  hid_t ww_file = file_open(name, 'w');
86✔
1285

1286
  // Write file type
1287
  write_attribute(ww_file, "filetype", "weight_windows");
86✔
1288

1289
  // Write revisiion number for state point file
1290
  write_attribute(ww_file, "version", VERSION_WEIGHT_WINDOWS);
86✔
1291

1292
  hid_t weight_windows_group = create_group(ww_file, "weight_windows");
86✔
1293

1294
  hid_t mesh_group = create_group(ww_file, "meshes");
86✔
1295

1296
  std::vector<int32_t> mesh_ids;
86✔
1297
  std::vector<int32_t> ww_ids;
86✔
1298
  for (const auto& ww : variance_reduction::weight_windows) {
172✔
1299

1300
    ww->to_hdf5(weight_windows_group);
86✔
1301
    ww_ids.push_back(ww->id());
86✔
1302

1303
    // if the mesh has already been written, move on
1304
    int32_t mesh_id = ww->mesh()->id();
86✔
1305
    if (std::find(mesh_ids.begin(), mesh_ids.end(), mesh_id) != mesh_ids.end())
86!
UNCOV
1306
      continue;
×
1307

1308
    mesh_ids.push_back(mesh_id);
86✔
1309
    ww->mesh()->to_hdf5(mesh_group);
86✔
1310
  }
1311

1312
  write_attribute(mesh_group, "n_meshes", mesh_ids.size());
86✔
1313
  write_attribute(mesh_group, "ids", mesh_ids);
86✔
1314
  close_group(mesh_group);
86✔
1315

1316
  write_attribute(weight_windows_group, "n_weight_windows", ww_ids.size());
86✔
1317
  write_attribute(weight_windows_group, "ids", ww_ids);
86✔
1318
  close_group(weight_windows_group);
86✔
1319

1320
  file_close(ww_file);
86✔
1321

1322
  return 0;
86✔
1323
}
86✔
1324

1325
extern "C" int openmc_weight_windows_import(const char* filename)
7✔
1326
{
1327
  std::string name = filename ? filename : "weight_windows.h5";
7!
1328

1329
  if (mpi::master)
7!
1330
    write_message(fmt::format("Importing weight windows from {}...", name), 5);
7✔
1331

1332
  if (!file_exists(name)) {
7!
1333
    set_errmsg(fmt::format("File '{}' does not exist", name));
×
1334
  }
1335

1336
  hid_t ww_file = file_open(name, 'r');
7✔
1337

1338
  // Check that filetype is correct
1339
  std::string filetype;
7✔
1340
  read_attribute(ww_file, "filetype", filetype);
7✔
1341
  if (filetype != "weight_windows") {
7!
UNCOV
1342
    file_close(ww_file);
×
1343
    set_errmsg(fmt::format("File '{}' is not a weight windows file.", name));
×
1344
    return OPENMC_E_INVALID_ARGUMENT;
×
1345
  }
1346

1347
  // Check that the file version is compatible
1348
  std::array<int, 2> file_version;
1349
  read_attribute(ww_file, "version", file_version);
7✔
1350
  if (file_version[0] != VERSION_WEIGHT_WINDOWS[0]) {
7!
1351
    std::string err_msg =
1352
      fmt::format("File '{}' has version {} which is incompatible with the "
1353
                  "expected version ({}).",
UNCOV
1354
        name, file_version, VERSION_WEIGHT_WINDOWS);
×
UNCOV
1355
    set_errmsg(err_msg);
×
UNCOV
1356
    return OPENMC_E_INVALID_ARGUMENT;
×
UNCOV
1357
  }
×
1358

1359
  hid_t weight_windows_group = open_group(ww_file, "weight_windows");
7✔
1360

1361
  hid_t mesh_group = open_group(ww_file, "meshes");
7✔
1362

1363
  read_meshes(mesh_group);
7✔
1364

1365
  std::vector<std::string> names = group_names(weight_windows_group);
7✔
1366

1367
  for (const auto& name : names) {
14✔
1368
    WeightWindows::from_hdf5(weight_windows_group, name);
7✔
1369
  }
1370

1371
  close_group(weight_windows_group);
7✔
1372

1373
  file_close(ww_file);
7✔
1374

1375
  return 0;
7✔
1376
}
7✔
1377

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