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

openmc-dev / openmc / 21621311327

03 Feb 2026 07:35AM UTC coverage: 81.677% (-0.09%) from 81.763%
21621311327

Pull #3751

github

web-flow
Merge 57b5d2785 into b41e22f68
Pull Request #3751: Resolve conflict with weight windows and global russian roulette

17335 of 24277 branches covered (71.41%)

Branch coverage included in aggregate %.

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

113 existing lines in 1 file now uncovered.

55948 of 65446 relevant lines covered (85.49%)

44199446.16 hits per line

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

78.75
/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)
247✔
57
{
58
  index_ = variance_reduction::weight_windows.size();
247✔
59
  set_id(id);
247✔
60
  set_defaults();
247✔
61
}
247✔
62

63
WeightWindows::WeightWindows(pugi::xml_node node)
92✔
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"};
644✔
68
  for (const auto& elem : required_elems) {
460✔
69
    if (!check_for_node(node, elem.c_str())) {
368!
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"));
92✔
76
  this->set_id(id);
92✔
77

78
  // get the particle type
79
  auto particle_type_str = std::string(get_node_value(node, "particle_type"));
92✔
80
  particle_type_ = ParticleType {particle_type_str};
92✔
81

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

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

90
  // get the survival value - optional
91
  if (check_for_node(node, "survival_ratio")) {
92!
92
    survival_ratio_ = std::stod(get_node_value(node, "survival_ratio"));
92✔
93
    if (survival_ratio_ <= 1)
92!
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")) {
92✔
100
    max_lb_ratio_ = std::stod(get_node_value(node, "max_lower_bound_ratio"));
34✔
101
    if (max_lb_ratio_ < 1.0) {
34!
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")) {
92!
108
    max_split_ = std::stod(get_node_value(node, "max_split"));
92✔
109
    if (max_split_ <= 1)
92!
110
      fatal_error("max split must be larger than 1");
×
111
  }
112

113
  // weight cutoff - optional
114
  if (check_for_node(node, "weight_cutoff")) {
92!
115
    weight_cutoff_ = std::stod(get_node_value(node, "weight_cutoff"));
92✔
116
    if (weight_cutoff_ <= 0)
92!
117
      fatal_error("weight_cutoff must be larger than 0");
×
118
    if (weight_cutoff_ > 1)
92!
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"),
92✔
124
    get_node_array<double>(node, "upper_ww_bounds"));
184✔
125

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

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

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

143
WeightWindows* WeightWindows::from_hdf5(
11✔
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);
11✔
148

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

151
  std::string particle_type;
11✔
152
  read_dataset(ww_group, "particle_type", particle_type);
11✔
153
  wws->particle_type_ = ParticleType {particle_type};
11✔
154

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

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

160
  if (model::mesh_map.count(mesh_id) == 0) {
11!
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]);
11✔
165

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

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

176
  close_group(ww_group);
11✔
177

178
  return wws;
11✔
179
}
11✔
180

181
void WeightWindows::set_defaults()
421✔
182
{
183
  // set energy bounds to the min/max energy supported by the data
184
  if (energy_bounds_.size() == 0) {
421✔
185
    int p_type = particle_type_.transport_index();
262✔
186
    if (p_type == C_NONE) {
262!
187
      fatal_error("Weight windows particle is not supported for transport.");
×
188
    }
189
    energy_bounds_.push_back(data::energy_min[p_type]);
262✔
190
    energy_bounds_.push_back(data::energy_max[p_type]);
262✔
191
  }
192
}
421✔
193

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

208
void WeightWindows::set_id(int32_t id)
493✔
209
{
210
  assert(id >= 0 || id == C_NONE);
402!
211

212
  // Clear entry in mesh map in case one was already assigned
213
  if (id_ != C_NONE) {
493!
214
    variance_reduction::ww_map.erase(id_);
493✔
215
    id_ = C_NONE;
493✔
216
  }
217

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

224
  // If no ID is specified, auto-assign the next ID in the sequence
225
  if (id == C_NONE) {
493✔
226
    id = 0;
247✔
227
    for (const auto& m : variance_reduction::weight_windows) {
269✔
228
      id = std::max(id, m->id_);
22✔
229
    }
230
    ++id;
247✔
231
  }
232

233
  // Update ID and entry in the mesh map
234
  id_ = id;
493✔
235
  variance_reduction::ww_map[id] = index_;
493✔
236
}
493✔
237

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

247
void WeightWindows::set_particle_type(ParticleType p_type)
258✔
248
{
249
  if (!p_type.is_neutron() && !p_type.is_photon())
258!
250
    fatal_error(fmt::format(
×
251
      "Particle type '{}' cannot be applied to weight windows.", p_type.str()));
×
252
  particle_type_ = p_type;
258✔
253
}
258✔
254

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

260
  mesh_idx_ = mesh_idx;
339✔
261
  model::meshes[mesh_idx_]->prepare_for_point_location();
339✔
262
  allocate_ww_bounds();
339✔
263
}
339✔
264

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

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

275
WeightWindow WeightWindows::get_weight_window(const Particle& p) const
90,891,848✔
276
{
277
  // check for particle type
278
  if (particle_type_ != p.type()) {
90,891,848✔
279
    return {};
19,708,756✔
280
  }
281

282
  // Get mesh index for particle's position
283
  const auto& mesh = this->mesh();
71,183,092✔
284
  int mesh_bin = mesh->get_bin(p.r());
71,183,092✔
285

286
  // particle is outside the weight window mesh
287
  if (mesh_bin < 0)
71,183,092✔
288
    return {};
14,834✔
289

290
  // particle energy
291
  double E = p.E();
71,168,258✔
292

293
  // check to make sure energy is in range, expects sorted energy values
294
  if (E < energy_bounds_.front() || E > energy_bounds_.back())
71,168,258!
295
    return {};
92,818✔
296

297
  // get the mesh bin in energy group
298
  int energy_bin =
299
    lower_bound_index(energy_bounds_.begin(), energy_bounds_.end(), E);
71,075,440✔
300

301
  // mesh_bin += energy_bin * mesh->n_bins();
302
  // Create individual weight window
303
  WeightWindow ww;
71,075,440✔
304
  ww.lower_weight = lower_ww_(energy_bin, mesh_bin);
71,075,440✔
305
  ww.upper_weight = upper_ww_(energy_bin, mesh_bin);
71,075,440✔
306
  ww.survival_weight = ww.lower_weight * survival_ratio_;
71,075,440✔
307
  ww.max_lb_ratio = max_lb_ratio_;
71,075,440✔
308
  ww.max_split = max_split_;
71,075,440✔
309
  ww.weight_cutoff = weight_cutoff_;
71,075,440✔
310
  return ww;
71,075,440✔
311
}
312

