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

openmc-dev / openmc / 20278909785

16 Dec 2025 06:41PM UTC coverage: 81.834% (-0.09%) from 81.92%
20278909785

Pull #3493

github

web-flow
Merge 9dc7c7a58 into bbfa18d72
Pull Request #3493: Implement vector fitting to replace external `vectfit` package

17020 of 23572 branches covered (72.2%)

Branch coverage included in aggregate %.

188 of 207 new or added lines in 3 files covered. (90.82%)

3101 existing lines in 56 files now uncovered.

54979 of 64410 relevant lines covered (85.36%)

41388074.54 hits per line

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

78.88
/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
// Non-member functions
54
//==============================================================================
55

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

61
  // WW on photon and neutron only
62
  if (p.type() != ParticleType::neutron && p.type() != ParticleType::photon)
74,787,601✔
63
    return;
9,934,357✔
64

65
  // skip dead or no energy
66
  if (p.E() <= 0 || !p.alive())
64,853,244✔
67
    return;
3,721,051✔
68

69
  bool in_domain = false;
61,132,193✔
70
  // TODO: this is a linear search - should do something more clever
71
  WeightWindow weight_window;
61,132,193✔
72
  for (const auto& ww : variance_reduction::weight_windows) {
76,942,008✔
73
    weight_window = ww->get_weight_window(p);
64,812,753✔
74
    if (weight_window.is_valid())
64,812,753✔
75
      break;
49,002,938✔
76
  }
77

78
  // If particle has not yet had its birth weight window value set, set it to
79
  // the current weight window (or 1.0 if not born in a weight window).
80
  if (p.wgt_ww_born() == -1.0) {
61,132,193✔
81
    if (weight_window.is_valid()) {
670,780✔
82
      p.wgt_ww_born() =
610,960✔
83
        (weight_window.lower_weight + weight_window.upper_weight) / 2;
610,960✔
84
    } else {
85
      p.wgt_ww_born() = 1.0;
59,820✔
86
    }
87
  }
88

89
  // particle is not in any of the ww domains, do nothing
90
  if (!weight_window.is_valid())
61,132,193✔
91
    return;
12,129,255✔
92

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

97
  // get the paramters
98
  double weight = p.wgt();
49,002,938✔
99

100
  // first check to see if particle should be killed for weight cutoff
101
  if (p.wgt() < weight_window.weight_cutoff) {
49,002,938!
102
    p.wgt() = 0.0;
×
UNCOV
103
    return;
×
104
  }
105

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

115
  // move weight window closer to the particle weight if needed
116
  if (p.ww_factor() > 1.0)
49,002,938✔
117
    weight_window.scale(p.ww_factor());
1,233,130✔
118

119
  // if particle's weight is above the weight window split until they are within
120
  // the window
121
  if (weight > weight_window.upper_weight) {
49,002,938✔
122
    // do not further split the particle if above the limit
123
    if (p.n_split() >= settings::max_history_splits)
12,226,454✔
124
      return;
11,033,155✔
125

126
    double n_split = std::ceil(weight / weight_window.upper_weight);
1,193,299✔
127
    double max_split = weight_window.max_split;
1,193,299✔
128
    n_split = std::min(n_split, max_split);
1,193,299✔
129

130
    p.n_split() += n_split;
1,193,299✔
131

132
    // Create secondaries and divide weight among all particles
133
    int i_split = std::round(n_split);
1,193,299✔
134
    for (int l = 0; l < i_split - 1; l++) {
4,909,786✔
135
      p.split(weight / n_split);
3,716,487✔
136
    }
137
    // remaining weight is applied to current particle
138
    p.wgt() = weight / n_split;
1,193,299✔
139

140
  } else if (weight <= weight_window.lower_weight) {
36,776,484✔
141
    // if the particle weight is below the window, play Russian roulette
142
    double weight_survive =
143
      std::min(weight * weight_window.max_split, weight_window.survival_weight);
1,152,417✔
144
    russian_roulette(p, weight_survive);
1,152,417✔
145
  } // else particle is in the window, continue as normal
146
}
147

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

154
//==============================================================================
155
// WeightWindowSettings implementation
156
//==============================================================================
157

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

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

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

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

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

188
  // energy bounds
189
  if (check_for_node(node, "energy_bounds"))
83✔
190
    energy_bounds_ = get_node_array<double>(node, "energy_bounds");
69✔
191

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

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

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

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

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

228
  set_defaults();
83✔
229
}
83✔
230

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

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

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

251
  auto wws = WeightWindows::create();
10✔
252

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

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

259
  int32_t mesh_id;
260
  read_dataset(ww_group, "mesh", mesh_id);
10✔
261

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

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

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

278
  close_group(ww_group);
10✔
279

280
  return wws;
10✔
281
}
10✔
282

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

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