313
std::array<int, 2> WeightWindows::bounds_size() const
781✔
314
{
315
  int num_spatial_bins = this->mesh()->n_bins();
781✔
316
  int num_energy_bins =
317
    energy_bounds_.size() > 0 ? energy_bounds_.size() - 1 : 1;
781✔
318
  return {num_energy_bins, num_spatial_bins};
781✔
319
}
320

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

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

349
void WeightWindows::set_bounds(const xt::xtensor<double, 2>& lower_bounds,
×
350
  const xt::xtensor<double, 2>& upper_bounds)
351
{
352

353
  this->check_bounds(lower_bounds, upper_bounds);
×
354

355
  // set new weight window values
356
  lower_ww_ = lower_bounds;
×
357
  upper_ww_ = upper_bounds;
×
358
}
×
359

360
void WeightWindows::set_bounds(
×
361
  const xt::xtensor<double, 2>& lower_bounds, double ratio)
362
{
363
  this->check_bounds(lower_bounds);
×
364

365
  // set new weight window values
366
  lower_ww_ = lower_bounds;
×
367
  upper_ww_ = lower_bounds;
×
368
  upper_ww_ *= ratio;
×
369
}
×
370

371
void WeightWindows::set_bounds(
103✔
372
  span<const double> lower_bounds, span<const double> upper_bounds)
373
{
374
  check_bounds(lower_bounds, upper_bounds);
103✔
375
  auto shape = this->bounds_size();
103✔
376
  lower_ww_ = xt::empty<double>(shape);
103✔
377
  upper_ww_ = xt::empty<double>(shape);
103✔
378

379
  // set new weight window values
380
  xt::view(lower_ww_, xt::all()) =
206✔
381
    xt::adapt(lower_bounds.data(), lower_ww_.shape());
309✔
382
  xt::view(upper_ww_, xt::all()) =
206✔
383
    xt::adapt(upper_bounds.data(), upper_ww_.shape());
309✔
384
}
103✔
385

386
void WeightWindows::set_bounds(span<const double> lower_bounds, double ratio)
×
387
{
388
  this->check_bounds(lower_bounds);
×
389

390
  auto shape = this->bounds_size();
×
391
  lower_ww_ = xt::empty<double>(shape);
×
392
  upper_ww_ = xt::empty<double>(shape);
×
393

394
  // set new weight window values
395
  xt::view(lower_ww_, xt::all()) =
×
396
    xt::adapt(lower_bounds.data(), lower_ww_.shape());
×
397
  xt::view(upper_ww_, xt::all()) =
×
398
    xt::adapt(lower_bounds.data(), upper_ww_.shape());
×
399
  upper_ww_ *= ratio;
×
400
}
×
401

402
void WeightWindows::update_weights(const Tally* tally, const std::string& value,
252✔
403
  double threshold, double ratio, WeightWindowUpdateMethod method)