307
void WeightWindows::set_id(int32_t id)
446✔
308
{
309
  assert(id >= 0 || id == C_NONE);
355!
310

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

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

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

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

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

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

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

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

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

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

375
WeightWindow WeightWindows::get_weight_window(const Particle& p) const
64,812,753✔
376
{
377
  // check for particle type
378
  if (particle_type_ != p.type()) {
64,812,753✔
379
    return {};
3,520,760✔
380
  }
381

382
  // Get mesh index for particle's position
383
  const auto& mesh = this->mesh();
61,291,993✔
384
  int mesh_bin = mesh->get_bin(p.r());
61,291,993✔
385

386
  // particle is outside the weight window mesh
387
  if (mesh_bin < 0)
61,291,993✔
388
    return {};
14,588✔
389

390
  // particle energy
391
  double E = p.E();
61,277,405✔
392

393
  // check to make sure energy is in range, expects sorted energy values
394
  if (E < energy_bounds_.front() || E > energy_bounds_.back())
61,277,405!
395
    return {};
84,180✔
396

397
  // get the mesh bin in energy group
398
  int energy_bin =
399
    lower_bound_index(energy_bounds_.begin(), energy_bounds_.end(), E);
61,193,225✔
400

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

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

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

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

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

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

455
  // set new weight window values
456
  lower_ww_ = lower_bounds;
×
457
  upper_ww_ = upper_bounds;
×
UNCOV
458
}
×
459

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

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

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

479
  // set new weight window values
480
  xt::view(lower_ww_, xt::all()) =
186✔
481
    xt::adapt(lower_bounds.data(), lower_ww_.shape());
279✔
482
  xt::view(upper_ww_, xt::all()) =
186✔
483
    xt::adapt(upper_bounds.data(), upper_ww_.shape());
279✔
484
}
93✔
485

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

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

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

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

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

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

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

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

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

548
  // build a shape for a view of the tally results, this will always be
549
  // dimension 5 (3 filter dimensions, 1 score dimension, 1 results dimension)
550
  // Look for the size of the last dimension of the results array
551
  const auto& results_arr = tally->results();
226✔
552
  const int results_dim = static_cast<int>(results_arr.shape()[2]);
226✔
553
  std::array<int, 5> shape = {1, 1, 1, tally->n_scores(), results_dim};
226✔
554

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

561
  // build the transpose information to re-order data according to filter type
562
  std::array<int, 5> transpose = {0, 1, 2, 3, 4};
226✔
563

564
  // track our filter types and where we've added new ones
565
  std::vector<FilterType> filter_types = tally->filter_types();
226✔
566

567
  // assign other filter types to dummy positions if needed
568
  if (!tally->has_filter(FilterType::PARTICLE))
226✔
569
    filter_types.push_back(FilterType::PARTICLE);
20✔
570

571
  if (!tally->has_filter(FilterType::ENERGY))
226✔
572
    filter_types.push_back(FilterType::ENERGY);
20✔
573

574
  // particle axis mapping
575
  transpose[0] =
226✔
576
    std::find(filter_types.begin(), filter_types.end(), FilterType::PARTICLE) -
226✔
577
    filter_types.begin();
226✔
578

579
  // energy axis mapping
580
  transpose[1] =
226✔
581
    std::find(filter_types.begin(), filter_types.end(), FilterType::ENERGY) -
226✔
582
    filter_types.begin();
226✔
583

584
  // mesh axis mapping
585
  transpose[2] =
226✔
586
    std::find(filter_types.begin(), filter_types.end(), FilterType::MESH) -
226✔
587
    filter_types.begin();
226✔
588

589
  // get a fully reshaped view of the tally according to tally ordering of
590
  // filters
591
  auto tally_values = xt::reshape_view(results_arr, shape);
226✔
592

593
  // get a that is (particle, energy, mesh, scores, values)
594
  auto transposed_view = xt::transpose(tally_values, transpose);
226✔
595

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

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

616
    // use the index of the particle in the filter to down-select data later
617
    particle_idx = p_it - particles.begin();
206✔
618
  }
619

620
  // down-select data based on particle and score
621
  auto sum = xt::dynamic_view(
1,130✔
622
    transposed_view, {particle_idx, xt::all(), xt::all(), score_index,
452✔
623
                       static_cast<int>(TallyResult::SUM)});
904✔
624
  auto sum_sq = xt::dynamic_view(
1,130✔
625
    transposed_view, {particle_idx, xt::all(), xt::all(), score_index,
452✔
626
                       static_cast<int>(TallyResult::SUM_SQ)});
904✔
627
  int n = tally->n_realizations_;
226✔
628

629
  //////////////////////////////////////////////
630
  //
631
  // Assign new weight windows
632
  //
633
  // Use references to the existing weight window data
634
  // to store and update the values
635
  //
636
  //////////////////////////////////////////////
637

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

644
  // get mesh volumes
645
  auto mesh_vols = this->mesh()->volumes();
226✔
646

647
  // Calculate mean (new_bounds) and relative error
648
#pragma omp parallel for collapse(2) schedule(static)
140✔
649
  for (int e = 0; e < e_bins; e++) {
740✔
650
    for (int64_t m = 0; m < mesh_bins; m++) {
943,694✔
651
      // Calculate mean
652
      new_bounds(e, m) = sum(e, m) / n;
943,040✔
653
      // Calculate relative error
654
      if (sum(e, m) > 0.0) {
943,040✔
655
        double mean_val = new_bounds(e, m);
81,184✔
656
        double variance = (sum_sq(e, m) / n - mean_val * mean_val) / (n - 1);
81,184✔
657
        rel_err(e, m) = std::sqrt(variance) / mean_val;
81,184✔
658
      } else {
659
        rel_err(e, m) = INFTY;
861,856✔
660
      }
661
      if (value == "rel_err") {
943,040✔
662
        new_bounds(e, m) = 1.0 / rel_err(e, m);
276,000✔
663
      }
664
    }
665
  }
666

667
  // Divide by volume of mesh elements
668
#pragma omp parallel for collapse(2) schedule(static)
140✔
669
  for (int e = 0; e < e_bins; e++) {
740✔
670
    for (int64_t m = 0; m < mesh_bins; m++) {
943,694✔
671
      new_bounds(e, m) /= mesh_vols[m];
943,040✔
672
    }
673
  }
674

675
  if (method == WeightWindowUpdateMethod::MAGIC) {
226✔
676
    // For MAGIC, weight windows are proportional to the forward fluxes.
677
    // We normalize weight windows independently for each energy group.
678

679
    // Find group maximum and normalize (per energy group)
680
    for (int e = 0; e < e_bins; e++) {
1,700✔
681
      double group_max = 0.0;
1,560✔
682

683
      // Find maximum value across all elements in this energy group
684
#pragma omp parallel for schedule(static) reduction(max : group_max)
936✔
685
      for (int64_t m = 0; m < mesh_bins; m++) {
874,004✔
686
        if (new_bounds(e, m) > group_max) {
873,380✔
687
          group_max = new_bounds(e, m);
2,016✔
688
        }
689
      }
690

691
      // Normalize values in this energy group by the maximum value
692
      if (group_max > 0.0) {
1,560✔
693
        double norm_factor = 1.0 / (2.0 * group_max);
1,530✔
694
#pragma omp parallel for schedule(static)
918✔
695
        for (int64_t m = 0; m < mesh_bins; m++) {
873,272✔
696
          new_bounds(e, m) *= norm_factor;
872,660✔
697
        }
698
      }
699
    }
700
  } else {
701
    // For FW-CADIS, weight windows are inversely proportional to the adjoint
702
    // fluxes. We normalize the weight windows across all energy groups.
703
#pragma omp parallel for collapse(2) schedule(static)
56✔
704
    for (int e = 0; e < e_bins; e++) {
60✔
705
      for (int64_t m = 0; m < mesh_bins; m++) {
69,690✔
706
        // Take the inverse, but are careful not to divide by zero
707
        if (new_bounds(e, m) != 0.0) {
69,660✔
708
          new_bounds(e, m) = 1.0 / new_bounds(e, m);
55,728✔
709
        } else {
710
          new_bounds(e, m) = 0.0;
13,932!
711
        }
712
      }
713
    }
714

715
    // Find the maximum value across all elements
716
    double max_val = 0.0;
86✔
717
#pragma omp parallel for collapse(2) schedule(static) reduction(max : max_val)
56✔
718
    for (int e = 0; e < e_bins; e++) {
60✔
719
      for (int64_t m = 0; m < mesh_bins; m++) {
69,690✔
720
        if (new_bounds(e, m) > max_val) {
69,660✔
721
          max_val = new_bounds(e, m);
324✔
722
        }
723
      }
724
    }
725

726
    // Parallel normalization
727
    if (max_val > 0.0) {
86✔
728
      double norm_factor = 1.0 / (2.0 * max_val);
62✔
729
#pragma omp parallel for collapse(2) schedule(static)
38✔
730
      for (int e = 0; e < e_bins; e++) {
48✔
731
        for (int64_t m = 0; m < mesh_bins; m++) {
55,752✔
732
          new_bounds(e, m) *= norm_factor;
55,728✔
733
        }
734
      }
735
    }
736
  }
737

738
  // Final processing