404
{
405
  ///////////////////////////
406
  // Setup and checks
407
  ///////////////////////////
408
  this->check_tally_update_compatibility(tally);
252✔
409

410
  // Dimensions of weight window arrays
411
  int e_bins = lower_ww_.shape()[0];
252✔
412
  int64_t mesh_bins = lower_ww_.shape()[1];
252✔
413

414
  // Initialize weight window arrays to -1.0 by default
415
#pragma omp parallel for collapse(2) schedule(static)
140✔
416
  for (int e = 0; e < e_bins; e++) {
934✔
417
    for (int64_t m = 0; m < mesh_bins; m++) {
1,190,071✔
418
      lower_ww_(e, m) = -1.0;
1,189,249✔
419
      upper_ww_(e, m) = -1.0;
1,189,249✔
420
    }
421
  }
422

423
  // determine which value to use
424
  const std::set<std::string> allowed_values = {"mean", "rel_err"};
1,260✔
425
  if (allowed_values.count(value) == 0) {
252!
426
    fatal_error(fmt::format("Invalid value '{}' specified for weight window "
×
427
                            "generation. Must be one of: 'mean' or 'rel_err'",
428
      value));
429
  }
430

431
  // determine the index of the specified score
432
  int score_index = tally->score_index("flux");
252✔
433
  if (score_index == C_NONE) {
252!
434
    fatal_error(
×
435
      fmt::format("A 'flux' score required for weight window generation "
×
436
                  "is not present on tally {}.",
437
        tally->id()));
×
438
  }
439

440
  ///////////////////////////
441
  // Extract tally data
442
  //
443
  // At the end of this section, the mean and rel_err array
444
  // is a 2D view of tally data (n_e_groups, n_mesh_bins)
445
  //
446
  ///////////////////////////
447

448
  // build a shape for a view of the tally results, this will always be
449
  // dimension 5 (3 filter dimensions, 1 score dimension, 1 results dimension)
450
  // Look for the size of the last dimension of the results array
451
  const auto& results_arr = tally->results();
252✔
452
  const int results_dim = static_cast<int>(results_arr.shape()[2]);
252✔
453
  std::array<int, 5> shape = {1, 1, 1, tally->n_scores(), results_dim};
252✔
454

455
  // set the shape for the filters applied on the tally
456
  for (int i = 0; i < tally->filters().size(); i++) {
964✔
457
    const auto& filter = model::tally_filters[tally->filters(i)];
712✔
458
    shape[i] = filter->n_bins();
712✔
459
  }
460

461
  // build the transpose information to re-order data according to filter type
462
  std::array<int, 5> transpose = {0, 1, 2, 3, 4};
252✔
463

464
  // track our filter types and where we've added new ones
465
  std::vector<FilterType> filter_types = tally->filter_types();
252✔
466

467
  // assign other filter types to dummy positions if needed
468
  if (!tally->has_filter(FilterType::PARTICLE))
252✔
469
    filter_types.push_back(FilterType::PARTICLE);
22✔
470

471
  if (!tally->has_filter(FilterType::ENERGY))
252✔
472
    filter_types.push_back(FilterType::ENERGY);
22✔
473

474
  // particle axis mapping
475
  transpose[0] =
252✔
476
    std::find(filter_types.begin(), filter_types.end(), FilterType::PARTICLE) -
252✔
477
    filter_types.begin();
252✔
478

479
  // energy axis mapping
480
  transpose[1] =
252✔
481
    std::find(filter_types.begin(), filter_types.end(), FilterType::ENERGY) -
252✔
482
    filter_types.begin();
252✔
483

484
  // mesh axis mapping
485
  transpose[2] =
252✔
486
    std::find(filter_types.begin(), filter_types.end(), FilterType::MESH) -
252✔
487
    filter_types.begin();
252✔
488

489
  // get a fully reshaped view of the tally according to tally ordering of
490
  // filters
491
  auto tally_values = xt::reshape_view(results_arr, shape);
252✔
492

493
  // get a that is (particle, energy, mesh, scores, values)
494
  auto transposed_view = xt::transpose(tally_values, transpose);
252✔
495

496
  // determine the dimension and index of the particle data
497
  int particle_idx = 0;
252✔
498
  if (tally->has_filter(FilterType::PARTICLE)) {
252✔
499
    // get the particle filter
500
    auto pf = tally->get_filter<ParticleFilter>();
230✔
501
    const auto& particles = pf->particles();
230✔
502

503
    // find the index of the particle that matches these weight windows
504
    auto p_it =
505
      std::find(particles.begin(), particles.end(), this->particle_type_);
230✔
506
    // if the particle filter doesn't have particle data for the particle
507
    // used on this weight windows instance, report an error
508
    if (p_it == particles.end()) {
230!
509
      auto msg = fmt::format("Particle type '{}' not present on Filter {} for "
510
                             "Tally {} used to update WeightWindows {}",
511
        this->particle_type_.str(), pf->id(), tally->id(), this->id());
×
512
      fatal_error(msg);
×
513
    }
×
514

515
    // use the index of the particle in the filter to down-select data later
516
    particle_idx = p_it - particles.begin();
230✔
517
  }
518

519
  // down-select data based on particle and score
520
  auto sum = xt::dynamic_view(
1,260✔
521
    transposed_view, {particle_idx, xt::all(), xt::all(), score_index,
504✔
522
                       static_cast<int>(TallyResult::SUM)});
1,008✔
523
  auto sum_sq = xt::dynamic_view(
1,260✔
524
    transposed_view, {particle_idx, xt::all(), xt::all(), score_index,
504✔
525
                       static_cast<int>(TallyResult::SUM_SQ)});
1,008✔
526
  int n = tally->n_realizations_;
252✔
527

528
  //////////////////////////////////////////////
529
  //
530
  // Assign new weight windows
531
  //
532
  // Use references to the existing weight window data
533
  // to store and update the values
534
  //
535
  //////////////////////////////////////////////
536

537
  // up to this point the data arrays are views into the tally results (no
538
  // computation has been performed) now we'll switch references to the tally's
539
  // bounds to avoid allocating additional memory
540
  auto& new_bounds = this->lower_ww_;
252✔
541
  auto& rel_err = this->upper_ww_;
252✔
542

543
  // get mesh volumes
544
  auto mesh_vols = this->mesh()->volumes();
252✔
545

546
  // Calculate mean (new_bounds) and relative error
547
#pragma omp parallel for collapse(2) schedule(static)
140✔
548
  for (int e = 0; e < e_bins; e++) {
934✔
549
    for (int64_t m = 0; m < mesh_bins; m++) {
1,190,071✔
550
      // Calculate mean
551
      new_bounds(e, m) = sum(e, m) / n;
1,189,249✔
552
      // Calculate relative error
553
      if (sum(e, m) > 0.0) {
1,189,249✔
554
        double mean_val = new_bounds(e, m);
101,480✔
555
        double variance = (sum_sq(e, m) / n - mean_val * mean_val) / (n - 1);
101,480✔
556
        rel_err(e, m) = std::sqrt(variance) / mean_val;
101,480✔
557
      } else {
558
        rel_err(e, m) = INFTY;
1,087,769✔
559
      }
560
      if (value == "rel_err") {
1,189,249✔
561
        new_bounds(e, m) = 1.0 / rel_err(e, m);
345,000✔
562
      }
563
    }
564
  }
565

566
  // Divide by volume of mesh elements
567
#pragma omp parallel for collapse(2) schedule(static)
140✔
568
  for (int e = 0; e < e_bins; e++) {
934✔
569
    for (int64_t m = 0; m < mesh_bins; m++) {
1,190,071✔
570
      new_bounds(e, m) /= mesh_vols[m];
1,189,249✔
571
    }
572
  }
573

574
  if (method == WeightWindowUpdateMethod::MAGIC) {
252✔
575
    // For MAGIC, weight windows are proportional to the forward fluxes.
576
    // We normalize weight windows independently for each energy group.
577

578
    // Find group maximum and normalize (per energy group)
579
    for (int e = 0; e < e_bins; e++) {
1,870✔
580
      double group_max = 0.0;
1,716✔
581

582
      // Find maximum value across all elements in this energy group
583
#pragma omp parallel for schedule(static) reduction(max : group_max)
936✔
584
      for (int64_t m = 0; m < mesh_bins; m++) {
1,092,505✔
585
        if (new_bounds(e, m) > group_max) {
1,091,725✔
586
          group_max = new_bounds(e, m);
2,520✔
587
        }
588
      }
589

590
      // Normalize values in this energy group by the maximum value
591
      if (group_max > 0.0) {
1,716✔
592
        double norm_factor = 1.0 / (2.0 * group_max);
1,683✔
593
#pragma omp parallel for schedule(static)
918✔
594
        for (int64_t m = 0; m < mesh_bins; m++) {
1,091,590✔
595
          new_bounds(e, m) *= norm_factor;
1,090,825✔
596
        }
597
      }
598
    }
599
  } else {
600
    // For FW-CADIS, weight windows are inversely proportional to the adjoint
601
    // fluxes. We normalize the weight windows across all energy groups.
602
#pragma omp parallel for collapse(2) schedule(static)
56✔
603
    for (int e = 0; e < e_bins; e++) {
84✔
604
      for (int64_t m = 0; m < mesh_bins; m++) {
97,566✔
605
        // Take the inverse, but are careful not to divide by zero
606
        if (new_bounds(e, m) != 0.0) {
97,524✔
607
          new_bounds(e, m) = 1.0 / new_bounds(e, m);
69,660✔
608
        } else {
609
          new_bounds(e, m) = 0.0;
27,864!
610
        }
611
      }
612
    }
613

614
    // Find the maximum value across all elements
615
    double max_val = 0.0;
98✔
616
#pragma omp parallel for collapse(2) schedule(static) reduction(max : max_val)
56✔
617
    for (int e = 0; e < e_bins; e++) {
84✔
618
      for (int64_t m = 0; m < mesh_bins; m++) {
97,566✔
619
        if (new_bounds(e, m) > max_val) {
97,524✔
620
          max_val = new_bounds(e, m);
405✔
621
        }
622
      }
623
    }
624

625
    // Parallel normalization
626
    if (max_val > 0.0) {
98✔
627
      double norm_factor = 1.0 / (2.0 * max_val);
68✔
628
#pragma omp parallel for collapse(2) schedule(static)
38✔
629
      for (int e = 0; e < e_bins; e++) {
60✔
630
        for (int64_t m = 0; m < mesh_bins; m++) {
69,690✔
631
          new_bounds(e, m) *= norm_factor;
69,660✔
632
        }
633
      }
634
    }
635
  }
636

637
  // Final processing
638
#pragma omp parallel for collapse(2) schedule(static)
140✔
639
  for (int e = 0; e < e_bins; e++) {
934✔
640
    for (int64_t m = 0; m < mesh_bins; m++) {
1,190,071✔
641
      // Values where the mean is zero should be ignored
642
      if (sum(e, m) <= 0.0) {
1,189,249✔
643
        new_bounds(e, m) = -1.0;
1,087,769✔
644
      }
645
      // Values where the relative error is higher than the threshold should be
646
      // ignored
647
      else if (rel_err(e, m) > threshold) {
101,480✔
648
        new_bounds(e, m) = -1.0;
1,420✔
649
      }
650
      // Set the upper bounds
651
      upper_ww_(e, m) = ratio * lower_ww_(e, m);
1,189,249✔
652
    }
653
  }
654
}
252✔
655