739
#pragma omp parallel for collapse(2) schedule(static)
140✔
740
  for (int e = 0; e < e_bins; e++) {
740✔
741
    for (int64_t m = 0; m < mesh_bins; m++) {
943,694✔
742
      // Values where the mean is zero should be ignored
743
      if (sum(e, m) <= 0.0) {
943,040✔
744
        new_bounds(e, m) = -1.0;
861,856✔
745
      }
746
      // Values where the relative error is higher than the threshold should be
747
      // ignored
748
      else if (rel_err(e, m) > threshold) {
81,184✔
749
        new_bounds(e, m) = -1.0;
1,136✔
750
      }
751
      // Set the upper bounds
752
      upper_ww_(e, m) = ratio * lower_ww_(e, m);
943,040✔
753
    }
754
  }
755
}
226✔
756

757
void WeightWindows::check_tally_update_compatibility(const Tally* tally)
226✔
758
{
759
  // define the set of allowed filters for the tally
760
  const std::set<FilterType> allowed_filters = {
761
    FilterType::MESH, FilterType::ENERGY, FilterType::PARTICLE};
226✔
762

763
  // retrieve a mapping of filter type to filter index for the tally
764
  auto filter_indices = tally->filter_indices();
226✔
765

766
  // a mesh filter is required for a tally used to update weight windows
767
  if (!filter_indices.count(FilterType::MESH)) {
226!
UNCOV
768
    fatal_error(
×
769
      "A mesh filter is required for a tally to update weight window bounds");
770
  }
771

772
  // ensure the mesh filter is using the same mesh as this weight window object
773
  auto mesh_filter = tally->get_filter<MeshFilter>();
226✔
774

775
  // make sure that all of the filters present on the tally are allowed
776
  for (auto filter_pair : filter_indices) {
864✔
777
    if (allowed_filters.find(filter_pair.first) == allowed_filters.end()) {
638!
UNCOV
778
      fatal_error(fmt::format("Invalid filter type '{}' found on tally "
×
779
                              "used for weight window generation.",
UNCOV
780
        model::tally_filters[tally->filters(filter_pair.second)]->type_str()));
×
781
    }
782
  }
783

784
  if (mesh_filter->mesh() != mesh_idx_) {
226!
UNCOV
785
    int32_t mesh_filter_id = model::meshes[mesh_filter->mesh()]->id();
×
786
    int32_t ww_mesh_id = model::meshes[this->mesh_idx_]->id();
×
UNCOV
787
    fatal_error(fmt::format("Mesh filter {} uses a different mesh ({}) than "
×
788
                            "weight window {} mesh ({})",
UNCOV
789
      mesh_filter->id(), mesh_filter_id, id_, ww_mesh_id));
×
790
  }
791

792
  // if an energy filter exists, make sure the energy grid matches that of this
793
  // weight window object
794
  if (auto energy_filter = tally->get_filter<EnergyFilter>()) {
226✔
795
    std::vector<double> filter_bins = energy_filter->bins();
206✔
796
    std::set<double> filter_e_bounds(
797
      energy_filter->bins().begin(), energy_filter->bins().end());
206✔
798
    if (filter_e_bounds.size() != energy_bounds().size()) {
206!
799
      fatal_error(
×
800
        fmt::format("Energy filter {} does not have the same number of energy "
×
801
                    "bounds ({}) as weight window object {} ({})",
UNCOV
802
          energy_filter->id(), filter_e_bounds.size(), id_,
×
UNCOV
803
          energy_bounds().size()));
×
804
    }
805

806
    for (auto e : energy_bounds()) {
2,038✔
807
      if (filter_e_bounds.count(e) == 0) {
1,832!
UNCOV
808
        fatal_error(fmt::format(
×
809
          "Energy bounds of filter {} and weight windows {} do not match",
UNCOV
810
          energy_filter->id(), id_));
×
811
      }
812
    }
813
  }
206✔
814
}
226✔
815

816
void WeightWindows::to_hdf5(hid_t group) const
122✔
817
{
818
  hid_t ww_group = create_group(group, fmt::format("weight_windows_{}", id()));
244✔
819

820
  write_dataset(ww_group, "mesh", this->mesh()->id());
122✔
821
  write_dataset(
122✔
822
    ww_group, "particle_type", openmc::particle_type_to_str(particle_type_));
244✔
823
  write_dataset(ww_group, "energy_bounds", energy_bounds_);
122✔
824
  write_dataset(ww_group, "lower_ww_bounds", lower_ww_);
122✔
825
  write_dataset(ww_group, "upper_ww_bounds", upper_ww_);
122✔
826
  write_dataset(ww_group, "survival_ratio", survival_ratio_);
122✔
827
  write_dataset(ww_group, "max_lower_bound_ratio", max_lb_ratio_);
122✔
828
  write_dataset(ww_group, "max_split", max_split_);
122✔
829
  write_dataset(ww_group, "weight_cutoff", weight_cutoff_);
122✔
830

831
  close_group(ww_group);
122✔
832
}
122✔
833