656
void WeightWindows::check_tally_update_compatibility(const Tally* tally)
252✔
657
{
658
  // define the set of allowed filters for the tally
659
  const std::set<FilterType> allowed_filters = {
660
    FilterType::MESH, FilterType::ENERGY, FilterType::PARTICLE};
252✔
661

662
  // retrieve a mapping of filter type to filter index for the tally
663
  auto filter_indices = tally->filter_indices();
252✔
664

665
  // a mesh filter is required for a tally used to update weight windows
666
  if (!filter_indices.count(FilterType::MESH)) {
252!
667
    fatal_error(
×
668
      "A mesh filter is required for a tally to update weight window bounds");
669
  }
670

671
  // ensure the mesh filter is using the same mesh as this weight window object
672
  auto mesh_filter = tally->get_filter<MeshFilter>();
252✔
673

674
  // make sure that all of the filters present on the tally are allowed
675
  for (auto filter_pair : filter_indices) {
964✔
676
    if (allowed_filters.find(filter_pair.first) == allowed_filters.end()) {
712!
677
      fatal_error(fmt::format("Invalid filter type '{}' found on tally "
×
678
                              "used for weight window generation.",
679
        model::tally_filters[tally->filters(filter_pair.second)]->type_str()));
×
680
    }
681
  }
682

683
  if (mesh_filter->mesh() != mesh_idx_) {
252!
684
    int32_t mesh_filter_id = model::meshes[mesh_filter->mesh()]->id();
×
685
    int32_t ww_mesh_id = model::meshes[this->mesh_idx_]->id();
×
686
    fatal_error(fmt::format("Mesh filter {} uses a different mesh ({}) than "
×
687
                            "weight window {} mesh ({})",
688
      mesh_filter->id(), mesh_filter_id, id_, ww_mesh_id));
×
689
  }
690

691
  // if an energy filter exists, make sure the energy grid matches that of this
692
  // weight window object
693
  if (auto energy_filter = tally->get_filter<EnergyFilter>()) {
252✔
694
    std::vector<double> filter_bins = energy_filter->bins();
230✔
695
    std::set<double> filter_e_bounds(
696
      energy_filter->bins().begin(), energy_filter->bins().end());
230✔
697
    if (filter_e_bounds.size() != energy_bounds().size()) {
230!
698
      fatal_error(
×
699
        fmt::format("Energy filter {} does not have the same number of energy "
×
700
                    "bounds ({}) as weight window object {} ({})",
701
          energy_filter->id(), filter_e_bounds.size(), id_,
×
702
          energy_bounds().size()));
×
703
    }
704

705
    for (auto e : energy_bounds()) {
2,252✔
706
      if (filter_e_bounds.count(e) == 0) {
2,022!
707
        fatal_error(fmt::format(
×
708
          "Energy bounds of filter {} and weight windows {} do not match",
709
          energy_filter->id(), id_));
×
710
      }
711
    }
712
  }
230✔
713
}
252✔
714

715
void WeightWindows::to_hdf5(hid_t group) const
134✔
716
{
717
  hid_t ww_group = create_group(group, fmt::format("weight_windows_{}", id()));
268✔
718

719
  write_dataset(ww_group, "mesh", this->mesh()->id());
134✔
720
  write_dataset(ww_group, "particle_type", particle_type_.str());
134✔
721
  write_dataset(ww_group, "energy_bounds", energy_bounds_);
134✔
722
  write_dataset(ww_group, "lower_ww_bounds", lower_ww_);
134✔
723
  write_dataset(ww_group, "upper_ww_bounds", upper_ww_);
134✔
724
  write_dataset(ww_group, "survival_ratio", survival_ratio_);
134✔
725
  write_dataset(ww_group, "max_lower_bound_ratio", max_lb_ratio_);
134✔
726
  write_dataset(ww_group, "max_split", max_split_);
134✔
727
  write_dataset(ww_group, "weight_cutoff", weight_cutoff_);
134✔
728

729
  close_group(ww_group);
134✔
730
}
134✔
731

732
WeightWindowsGenerator::WeightWindowsGenerator(pugi::xml_node node)
82✔
733
{
734
  // read information from the XML node
735
  int32_t mesh_id = std::stoi(get_node_value(node, "mesh"));
82✔
736
  int32_t mesh_idx = model::mesh_map[mesh_id];
82✔
737
  max_realizations_ = std::stoi(get_node_value(node, "max_realizations"));
82✔
738

739
  int32_t active_batches = settings::n_batches - settings::n_inactive;
82✔
740
  if (max_realizations_ > active_batches) {
82✔
741
    auto msg =
742
      fmt::format("The maximum number of specified tally realizations ({}) is "
743
                  "greater than the number of active batches ({}).",
744
        max_realizations_, active_batches);
31✔
745
    warning(msg);
17✔
746
  }
17✔
747
  auto tmp_str = get_node_value(node, "particle_type", false, true);
82✔
748
  auto particle_type = ParticleType {tmp_str};
82✔
749

750
  update_interval_ = std::stoi(get_node_value(node, "update_interval"));
82✔
751
  on_the_fly_ = get_node_value_bool(node, "on_the_fly");
82✔
752

753
  std::vector<double> e_bounds;
82✔
754
  if (check_for_node(node, "energy_bounds")) {
82✔
755
    e_bounds = get_node_array<double>(node, "energy_bounds");
23✔
756
  } else {
757
    int p_type = particle_type.transport_index();
59✔
758
    if (p_type == C_NONE) {
59!
759
      fatal_error("Weight windows particle is not supported for transport.");
×
760
    }
761
    e_bounds.push_back(data::energy_min[p_type]);
59✔
762
    e_bounds.push_back(data::energy_max[p_type]);
59✔
763
  }
764

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

785
  // parse non-default update parameters if specified
786
  if (check_for_node(node, "update_parameters")) {
82✔
787
    pugi::xml_node params_node = node.child("update_parameters");
22✔
788
    if (check_for_node(params_node, "value"))
22!
789
      tally_value_ = get_node_value(params_node, "value");
22✔
790
    if (check_for_node(params_node, "threshold"))
22!
791
      threshold_ = std::stod(get_node_value(params_node, "threshold"));
22✔
792
    if (check_for_node(params_node, "ratio")) {
22!
793
      ratio_ = std::stod(get_node_value(params_node, "ratio"));
22✔
794
    }
795
  }
796

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

811
  // create a matching weight windows object
812
  auto wws = WeightWindows::create();
82✔
813
  ww_idx_ = wws->index();
82✔
814
  wws->set_mesh(mesh_idx);
82✔
815
  if (e_bounds.size() > 0)
82!
816
    wws->set_energy_bounds(e_bounds);
82✔
817
  wws->set_particle_type(particle_type);
82✔
818
  wws->set_defaults();
82✔
819
}
82✔
820

821
void WeightWindowsGenerator::create_tally()
82✔
822
{
823
  const auto& wws = variance_reduction::weight_windows[ww_idx_];
82✔
824

825
  // create a tally based on the WWG information
826
  Tally* ww_tally = Tally::create();
82✔
827
  tally_idx_ = model::tally_map[ww_tally->id()];
82✔
828
  ww_tally->set_scores({"flux"});
164!
829

830
  int32_t mesh_id = wws->mesh()->id();
82✔
831
  int32_t mesh_idx = model::mesh_map.at(mesh_id);
82✔
832
  // see if there's already a mesh filter using this mesh
833
  bool found_mesh_filter = false;
82✔
834
  for (const auto& f : model::tally_filters) {
259✔
835
    if (f->type() == FilterType::MESH) {
188✔
836
      const auto* mesh_filter = dynamic_cast<MeshFilter*>(f.get());
11!
837
      if (mesh_filter->mesh() == mesh_idx && !mesh_filter->translated() &&
22!
838
          !mesh_filter->rotated()) {
11!
839
        ww_tally->add_filter(f.get());
11✔
840
        found_mesh_filter = true;
11✔
841
        break;
11✔
842
      }
843
    }
844
  }
845

846
  if (!found_mesh_filter) {
82✔
847
    auto mesh_filter = Filter::create("mesh");
71✔
848
    openmc_mesh_filter_set_mesh(mesh_filter->index(), model::mesh_map[mesh_id]);
71✔
849
    ww_tally->add_filter(mesh_filter);
71✔
850
  }
851

852
  const auto& e_bounds = wws->energy_bounds();
82✔
853
  if (e_bounds.size() > 0) {
82!
854
    auto energy_filter = Filter::create("energy");
82✔
855
    openmc_energy_filter_set_bins(
164✔
856
      energy_filter->index(), e_bounds.size(), e_bounds.data());
82✔
857
    ww_tally->add_filter(energy_filter);
82✔
858
  }
859

860
  // add a particle filter
861
  auto particle_type = wws->particle_type();
82✔
862
  auto particle_filter = Filter::create("particle");
82✔
863
  auto pf = dynamic_cast<ParticleFilter*>(particle_filter);
82!
864
  pf->set_particles({&particle_type, 1});
82✔
865
  ww_tally->add_filter(particle_filter);
82✔
866
}
82✔
867

868
void WeightWindowsGenerator::update() const
2,417✔
869
{
870
  const auto& wws = variance_reduction::weight_windows[ww_idx_];
2,417✔
871

872
  Tally* tally = model::tallies[tally_idx_].get();
2,417✔
873

874
  // If in random ray mode, only update on the last batch
875
  if (settings::solver_type == SolverType::RANDOM_RAY) {
2,417✔
876
    if (simulation::current_batch != settings::n_batches) {
2,252✔
877
      return;
2,154✔
878
    }
879
    // If in Monte Carlo mode and beyond the number of max realizations or
880
    // not at the correct update interval, skip the update
881
  } else if (max_realizations_ < tally->n_realizations_ ||
165✔
882
             tally->n_realizations_ % update_interval_ != 0) {
33!
883
    return;
132✔
884
  }
885

886
  wws->update_weights(tally, tally_value_, threshold_, ratio_, method_);
131✔
887

888
  // if we're not doing on the fly generation, reset the tally results once
889
  // we're done with the update
890
  if (!on_the_fly_)
131!
891
    tally->reset();
×
892

893
  // TODO: deactivate or remove tally once weight window generation is
894
  // complete
895
}
896

897
//==============================================================================
898
// Non-member functions
899
//==============================================================================
900

901
WeightWindow search_weight_window(const Particle& p)
82,242,218✔
902
{
903
  // TODO: this is a linear search - should do something more clever
904
  int mesh_bin;
905
  WeightWindow weight_window;
82,242,218✔
906
  for (const auto& ww : variance_reduction::weight_windows) {
115,653,031✔
907
    weight_window = ww->get_weight_window(p);
90,891,848✔
908
    if (weight_window.is_valid())
90,891,848✔
909
      return weight_window;
57,481,035✔
910
  }
911
  return {};
24,761,183✔
912
}
913

914
void apply_weight_windows(Particle& p)
167,087,893✔
915
{
916
  if (!settings::weight_windows_on)
167,087,893✔
917
    return;
166,343,152✔
918

919
  // WW on photon and neutron only
920
  if (!p.type().is_neutron() && !p.type().is_photon())
748,418!
NEW
921
    return;
×
922

923
  // skip dead or no energy
924
  if (p.E() <= 0 || !p.alive())
748,418!
925
    return;
3,677✔
926

927
  auto ww = search_weight_window(p);
744,741✔
928
  if (ww.is_valid()) {
744,741✔
929
    apply_weight_window(p, ww);
677,508✔
930
  } else {
931
    if (p.wgt_ww_born() == -1.0)
67,233✔
932
      p.wgt_ww_born() = 1.0;
65,802✔
933
  }
934
}
935

936
void apply_weight_window(Particle& p, WeightWindow weight_window)
71,062,187✔
937
{
938
  if (!weight_window.is_valid())
71,062,187✔
939
    return;
13,581,152✔
940

941
  // skip dead or no energy
942
  if (p.E() <= 0 || !p.alive())
57,481,035✔
943
    return;
3,595,950✔
944

945
  // If particle has not yet had its birth weight window value set, set it to
946
  // the current weight window.
947
  if (p.wgt_ww_born() == -1.0)
53,885,085✔
948
    p.wgt_ww_born() =
671,878✔
949
      (weight_window.lower_weight + weight_window.upper_weight) / 2;
671,878✔
950

951
  // Normalize weight windows based on particle's starting weight
952
  // and the value of the weight window the particle was born in.
953
  weight_window.scale(p.wgt_born() / p.wgt_ww_born());
53,885,085✔
954

955
  // get the paramters
956
  double weight = p.wgt();
53,885,085✔
957

958
  // first check to see if particle should be killed for weight cutoff
959
  if (p.wgt() < weight_window.weight_cutoff) {
53,885,085!
NEW
960
    p.wgt() = 0.0;
×
NEW
961
    return;
×
962
  }
963

964
  // check if particle is far above current weight window
965
  // only do this if the factor is not already set on the particle and a
966
  // maximum lower bound ratio is specified
967
  if (p.ww_factor() == 0.0 && weight_window.max_lb_ratio > 1.0 &&
53,887,923✔
968
      p.wgt() > weight_window.lower_weight * weight_window.max_lb_ratio) {
2,838!
969
    p.ww_factor() =
2,838✔
970
      p.wgt() / (weight_window.lower_weight * weight_window.max_lb_ratio);
2,838✔
971
  }
972

973
  // move weight window closer to the particle weight if needed
974
  if (p.ww_factor() > 1.0)
53,885,085✔
975
    weight_window.scale(p.ww_factor());
1,356,443✔
976

977
  // if particle's weight is above the weight window split until they are within
978
  // the window
979
  if (weight > weight_window.upper_weight) {
53,885,085✔
980
    // do not further split the particle if above the limit
981
    if (p.n_split() >= settings::max_history_splits)
13,449,367✔
982
      return;
12,137,323✔
983

984
    double n_split = std::ceil(weight / weight_window.upper_weight);
1,312,044✔
985
    double max_split = weight_window.max_split;
1,312,044✔
986
    n_split = std::min(n_split, max_split);
1,312,044✔
987

988
    p.n_split() += n_split;
1,312,044✔
989

990
    // Create secondaries and divide weight among all particles
991
    int i_split = std::round(n_split);
1,312,044✔
992
    for (int l = 0; l < i_split - 1; l++) {
5,399,182✔
993
      p.split(weight / n_split);
4,087,138✔
994
    }
995
    // remaining weight is applied to current particle
996
    p.wgt() = weight / n_split;
1,312,044✔
997

998
  } else if (weight <= weight_window.lower_weight) {
40,435,718✔
999
    // if the particle weight is below the window, play Russian roulette
1000
    double weight_survive =
1001
      std::min(weight * weight_window.max_split, weight_window.survival_weight);
1,267,630✔
1002
    russian_roulette(p, weight_survive);
1,267,630✔
1003
  } // else particle is in the window, continue as normal
1004
}
1005

1006
void free_memory_weight_windows()
8,186✔
1007
{
1008
  variance_reduction::ww_map.clear();
8,186✔
1009
  variance_reduction::weight_windows.clear();
8,186✔
1010
}
8,186✔
1011

1012
void finalize_variance_reduction()
8,040✔
1013
{
1014
  for (const auto& wwg : variance_reduction::weight_windows_generators) {
8,122✔
1015
    wwg->create_tally();
82✔
1016
  }
1017
}
8,040✔
1018

1019
//==============================================================================
1020
// C API
1021
//==============================================================================
1022

1023
int verify_ww_index(int32_t index)
1,991✔
1024
{
1025
  if (index < 0 || index >= variance_reduction::weight_windows.size()) {
1,991!
1026
    set_errmsg(fmt::format("Index '{}' for weight windows is invalid", index));
×
1027
    return OPENMC_E_OUT_OF_BOUNDS;
×
1028
  }
1029
  return 0;
1,991✔
1030
}
1031

1032
extern "C" int openmc_get_weight_windows_index(int32_t id, int32_t* idx)
165✔
1033
{
1034
  auto it = variance_reduction::ww_map.find(id);
165✔
1035
  if (it == variance_reduction::ww_map.end()) {
165!
1036
    set_errmsg(fmt::format("No weight windows exist with ID={}", id));
×
1037
    return OPENMC_E_INVALID_ID;
×
1038
  }
1039

1040
  *idx = it->second;
165✔
1041
  return 0;
165✔
1042
}
1043

1044
extern "C" int openmc_weight_windows_get_id(int32_t index, int32_t* id)
517✔
1045
{
1046
  if (int err = verify_ww_index(index))
517!
1047
    return err;
×
1048

1049
  const auto& wws = variance_reduction::weight_windows.at(index);
517✔
1050
  *id = wws->id();
517✔
1051
  return 0;
517✔
1052
}
1053

1054
extern "C" int openmc_weight_windows_set_id(int32_t index, int32_t id)
154✔
1055
{
1056
  if (int err = verify_ww_index(index))
154!
1057
    return err;
×
1058

1059
  const auto& wws = variance_reduction::weight_windows.at(index);
154✔
1060
  wws->set_id(id);
154✔
1061
  return 0;
154✔
1062
}
1063

1064
extern "C" int openmc_weight_windows_update_magic(int32_t ww_idx,
121✔
1065
  int32_t tally_idx, const char* value, double threshold, double ratio)