834
WeightWindowsGenerator::WeightWindowsGenerator(pugi::xml_node node)
73✔
835
{
836
  // read information from the XML node
837
  int32_t mesh_id = std::stoi(get_node_value(node, "mesh"));
73✔
838
  int32_t mesh_idx = model::mesh_map[mesh_id];
73✔
839
  max_realizations_ = std::stoi(get_node_value(node, "max_realizations"));
73✔
840

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

852
  update_interval_ = std::stoi(get_node_value(node, "update_interval"));
73✔
853
  on_the_fly_ = get_node_value_bool(node, "on_the_fly");
73✔
854

855
  std::vector<double> e_bounds;
73✔
856
  if (check_for_node(node, "energy_bounds")) {
73✔
857
    e_bounds = get_node_array<double>(node, "energy_bounds");
21✔
858
  } else {
859
    int p_type = static_cast<int>(particle_type);
52✔
860
    e_bounds.push_back(data::energy_min[p_type]);
52✔
861
    e_bounds.push_back(data::energy_max[p_type]);
52✔
862
  }
863

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

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

896
  // check update parameter values
897
  if (tally_value_ != "mean" && tally_value_ != "rel_err") {
73!
UNCOV
898
    fatal_error(fmt::format("Unsupported tally value '{}' specified for "
×
899
                            "weight window generation.",
900
      tally_value_));
×
901
  }
902
  if (threshold_ <= 0.0)
73!
UNCOV
903
    fatal_error(fmt::format("Invalid relative error threshold '{}' (<= 0.0) "
×
904
                            "specified for weight window generation",
UNCOV
905
      ratio_));
×
906
  if (ratio_ <= 1.0)
73!
UNCOV
907
    fatal_error(fmt::format("Invalid weight window ratio '{}' (<= 1.0) "
×
908
                            "specified for weight window generation"));
909

910
  // create a matching weight windows object
911
  auto wws = WeightWindows::create();
73✔
912
  ww_idx_ = wws->index();
73✔
913
  wws->set_mesh(mesh_idx);
73✔
914
  if (e_bounds.size() > 0)
73!
915
    wws->set_energy_bounds(e_bounds);
73✔
916
  wws->set_particle_type(particle_type);
73✔
917
  wws->set_defaults();
73✔
918
}
73✔
919