1066
{
1067
  if (int err = verify_ww_index(ww_idx))
121!
1068
    return err;
×
1069

1070
  if (tally_idx < 0 || tally_idx >= model::tallies.size()) {
121!
1071
    set_errmsg(fmt::format("Index '{}' for tally is invalid", tally_idx));
×
1072
    return OPENMC_E_OUT_OF_BOUNDS;
×
1073
  }
1074

1075
  // get the requested tally
1076
  const Tally* tally = model::tallies.at(tally_idx).get();
121✔
1077

1078
  // get the WeightWindows object
1079
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
121✔
1080

1081
  wws->update_weights(tally, value, threshold, ratio);
121✔
1082

1083
  return 0;
121✔
1084
}
1085

1086
extern "C" int openmc_weight_windows_set_mesh(int32_t ww_idx, int32_t mesh_idx)
154✔
1087
{
1088
  if (int err = verify_ww_index(ww_idx))
154!
1089
    return err;
×
1090
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
154✔
1091
  wws->set_mesh(mesh_idx);
154✔
1092
  return 0;
154✔
1093
}
1094

1095
extern "C" int openmc_weight_windows_get_mesh(int32_t ww_idx, int32_t* mesh_idx)
11✔
1096
{
1097
  if (int err = verify_ww_index(ww_idx))
11!
1098
    return err;
×
1099
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
11✔
1100
  *mesh_idx = model::mesh_map.at(wws->mesh()->id());
11✔
1101
  return 0;
11✔
1102
}
1103

1104
extern "C" int openmc_weight_windows_set_energy_bounds(
132✔
1105
  int32_t ww_idx, double* e_bounds, size_t e_bounds_size)
1106
{
1107
  if (int err = verify_ww_index(ww_idx))
132!
1108
    return err;
×
1109
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
132✔
1110
  wws->set_energy_bounds({e_bounds, e_bounds_size});
132✔
1111
  return 0;
132✔
1112
}
1113

1114
extern "C" int openmc_weight_windows_get_energy_bounds(
11✔
1115
  int32_t ww_idx, const double** e_bounds, size_t* e_bounds_size)
1116
{
1117
  if (int err = verify_ww_index(ww_idx))
11!
1118
    return err;
×
1119
  const auto& wws = variance_reduction::weight_windows[ww_idx].get();
11✔
1120
  *e_bounds = wws->energy_bounds().data();
11✔
1121
  *e_bounds_size = wws->energy_bounds().size();
11✔
1122
  return 0;
11✔
1123
}
1124

1125
extern "C" int openmc_weight_windows_set_particle(
176✔
1126
  int32_t index, int32_t particle)
1127
{
1128
  if (int err = verify_ww_index(index))
176!
1129
    return err;
×
1130

1131
  const auto& wws = variance_reduction::weight_windows.at(index);
176✔
1132
  wws->set_particle_type(ParticleType {particle});
176✔
1133
  return 0;
176✔
1134
}
1135

1136
extern "C" int openmc_weight_windows_get_particle(
44✔
1137
  int32_t index, int32_t* particle)
1138
{
1139
  if (int err = verify_ww_index(index))
44!
1140
    return err;
×
1141

1142
  const auto& wws = variance_reduction::weight_windows.at(index);
44✔
1143
  *particle = wws->particle_type().pdg_number();
44✔
1144
  return 0;
44✔
1145
}
1146

1147
extern "C" int openmc_weight_windows_get_bounds(int32_t index,
484✔
1148
  const double** lower_bounds, const double** upper_bounds, size_t* size)
1149
{
1150
  if (int err = verify_ww_index(index))
484!
1151
    return err;
×
1152

1153
  const auto& wws = variance_reduction::weight_windows[index];
484✔
1154
  *size = wws->lower_ww_bounds().size();
484✔
1155
  *lower_bounds = wws->lower_ww_bounds().data();
484✔
1156
  *upper_bounds = wws->upper_ww_bounds().data();
484✔
1157
  return 0;
484✔
1158
}
1159

1160
extern "C" int openmc_weight_windows_set_bounds(int32_t index,
11✔
1161
  const double* lower_bounds, const double* upper_bounds, size_t size)
1162
{
1163
  if (int err = verify_ww_index(index))
11!
1164
    return err;
×
1165

1166
  const auto& wws = variance_reduction::weight_windows[index];
11✔
1167
  wws->set_bounds({lower_bounds, size}, {upper_bounds, size});
11✔
1168
  return 0;
11✔
1169
}
1170

1171
extern "C" int openmc_weight_windows_get_survival_ratio(
33✔
1172
  int32_t index, double* ratio)
1173
{
1174
  if (int err = verify_ww_index(index))
33!
1175
    return err;
×
1176
  const auto& wws = variance_reduction::weight_windows[index];
33✔
1177
  *ratio = wws->survival_ratio();
33✔
1178
  return 0;
33✔
1179
}
1180

1181
extern "C" int openmc_weight_windows_set_survival_ratio(
11✔
1182
  int32_t index, double ratio)
1183
{
1184
  if (int err = verify_ww_index(index))
11!
1185
    return err;
×
1186
  const auto& wws = variance_reduction::weight_windows[index];
11✔
1187
  wws->survival_ratio() = ratio;
11✔
1188
  std::cout << "Survival ratio: " << wws->survival_ratio() << std::endl;
11✔
1189
  return 0;
11✔
1190
}
1191

1192
extern "C" int openmc_weight_windows_get_max_lower_bound_ratio(
33✔
1193
  int32_t index, double* lb_ratio)
1194
{
1195
  if (int err = verify_ww_index(index))
33!
1196
    return err;
×
1197
  const auto& wws = variance_reduction::weight_windows[index];
33✔
1198
  *lb_ratio = wws->max_lower_bound_ratio();
33✔
1199
  return 0;
33✔
1200
}
1201

1202
extern "C" int openmc_weight_windows_set_max_lower_bound_ratio(
11✔
1203
  int32_t index, double lb_ratio)
1204
{
1205
  if (int err = verify_ww_index(index))
11!
1206
    return err;
×
1207
  const auto& wws = variance_reduction::weight_windows[index];
11✔
1208
  wws->max_lower_bound_ratio() = lb_ratio;
11✔
1209
  return 0;
11✔
1210
}
1211

1212
extern "C" int openmc_weight_windows_get_weight_cutoff(
33✔
1213
  int32_t index, double* cutoff)
1214
{
1215
  if (int err = verify_ww_index(index))
33!
1216
    return err;
×
1217
  const auto& wws = variance_reduction::weight_windows[index];
33✔
1218
  *cutoff = wws->weight_cutoff();
33✔
1219
  return 0;
33✔
1220
}
1221

1222
extern "C" int openmc_weight_windows_set_weight_cutoff(
11✔
1223
  int32_t index, double cutoff)
1224
{
1225
  if (int err = verify_ww_index(index))
11!
1226
    return err;
×
1227
  const auto& wws = variance_reduction::weight_windows[index];
11✔
1228
  wws->weight_cutoff() = cutoff;
11✔
1229
  return 0;
11✔
1230
}
1231

1232
extern "C" int openmc_weight_windows_get_max_split(
33✔
1233
  int32_t index, int* max_split)
1234
{
1235
  if (int err = verify_ww_index(index))
33!
1236
    return err;
×
1237
  const auto& wws = variance_reduction::weight_windows[index];
33✔
1238
  *max_split = wws->max_split();
33✔
1239
  return 0;
33✔
1240
}
1241

1242
extern "C" int openmc_weight_windows_set_max_split(int32_t index, int max_split)
11✔
1243
{
1244
  if (int err = verify_ww_index(index))
11!
1245
    return err;
×
1246
  const auto& wws = variance_reduction::weight_windows[index];
11✔
1247
  wws->max_split() = max_split;
11✔
1248
  return 0;
11✔
1249
}
1250

1251
extern "C" int openmc_extend_weight_windows(
154✔
1252
  int32_t n, int32_t* index_start, int32_t* index_end)
1253
{
1254
  if (index_start)
154!
1255
    *index_start = variance_reduction::weight_windows.size();
154✔
1256
  if (index_end)
154!
1257
    *index_end = variance_reduction::weight_windows.size() + n - 1;
×
1258
  for (int i = 0; i < n; ++i)
308✔
1259
    variance_reduction::weight_windows.push_back(make_unique<WeightWindows>());
154✔
1260
  return 0;
154✔
1261
}
1262

1263
extern "C" size_t openmc_weight_windows_size()
154✔
1264
{
1265
  return variance_reduction::weight_windows.size();
154✔
1266
}
1267

1268
extern "C" int openmc_weight_windows_export(const char* filename)
164✔
1269
{
1270

1271
  if (!mpi::master)
164✔
1272
    return 0;
30✔
1273

1274
  std::string name = filename ? filename : "weight_windows.h5";
268✔
1275

1276
  write_message(fmt::format("Exporting weight windows to {}...", name), 5);
134✔
1277

1278
  hid_t ww_file = file_open(name, 'w');
134✔
1279

1280
  // Write file type
1281
  write_attribute(ww_file, "filetype", "weight_windows");
134✔
1282

1283
  // Write revisiion number for state point file
1284
  write_attribute(ww_file, "version", VERSION_WEIGHT_WINDOWS);
134✔
1285

1286
  hid_t weight_windows_group = create_group(ww_file, "weight_windows");
134✔
1287

1288
  hid_t mesh_group = create_group(ww_file, "meshes");
134✔
1289

1290
  std::vector<int32_t> mesh_ids;
134✔
1291
  std::vector<int32_t> ww_ids;
134✔
1292
  for (const auto& ww : variance_reduction::weight_windows) {
268✔
1293

1294
    ww->to_hdf5(weight_windows_group);
134✔
1295
    ww_ids.push_back(ww->id());
134✔
1296

1297
    // if the mesh has already been written, move on
1298
    int32_t mesh_id = ww->mesh()->id();
134✔
1299
    if (std::find(mesh_ids.begin(), mesh_ids.end(), mesh_id) != mesh_ids.end())
134!
1300
      continue;
×
1301

1302
    mesh_ids.push_back(mesh_id);
134✔
1303
    ww->mesh()->to_hdf5(mesh_group);
134✔
1304
  }
1305

1306
  write_attribute(mesh_group, "n_meshes", mesh_ids.size());
134✔
1307
  write_attribute(mesh_group, "ids", mesh_ids);
134✔
1308
  close_group(mesh_group);
134✔
1309

1310
  write_attribute(weight_windows_group, "n_weight_windows", ww_ids.size());
134✔
1311
  write_attribute(weight_windows_group, "ids", ww_ids);
134✔
1312
  close_group(weight_windows_group);
134✔
1313

1314
  file_close(ww_file);
134✔
1315

1316
  return 0;
134✔
1317
}
134✔
1318

1319
extern "C" int openmc_weight_windows_import(const char* filename)
11✔
1320
{
1321
  std::string name = filename ? filename : "weight_windows.h5";
11!
1322

1323
  if (mpi::master)
11!
1324
    write_message(fmt::format("Importing weight windows from {}...", name), 5);
11✔
1325

1326
  if (!file_exists(name)) {
11!
1327
    set_errmsg(fmt::format("File '{}' does not exist", name));
×
1328
  }
1329

1330
  hid_t ww_file = file_open(name, 'r');
11✔
1331

1332
  // Check that filetype is correct
1333
  std::string filetype;
11✔
1334
  read_attribute(ww_file, "filetype", filetype);
11✔
1335
  if (filetype != "weight_windows") {
11!
1336
    file_close(ww_file);
×
1337
    set_errmsg(fmt::format("File '{}' is not a weight windows file.", name));
×
1338
    return OPENMC_E_INVALID_ARGUMENT;
×
1339
  }
1340

1341
  // Check that the file version is compatible
1342
  std::array<int, 2> file_version;
1343
  read_attribute(ww_file, "version", file_version);
11✔
1344
  if (file_version[0] != VERSION_WEIGHT_WINDOWS[0]) {
11!
1345
    std::string err_msg =
1346
      fmt::format("File '{}' has version {} which is incompatible with the "
1347
                  "expected version ({}).",
1348
        name, file_version, VERSION_WEIGHT_WINDOWS);
×
1349
    set_errmsg(err_msg);
×
1350
    return OPENMC_E_INVALID_ARGUMENT;
×
1351
  }
×
1352

1353
  hid_t weight_windows_group = open_group(ww_file, "weight_windows");
11✔
1354

1355
  hid_t mesh_group = open_group(ww_file, "meshes");
11✔
1356

1357
  read_meshes(mesh_group);
11✔
1358

1359
  std::vector<std::string> names = group_names(weight_windows_group);
11✔
1360

1361
  for (const auto& name : names) {
22✔
1362
    WeightWindows::from_hdf5(weight_windows_group, name);
11✔
1363
  }
1364

1365
  close_group(weight_windows_group);
11✔
1366

1367
  file_close(ww_file);
11✔
1368

1369
  return 0;
11✔
1370
}
11✔
1371

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