920
void WeightWindowsGenerator::create_tally()
73✔
921
{
922
  const auto& wws = variance_reduction::weight_windows[ww_idx_];
73✔
923

924
  // create a tally based on the WWG information
925
  Tally* ww_tally = Tally::create();
73✔
926
  tally_idx_ = model::tally_map[ww_tally->id()];
73✔
927
  ww_tally->set_scores({"flux"});
146!
928

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

944
  if (!found_mesh_filter) {
73✔
945
    auto mesh_filter = Filter::create("mesh");
63✔
946
    openmc_mesh_filter_set_mesh(mesh_filter->index(), model::mesh_map[mesh_id]);
63✔
947
    ww_tally->add_filter(mesh_filter);
63✔
948
  }
949

950
  const auto& e_bounds = wws->energy_bounds();
73✔
951
  if (e_bounds.size() > 0) {
73!
952
    auto energy_filter = Filter::create("energy");
73✔
953
    openmc_energy_filter_set_bins(
146✔
954
      energy_filter->index(), e_bounds.size(), e_bounds.data());
73✔
955
    ww_tally->add_filter(energy_filter);
73✔
956
  }
957

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

966
void WeightWindowsGenerator::update() const
2,122✔
967
{
968
  const auto& wws = variance_reduction::weight_windows[ww_idx_];
2,122✔
969

970
  Tally* tally = model::tallies[tally_idx_].get();
2,122✔
971

972
  // If in random ray mode, only update on the last batch
973
  if (settings::solver_type == SolverType::RANDOM_RAY) {
2,122✔
974
    if (simulation::current_batch != settings::n_batches) {
1,972✔
975
      return;
1,886✔
976
    }
977
    // If in Monte Carlo mode and beyond the number of max realizations or
978
    // not at the correct update interval, skip the update
979
  } else if (max_realizations_ < tally->n_realizations_ ||
150✔
980
             tally->n_realizations_ % update_interval_ != 0) {
30!
981
    return;
120✔
982
  }
983

984
  wws->update_weights(tally, tally_value_, threshold_, ratio_, method_);
116✔
985

986
  // if we're not doing on the fly generation, reset the tally results once
987
  // we're done with the update
988
  if (!on_the_fly_)
116!
UNCOV
989
    tally->reset();
×
990

991
  // TODO: deactivate or remove tally once weight window generation is
992
  // complete
993
}
994

995
//==============================================================================
996
// Non-member functions
997
//==============================================================================
998

999
void finalize_variance_reduction()
6,765✔
1000
{
1001
  for (const auto& wwg : variance_reduction::weight_windows_generators) {
6,838✔
1002
    wwg->create_tally();
73✔
1003
  }
1004
}
6,765✔
1005

1006
//==============================================================================
1007
// C API
1008
//==============================================================================
1009

1010
int verify_ww_index(int32_t index)
1,810✔
1011
{
1012
  if (index < 0 || index >= variance_reduction::weight_windows.size()) {
1,810!
UNCOV
1013
    set_errmsg(fmt::format("Index '{}' for weight windows is invalid", index));
×
1014
    return OPENMC_E_OUT_OF_BOUNDS;
×
1015
  }
1016
  return 0;
1,810✔
1017
}
1018

1019
extern "C" int openmc_get_weight_windows_index(int32_t id, int32_t* idx)
150✔
1020
{
1021
  auto it = variance_reduction::ww_map.find(id);
150✔
1022
  if (it == variance_reduction::ww_map.end()) {
150!
UNCOV
1023
    set_errmsg(fmt::format("No weight windows exist with ID={}", id));
×
UNCOV
1024
    return OPENMC_E_INVALID_ID;
×
1025
  }
1026

1027
  *idx = it->second;
150✔
1028
  return 0;
150✔
1029
}
1030

1031
extern "C" int openmc_weight_windows_get_id(int32_t index, int32_t* id)
470✔
1032
{
1033
  if (int err = verify_ww_index(index))
470!
UNCOV
1034
    return err;
×
1035

1036
  const auto& wws = variance_reduction::weight_windows.at(index);
470✔
1037
  *id = wws->id();
470✔
1038
  return 0;
470✔
1039
}
1040

1041
extern "C" int openmc_weight_windows_set_id(int32_t index, int32_t id)
140✔
1042
{
1043
  if (int err = verify_ww_index(index))
140!
UNCOV
1044
    return err;
×
1045

1046
  const auto& wws = variance_reduction::weight_windows.at(index);
140✔
1047
  wws->set_id(id);
140✔
1048
  return 0;
140✔
1049
}
1050

1051
extern "C" int openmc_weight_windows_update_magic(int32_t ww_idx,
110✔
1052
  int32_t tally_idx, const char* value, double threshold, double ratio)
1053
{
1054
  if (int err = verify_ww_index(ww_idx))
110!
UNCOV
1055
    return err;
×
1056

1057
  if (tally_idx < 0 || tally_idx >= model::tallies.size()) {
110!
UNCOV
1058
    set_errmsg(fmt::format("Index '{}' for tally is invalid", tally_idx));
×
UNCOV
1059
    return OPENMC_E_OUT_OF_BOUNDS;
×
1060
  }
1061

1062
  // get the requested tally
1063
  const Tally* tally = model::tallies.at(tally_idx).get();
110✔
1064

1065
  // get the WeightWindows object
1066
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
110✔
1067

1068
  wws->update_weights(tally, value, threshold, ratio);
110✔
1069

1070
  return 0;
110✔
1071
}
1072

1073
extern "C" int openmc_weight_windows_set_mesh(int32_t ww_idx, int32_t mesh_idx)
140✔
1074
{
1075
  if (int err = verify_ww_index(ww_idx))
140!
1076
    return err;
×
1077
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
140✔
1078
  wws->set_mesh(mesh_idx);
140✔
1079
  return 0;
140✔
1080
}
1081

1082
extern "C" int openmc_weight_windows_get_mesh(int32_t ww_idx, int32_t* mesh_idx)
10✔
1083
{
1084
  if (int err = verify_ww_index(ww_idx))
10!
UNCOV
1085
    return err;
×
1086
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
10✔
1087
  *mesh_idx = model::mesh_map.at(wws->mesh()->id());
10✔
1088
  return 0;
10✔
1089
}
1090

1091
extern "C" int openmc_weight_windows_set_energy_bounds(
120✔
1092
  int32_t ww_idx, double* e_bounds, size_t e_bounds_size)
1093
{
1094
  if (int err = verify_ww_index(ww_idx))
120!
UNCOV
1095
    return err;
×
1096
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
120✔
1097
  wws->set_energy_bounds({e_bounds, e_bounds_size});
120✔
1098
  return 0;
120✔
1099
}
1100

1101
extern "C" int openmc_weight_windows_get_energy_bounds(
10✔
1102
  int32_t ww_idx, const double** e_bounds, size_t* e_bounds_size)
1103
{
1104
  if (int err = verify_ww_index(ww_idx))
10!
UNCOV
1105
    return err;
×
1106
  const auto& wws = variance_reduction::weight_windows[ww_idx].get();
10✔
1107
  *e_bounds = wws->energy_bounds().data();
10✔
1108
  *e_bounds_size = wws->energy_bounds().size();
10✔
1109
  return 0;
10✔
1110
}
1111

1112
extern "C" int openmc_weight_windows_set_particle(int32_t index, int particle)
160✔
1113
{
1114
  if (int err = verify_ww_index(index))
160!
UNCOV
1115
    return err;
×
1116

1117
  const auto& wws = variance_reduction::weight_windows.at(index);
160✔
1118
  wws->set_particle_type(static_cast<ParticleType>(particle));
160✔
1119
  return 0;
160✔
1120
}
1121

1122
extern "C" int openmc_weight_windows_get_particle(int32_t index, int* particle)
40✔
1123
{
1124
  if (int err = verify_ww_index(index))
40!
UNCOV
1125
    return err;
×
1126

1127
  const auto& wws = variance_reduction::weight_windows.at(index);
40✔
1128
  *particle = static_cast<int>(wws->particle_type());
40✔
1129
  return 0;
40✔
1130
}
1131

1132
extern "C" int openmc_weight_windows_get_bounds(int32_t index,
440✔
1133
  const double** lower_bounds, const double** upper_bounds, size_t* size)
1134
{
1135
  if (int err = verify_ww_index(index))
440!
UNCOV
1136
    return err;
×
1137

1138
  const auto& wws = variance_reduction::weight_windows[index];
440✔
1139
  *size = wws->lower_ww_bounds().size();
440✔
1140
  *lower_bounds = wws->lower_ww_bounds().data();
440✔
1141
  *upper_bounds = wws->upper_ww_bounds().data();
440✔
1142
  return 0;
440✔
1143
}
1144

1145
extern "C" int openmc_weight_windows_set_bounds(int32_t index,
10✔
1146
  const double* lower_bounds, const double* upper_bounds, size_t size)
1147
{
1148
  if (int err = verify_ww_index(index))
10!
UNCOV
1149
    return err;
×
1150

1151
  const auto& wws = variance_reduction::weight_windows[index];
10✔
1152
  wws->set_bounds({lower_bounds, size}, {upper_bounds, size});
10✔
1153
  return 0;
10✔
1154
}
1155

1156
extern "C" int openmc_weight_windows_get_survival_ratio(
30✔
1157
  int32_t index, double* ratio)
1158
{
1159
  if (int err = verify_ww_index(index))
30!
UNCOV
1160
    return err;
×
1161
  const auto& wws = variance_reduction::weight_windows[index];
30✔
1162
  *ratio = wws->survival_ratio();
30✔
1163
  return 0;
30✔
1164
}
1165

1166
extern "C" int openmc_weight_windows_set_survival_ratio(
10✔
1167
  int32_t index, double ratio)
1168
{
1169
  if (int err = verify_ww_index(index))
10!
UNCOV
1170
    return err;
×
1171
  const auto& wws = variance_reduction::weight_windows[index];
10✔
1172
  wws->survival_ratio() = ratio;
10✔
1173
  std::cout << "Survival ratio: " << wws->survival_ratio() << std::endl;
10✔
1174
  return 0;
10✔
1175
}
1176

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

1187
extern "C" int openmc_weight_windows_set_max_lower_bound_ratio(
10✔
1188
  int32_t index, double lb_ratio)
1189
{
1190
  if (int err = verify_ww_index(index))
10!
UNCOV
1191
    return err;
×
1192
  const auto& wws = variance_reduction::weight_windows[index];
10✔
1193
  wws->max_lower_bound_ratio() = lb_ratio;
10✔
1194
  return 0;
10✔
1195
}
1196

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

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

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

1227
extern "C" int openmc_weight_windows_set_max_split(int32_t index, int max_split)
10✔
1228
{
1229
  if (int err = verify_ww_index(index))
10!
UNCOV
1230
    return err;
×
1231
  const auto& wws = variance_reduction::weight_windows[index];
10✔
1232
  wws->max_split() = max_split;
10✔
1233
  return 0;
10✔
1234
}
1235

1236
extern "C" int openmc_extend_weight_windows(
140✔
1237
  int32_t n, int32_t* index_start, int32_t* index_end)
1238
{
1239
  if (index_start)
140!
1240
    *index_start = variance_reduction::weight_windows.size();
140✔
1241
  if (index_end)
140!
UNCOV
1242
    *index_end = variance_reduction::weight_windows.size() + n - 1;
×
1243
  for (int i = 0; i < n; ++i)
280✔
1244
    variance_reduction::weight_windows.push_back(make_unique<WeightWindows>());
140✔
1245
  return 0;
140✔
1246
}
1247

1248
extern "C" size_t openmc_weight_windows_size()
140✔
1249
{
1250
  return variance_reduction::weight_windows.size();
140✔
1251
}
1252

1253
extern "C" int openmc_weight_windows_export(const char* filename)
146✔
1254
{
1255

1256
  if (!mpi::master)
146✔
1257
    return 0;
24✔
1258

1259
  std::string name = filename ? filename : "weight_windows.h5";
244✔
1260

1261
  write_message(fmt::format("Exporting weight windows to {}...", name), 5);
122✔
1262

1263
  hid_t ww_file = file_open(name, 'w');
122✔
1264

1265
  // Write file type
1266
  write_attribute(ww_file, "filetype", "weight_windows");
122✔
1267

1268
  // Write revisiion number for state point file
1269
  write_attribute(ww_file, "version", VERSION_WEIGHT_WINDOWS);
122✔
1270

1271
  hid_t weight_windows_group = create_group(ww_file, "weight_windows");
122✔
1272

1273
  hid_t mesh_group = create_group(ww_file, "meshes");
122✔
1274

1275
  std::vector<int32_t> mesh_ids;
122✔
1276
  std::vector<int32_t> ww_ids;
122✔
1277
  for (const auto& ww : variance_reduction::weight_windows) {
244✔
1278

1279
    ww->to_hdf5(weight_windows_group);
122✔
1280
    ww_ids.push_back(ww->id());
122✔
1281

1282
    // if the mesh has already been written, move on
1283
    int32_t mesh_id = ww->mesh()->id();
122✔
1284
    if (std::find(mesh_ids.begin(), mesh_ids.end(), mesh_id) != mesh_ids.end())
122!
UNCOV
1285
      continue;
×
1286

1287
    mesh_ids.push_back(mesh_id);
122✔
1288
    ww->mesh()->to_hdf5(mesh_group);
122✔
1289
  }
1290

1291
  write_attribute(mesh_group, "n_meshes", mesh_ids.size());
122✔
1292
  write_attribute(mesh_group, "ids", mesh_ids);
122✔
1293
  close_group(mesh_group);
122✔
1294

1295
  write_attribute(weight_windows_group, "n_weight_windows", ww_ids.size());
122✔
1296
  write_attribute(weight_windows_group, "ids", ww_ids);
122✔
1297
  close_group(weight_windows_group);
122✔
1298

1299
  file_close(ww_file);
122✔
1300

1301
  return 0;
122✔
1302
}
122✔
1303

1304
extern "C" int openmc_weight_windows_import(const char* filename)
10✔
1305
{
1306
  std::string name = filename ? filename : "weight_windows.h5";
10!
1307

1308
  if (mpi::master)
10!
1309
    write_message(fmt::format("Importing weight windows from {}...", name), 5);
10✔
1310

1311
  if (!file_exists(name)) {
10!
1312
    set_errmsg(fmt::format("File '{}' does not exist", name));
×
1313
  }
1314

1315
  hid_t ww_file = file_open(name, 'r');
10✔
1316

1317
  // Check that filetype is correct
1318
  std::string filetype;
10✔
1319
  read_attribute(ww_file, "filetype", filetype);
10✔
1320
  if (filetype != "weight_windows") {
10!
UNCOV
1321
    file_close(ww_file);
×
UNCOV
1322
    set_errmsg(fmt::format("File '{}' is not a weight windows file.", name));
×
UNCOV
1323
    return OPENMC_E_INVALID_ARGUMENT;
×
1324
  }
1325

1326
  // Check that the file version is compatible
1327
  std::array<int, 2> file_version;
1328
  read_attribute(ww_file, "version", file_version);
10✔
1329
  if (file_version[0] != VERSION_WEIGHT_WINDOWS[0]) {
10!
1330
    std::string err_msg =
1331
      fmt::format("File '{}' has version {} which is incompatible with the "
1332
                  "expected version ({}).",
UNCOV
1333
        name, file_version, VERSION_WEIGHT_WINDOWS);
×
UNCOV
1334
    set_errmsg(err_msg);
×
UNCOV
1335
    return OPENMC_E_INVALID_ARGUMENT;
×
UNCOV
1336
  }
×
1337

1338
  hid_t weight_windows_group = open_group(ww_file, "weight_windows");
10✔
1339

1340
  hid_t mesh_group = open_group(ww_file, "meshes");
10✔
1341

1342
  read_meshes(mesh_group);
10✔
1343

1344
  std::vector<std::string> names = group_names(weight_windows_group);
10✔
1345

1346
  for (const auto& name : names) {
20✔
1347
    WeightWindows::from_hdf5(weight_windows_group, name);
10✔
1348
  }
1349

1350
  close_group(weight_windows_group);
10✔
1351

1352
  file_close(ww_file);
10✔
1353

1354
  return 0;
10✔
1355
}
10✔
1356

1357
} // namespace openmc
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc