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

openmc-dev / openmc / 21686031975

04 Feb 2026 07:50PM UTC coverage: 81.024% (-0.7%) from 81.763%
21686031975

Pull #3755

github

web-flow
Merge 27d6053a4 into b41e22f68
Pull Request #3755: Warn users that tally heating score with photon bin but without electron and positron bins.

16378 of 22828 branches covered (71.75%)

Branch coverage included in aggregate %.

22 of 23 new or added lines in 1 file covered. (95.65%)

862 existing lines in 51 files now uncovered.

54491 of 64639 relevant lines covered (84.3%)

8259986.93 hits per line

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

78.49
/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)
262,581,854✔
57
{
58
  if (!settings::weight_windows_on)
262,581,854✔
59
    return;
258,804,905✔
60

61
  // WW on photon and neutron only
62
  if (!p.type().is_neutron() && !p.type().is_photon())
7,458,201✔
63
    return;
994,049✔
64

65
  // skip dead or no energy
66
  if (p.E() <= 0 || !p.alive())
6,464,152✔
67
    return;
371,489✔
68

69
  bool in_domain = false;
6,092,663✔
70
  // TODO: this is a linear search - should do something more clever
71
  WeightWindow weight_window;
6,092,663✔
72
  for (const auto& ww : variance_reduction::weight_windows) {
7,673,922✔
73
    weight_window = ww->get_weight_window(p);
6,460,719✔
74
    if (weight_window.is_valid())
6,460,719✔
75
      break;
4,879,460✔
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) {
6,092,663✔
81
    if (weight_window.is_valid()) {
66,900✔
82
      p.wgt_ww_born() =
60,918✔
83
        (weight_window.lower_weight + weight_window.upper_weight) / 2;
60,918✔
84
    } else {
85
      p.wgt_ww_born() = 1.0;
5,982✔
86
    }
87
  }
88

89
  // particle is not in any of the ww domains, do nothing
90
  if (!weight_window.is_valid())
6,092,663✔
91
    return;
1,213,203✔
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());
4,879,460✔
96

97
  // get the paramters
98
  double weight = p.wgt();
4,879,460✔
99

100
  // first check to see if particle should be killed for weight cutoff
101
  if (p.wgt() < weight_window.weight_cutoff) {
4,879,460!
102
    p.wgt() = 0.0;
×
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 &&
4,879,718✔
110
      p.wgt() > weight_window.lower_weight * weight_window.max_lb_ratio) {
258!
111
    p.ww_factor() =
258✔
112
      p.wgt() / (weight_window.lower_weight * weight_window.max_lb_ratio);
258✔
113
  }
114

115
  // move weight window closer to the particle weight if needed
116
  if (p.ww_factor() > 1.0)
4,879,460✔
117
    weight_window.scale(p.ww_factor());
123,313✔
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) {
4,879,460✔
122
    // do not further split the particle if above the limit
123
    if (p.n_split() >= settings::max_history_splits)
1,221,452✔
124
      return;
1,102,511✔
125

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

130
    p.n_split() += n_split;
118,941✔
131

132
    // Create secondaries and divide weight among all particles
133
    int i_split = std::round(n_split);
118,941✔
134
    for (int l = 0; l < i_split - 1; l++) {
490,393✔
135
      p.split(weight / n_split);
371,452✔
136
    }
137
    // remaining weight is applied to current particle
138
    p.wgt() = weight / n_split;
118,941✔
139

140
  } else if (weight <= weight_window.lower_weight) {
3,658,008✔
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);
115,478✔
144
    russian_roulette(p, weight_survive);
115,478✔
145
  } // else particle is in the window, continue as normal
146
}
147

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

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

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

165
WeightWindows::WeightWindows(pugi::xml_node node)
9✔
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"};
63✔
170
  for (const auto& elem : required_elems) {
45✔
171
    if (!check_for_node(node, elem.c_str())) {
36!
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"));
9✔
178
  this->set_id(id);
9✔
179

180
  // get the particle type
181
  auto particle_type_str = std::string(get_node_value(node, "particle_type"));
9✔
182
  particle_type_ = ParticleType {particle_type_str};
9✔
183

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

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

192
  // get the survival value - optional
193
  if (check_for_node(node, "survival_ratio")) {
9!
194
    survival_ratio_ = std::stod(get_node_value(node, "survival_ratio"));
9✔
195
    if (survival_ratio_ <= 1)
9!
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")) {
9✔
202
    max_lb_ratio_ = std::stod(get_node_value(node, "max_lower_bound_ratio"));
4✔
203
    if (max_lb_ratio_ < 1.0) {
4!
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")) {
9!
210
    max_split_ = std::stod(get_node_value(node, "max_split"));
9✔
211
    if (max_split_ <= 1)
9!
212
      fatal_error("max split must be larger than 1");
×
213
  }
214

215
  // weight cutoff - optional
216
  if (check_for_node(node, "weight_cutoff")) {
9!
217
    weight_cutoff_ = std::stod(get_node_value(node, "weight_cutoff"));
9✔
218
    if (weight_cutoff_ <= 0)
9!
219
      fatal_error("weight_cutoff must be larger than 0");
×
220
    if (weight_cutoff_ > 1)
9!
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"),
9✔
226
    get_node_array<double>(node, "upper_ww_bounds"));
18✔
227

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

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

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

245
WeightWindows* WeightWindows::from_hdf5(
1✔
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);
1✔
250

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

253
  std::string particle_type;
1✔
254
  read_dataset(ww_group, "particle_type", particle_type);
1✔
255
  wws->particle_type_ = ParticleType {particle_type};
1✔
256

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

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

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

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

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

278
  close_group(ww_group);
1✔
279

280
  return wws;
1✔
281
}
1✔
282

283
void WeightWindows::set_defaults()
42✔
284
{
285
  // set energy bounds to the min/max energy supported by the data
286
  if (energy_bounds_.size() == 0) {
42✔
287
    int p_type = particle_type_.transport_index();
25✔
288
    if (p_type == C_NONE) {
25!
289
      fatal_error("Weight windows particle is not supported for transport.");
×
290
    }
291
    energy_bounds_.push_back(data::energy_min[p_type]);
25✔
292
    energy_bounds_.push_back(data::energy_max[p_type]);
25✔
293
  }
294
}
42✔
295

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

310
void WeightWindows::set_id(int32_t id)
47✔
311
{
312
  assert(id >= 0 || id == C_NONE);
47!
313

314
  // Clear entry in mesh map in case one was already assigned
315
  if (id_ != C_NONE) {
47!
316
    variance_reduction::ww_map.erase(id_);
47✔
317
    id_ = C_NONE;
47✔
318
  }
319

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

326
  // If no ID is specified, auto-assign the next ID in the sequence
327
  if (id == C_NONE) {
47✔
328
    id = 0;
24✔
329
    for (const auto& m : variance_reduction::weight_windows) {
26✔
330
      id = std::max(id, m->id_);
2✔
331
    }
332
    ++id;
24✔
333
  }
334

335
  // Update ID and entry in the mesh map
336
  id_ = id;
47✔
337
  variance_reduction::ww_map[id] = index_;
47✔
338
}
47✔
339

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

349
void WeightWindows::set_particle_type(ParticleType p_type)
25✔
350
{
351
  if (!p_type.is_neutron() && !p_type.is_photon())
25!
352
    fatal_error(fmt::format(
×
353
      "Particle type '{}' cannot be applied to weight windows.", p_type.str()));
×
354
  particle_type_ = p_type;
25✔
355
}
25✔
356

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

362
  mesh_idx_ = mesh_idx;
33✔
363
  model::meshes[mesh_idx_]->prepare_for_point_location();
33✔
364
  allocate_ww_bounds();
33✔
365
}
33✔
366

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

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

377
WeightWindow WeightWindows::get_weight_window(const Particle& p) const
6,460,719✔
378
{
379
  // check for particle type
380
  if (particle_type_ != p.type()) {
6,460,719✔
381
    return {};
352,076✔
382
  }
383

384
  // Get mesh index for particle's position
385
  const auto& mesh = this->mesh();
6,108,643✔
386
  int mesh_bin = mesh->get_bin(p.r());
6,108,643✔
387

388
  // particle is outside the weight window mesh
389
  if (mesh_bin < 0)
6,108,643!
UNCOV
390
    return {};
×
391

392
  // particle energy
393
  double E = p.E();
6,108,643✔
394

395
  // check to make sure energy is in range, expects sorted energy values
396
  if (E < energy_bounds_.front() || E > energy_bounds_.back())
6,108,643!
397
    return {};
8,418✔
398

399
  // get the mesh bin in energy group
400
  int energy_bin =
401
    lower_bound_index(energy_bounds_.begin(), energy_bounds_.end(), E);
6,100,225✔
402

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

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

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

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

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

455
  this->check_bounds(lower_bounds, upper_bounds);
×
456

457
  // set new weight window values
458
  lower_ww_ = lower_bounds;
×
459
  upper_ww_ = upper_bounds;
×
460
}
×
461

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

467
  // set new weight window values
468
  lower_ww_ = lower_bounds;
×
469
  upper_ww_ = lower_bounds;
×
470
  upper_ww_ *= ratio;
×
471
}
×
472

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

481
  // set new weight window values
482
  xt::view(lower_ww_, xt::all()) =
20✔
483
    xt::adapt(lower_bounds.data(), lower_ww_.shape());
30✔
484
  xt::view(upper_ww_, xt::all()) =
20✔
485
    xt::adapt(upper_bounds.data(), upper_ww_.shape());
30✔
486
}
10✔
487

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

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

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

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

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

516
  // Initialize weight window arrays to -1.0 by default
517
#pragma omp parallel for collapse(2) schedule(static)
518
  for (int e = 0; e < e_bins; e++) {
194✔
519
    for (int64_t m = 0; m < mesh_bins; m++) {
246,377✔
520
      lower_ww_(e, m) = -1.0;
246,209✔
521
      upper_ww_(e, m) = -1.0;
246,209✔
522
    }
523
  }
524

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

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

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

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

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

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

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

569
  // assign other filter types to dummy positions if needed
570
  if (!tally->has_filter(FilterType::PARTICLE))
26✔
571
    filter_types.push_back(FilterType::PARTICLE);
2✔
572

573
  if (!tally->has_filter(FilterType::ENERGY))
26✔
574
    filter_types.push_back(FilterType::ENERGY);
2✔
575

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

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

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

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

595
  // get a that is (particle, energy, mesh, scores, values)
596
  auto transposed_view = xt::transpose(tally_values, transpose);
26✔
597

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

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

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

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

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

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

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

648
  // Calculate mean (new_bounds) and relative error
649
#pragma omp parallel for collapse(2) schedule(static)
650
  for (int e = 0; e < e_bins; e++) {
194✔
651
    for (int64_t m = 0; m < mesh_bins; m++) {
246,377✔
652
      // Calculate mean
653
      new_bounds(e, m) = sum(e, m) / n;
246,209✔
654
      // Calculate relative error
655
      if (sum(e, m) > 0.0) {
246,209✔
656
        double mean_val = new_bounds(e, m);
20,296✔
657
        double variance = (sum_sq(e, m) / n - mean_val * mean_val) / (n - 1);
20,296✔
658
        rel_err(e, m) = std::sqrt(variance) / mean_val;
20,296✔
659
      } else {
660
        rel_err(e, m) = INFTY;
225,913✔
661
      }
662
      if (value == "rel_err") {
246,209✔
663
        new_bounds(e, m) = 1.0 / rel_err(e, m);
69,000✔
664
      }
665
    }
666
  }
667

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

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

680
    // Find group maximum and normalize (per energy group)
681
    for (int e = 0; e < e_bins; e++) {
170✔
682
      double group_max = 0.0;
156✔
683

684
      // Find maximum value across all elements in this energy group
685
#pragma omp parallel for schedule(static) reduction(max : group_max)
686
      for (int64_t m = 0; m < mesh_bins; m++) {
218,501✔
687
        if (new_bounds(e, m) > group_max) {
218,345✔
688
          group_max = new_bounds(e, m);
504✔
689
        }
690
      }
691

692
      // Normalize values in this energy group by the maximum value
693
      if (group_max > 0.0) {
156✔
694
        double norm_factor = 1.0 / (2.0 * group_max);
153✔
695
#pragma omp parallel for schedule(static)
696
        for (int64_t m = 0; m < mesh_bins; m++) {
218,318✔
697
          new_bounds(e, m) *= norm_factor;
218,165✔
698
        }
699
      }
700
    }
701
  } else {
702
    // For FW-CADIS, weight windows are inversely proportional to the adjoint
703
    // fluxes. We normalize the weight windows across all energy groups.
704
#pragma omp parallel for collapse(2) schedule(static)
705
    for (int e = 0; e < e_bins; e++) {
24✔
706
      for (int64_t m = 0; m < mesh_bins; m++) {
27,876✔
707
        // Take the inverse, but are careful not to divide by zero
708
        if (new_bounds(e, m) != 0.0) {
27,864✔
709
          new_bounds(e, m) = 1.0 / new_bounds(e, m);
13,932✔
710
        } else {
711
          new_bounds(e, m) = 0.0;
13,932✔
712
        }
713
      }
714
    }
715

716
    // Find the maximum value across all elements
717
    double max_val = 0.0;
12✔
718
#pragma omp parallel for collapse(2) schedule(static) reduction(max : max_val)
719
    for (int e = 0; e < e_bins; e++) {
24✔
720
      for (int64_t m = 0; m < mesh_bins; m++) {
27,876✔
721
        if (new_bounds(e, m) > max_val) {
27,864✔
722
          max_val = new_bounds(e, m);
81✔
723
        }
724
      }
725
    }
726

727
    // Parallel normalization
728
    if (max_val > 0.0) {
12✔
729
      double norm_factor = 1.0 / (2.0 * max_val);
6✔
730
#pragma omp parallel for collapse(2) schedule(static)
731
      for (int e = 0; e < e_bins; e++) {
12✔
732
        for (int64_t m = 0; m < mesh_bins; m++) {
13,938✔
733
          new_bounds(e, m) *= norm_factor;
13,932✔
734
        }
735
      }
736
    }
737
  }
738

739
  // Final processing
740
#pragma omp parallel for collapse(2) schedule(static)
741
  for (int e = 0; e < e_bins; e++) {
194✔
742
    for (int64_t m = 0; m < mesh_bins; m++) {
246,377✔
743
      // Values where the mean is zero should be ignored
744
      if (sum(e, m) <= 0.0) {
246,209✔
745
        new_bounds(e, m) = -1.0;
225,913✔
746
      }
747
      // Values where the relative error is higher than the threshold should be
748
      // ignored
749
      else if (rel_err(e, m) > threshold) {
20,296✔
750
        new_bounds(e, m) = -1.0;
284✔
751
      }
752
      // Set the upper bounds
753
      upper_ww_(e, m) = ratio * lower_ww_(e, m);
246,209✔
754
    }
755
  }
756
}
26✔
757

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

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

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

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

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

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

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

807
    for (auto e : energy_bounds()) {
214✔
808
      if (filter_e_bounds.count(e) == 0) {
190!
809
        fatal_error(fmt::format(
×
810
          "Energy bounds of filter {} and weight windows {} do not match",
811
          energy_filter->id(), id_));
×
812
      }
813
    }
814
  }
24✔
815
}
26✔
816

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

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

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

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

841
  int32_t active_batches = settings::n_batches - settings::n_inactive;
9✔
842
  if (max_realizations_ > active_batches) {
9✔
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);
4✔
847
    warning(msg);
2✔
848
  }
2✔
849
  auto tmp_str = get_node_value(node, "particle_type", false, true);
9✔
850
  auto particle_type = ParticleType {tmp_str};
9✔
851

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

855
  std::vector<double> e_bounds;
9✔
856
  if (check_for_node(node, "energy_bounds")) {
9✔
857
    e_bounds = get_node_array<double>(node, "energy_bounds");
2✔
858
  } else {
859
    int p_type = particle_type.transport_index();
7✔
860
    if (p_type == C_NONE) {
7!
861
      fatal_error("Weight windows particle is not supported for transport.");
×
862
    }
863
    e_bounds.push_back(data::energy_min[p_type]);
7✔
864
    e_bounds.push_back(data::energy_max[p_type]);
7✔
865
  }
866

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

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

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

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

923
void WeightWindowsGenerator::create_tally()
9✔
924
{
925
  const auto& wws = variance_reduction::weight_windows[ww_idx_];
9✔
926

927
  // create a tally based on the WWG information
928
  Tally* ww_tally = Tally::create();
9✔
929
  tally_idx_ = model::tally_map[ww_tally->id()];
9✔
930
  ww_tally->set_scores({"flux"});
18!
931

932
  int32_t mesh_id = wws->mesh()->id();
9✔
933
  int32_t mesh_idx = model::mesh_map.at(mesh_id);
9✔
934
  // see if there's already a mesh filter using this mesh
935
  bool found_mesh_filter = false;
9✔
936
  for (const auto& f : model::tally_filters) {
30✔
937
    if (f->type() == FilterType::MESH) {
22✔
938
      const auto* mesh_filter = dynamic_cast<MeshFilter*>(f.get());
1!
939
      if (mesh_filter->mesh() == mesh_idx && !mesh_filter->translated() &&
2!
940
          !mesh_filter->rotated()) {
1!
941
        ww_tally->add_filter(f.get());
1✔
942
        found_mesh_filter = true;
1✔
943
        break;
1✔
944
      }
945
    }
946
  }
947

948
  if (!found_mesh_filter) {
9✔
949
    auto mesh_filter = Filter::create("mesh");
8✔
950
    openmc_mesh_filter_set_mesh(mesh_filter->index(), model::mesh_map[mesh_id]);
8✔
951
    ww_tally->add_filter(mesh_filter);
8✔
952
  }
953

954
  const auto& e_bounds = wws->energy_bounds();
9✔
955
  if (e_bounds.size() > 0) {
9!
956
    auto energy_filter = Filter::create("energy");
9✔
957
    openmc_energy_filter_set_bins(
18✔
958
      energy_filter->index(), e_bounds.size(), e_bounds.data());
9✔
959
    ww_tally->add_filter(energy_filter);
9✔
960
  }
961

962
  // add a particle filter
963
  auto particle_type = wws->particle_type();
9✔
964
  auto particle_filter = Filter::create("particle");
9✔
965
  auto pf = dynamic_cast<ParticleFilter*>(particle_filter);
9!
966
  pf->set_particles({&particle_type, 1});
9✔
967
  ww_tally->add_filter(particle_filter);
9✔
968
}
9✔
969

970
void WeightWindowsGenerator::update() const
295✔
971
{
972
  const auto& wws = variance_reduction::weight_windows[ww_idx_];
295✔
973

974
  Tally* tally = model::tallies[tally_idx_].get();
295✔
975

976
  // If in random ray mode, only update on the last batch
977
  if (settings::solver_type == SolverType::RANDOM_RAY) {
295✔
978
    if (simulation::current_batch != settings::n_batches) {
280✔
979
      return;
268✔
980
    }
981
    // If in Monte Carlo mode and beyond the number of max realizations or
982
    // not at the correct update interval, skip the update
983
  } else if (max_realizations_ < tally->n_realizations_ ||
15✔
984
             tally->n_realizations_ % update_interval_ != 0) {
3!
985
    return;
12✔
986
  }
987

988
  wws->update_weights(tally, tally_value_, threshold_, ratio_, method_);
15✔
989

990
  // if we're not doing on the fly generation, reset the tally results once
991
  // we're done with the update
992
  if (!on_the_fly_)
15!
993
    tally->reset();
×
994

995
  // TODO: deactivate or remove tally once weight window generation is
996
  // complete
997
}
998

999
//==============================================================================
1000
// Non-member functions
1001
//==============================================================================
1002

1003
void finalize_variance_reduction()
845✔
1004
{
1005
  for (const auto& wwg : variance_reduction::weight_windows_generators) {
854✔
1006
    wwg->create_tally();
9✔
1007
  }
1008
}
845✔
1009

1010
//==============================================================================
1011
// C API
1012
//==============================================================================
1013

1014
int verify_ww_index(int32_t index)
181✔
1015
{
1016
  if (index < 0 || index >= variance_reduction::weight_windows.size()) {
181!
1017
    set_errmsg(fmt::format("Index '{}' for weight windows is invalid", index));
×
1018
    return OPENMC_E_OUT_OF_BOUNDS;
×
1019
  }
1020
  return 0;
181✔
1021
}
1022

1023
extern "C" int openmc_get_weight_windows_index(int32_t id, int32_t* idx)
15✔
1024
{
1025
  auto it = variance_reduction::ww_map.find(id);
15✔
1026
  if (it == variance_reduction::ww_map.end()) {
15!
1027
    set_errmsg(fmt::format("No weight windows exist with ID={}", id));
×
1028
    return OPENMC_E_INVALID_ID;
×
1029
  }
1030

1031
  *idx = it->second;
15✔
1032
  return 0;
15✔
1033
}
1034

1035
extern "C" int openmc_weight_windows_get_id(int32_t index, int32_t* id)
47✔
1036
{
1037
  if (int err = verify_ww_index(index))
47!
1038
    return err;
×
1039

1040
  const auto& wws = variance_reduction::weight_windows.at(index);
47✔
1041
  *id = wws->id();
47✔
1042
  return 0;
47✔
1043
}
1044

1045
extern "C" int openmc_weight_windows_set_id(int32_t index, int32_t id)
14✔
1046
{
1047
  if (int err = verify_ww_index(index))
14!
1048
    return err;
×
1049

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

1055
extern "C" int openmc_weight_windows_update_magic(int32_t ww_idx,
11✔
1056
  int32_t tally_idx, const char* value, double threshold, double ratio)
1057
{
1058
  if (int err = verify_ww_index(ww_idx))
11!
1059
    return err;
×
1060

1061
  if (tally_idx < 0 || tally_idx >= model::tallies.size()) {
11!
1062
    set_errmsg(fmt::format("Index '{}' for tally is invalid", tally_idx));
×
1063
    return OPENMC_E_OUT_OF_BOUNDS;
×
1064
  }
1065

1066
  // get the requested tally
1067
  const Tally* tally = model::tallies.at(tally_idx).get();
11✔
1068

1069
  // get the WeightWindows object
1070
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
11✔
1071

1072
  wws->update_weights(tally, value, threshold, ratio);
11✔
1073

1074
  return 0;
11✔
1075
}
1076

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

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

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

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

1116
extern "C" int openmc_weight_windows_set_particle(
16✔
1117
  int32_t index, int32_t particle)
1118
{
1119
  if (int err = verify_ww_index(index))
16!
1120
    return err;
×
1121

1122
  const auto& wws = variance_reduction::weight_windows.at(index);
16✔
1123
  wws->set_particle_type(ParticleType {particle});
16✔
1124
  return 0;
16✔
1125
}
1126

1127
extern "C" int openmc_weight_windows_get_particle(
4✔
1128
  int32_t index, int32_t* particle)
1129
{
1130
  if (int err = verify_ww_index(index))
4!
1131
    return err;
×
1132

1133
  const auto& wws = variance_reduction::weight_windows.at(index);
4✔
1134
  *particle = wws->particle_type().pdg_number();
4✔
1135
  return 0;
4✔
1136
}
1137

1138
extern "C" int openmc_weight_windows_get_bounds(int32_t index,
44✔
1139
  const double** lower_bounds, const double** upper_bounds, size_t* size)
1140
{
1141
  if (int err = verify_ww_index(index))
44!
1142
    return err;
×
1143

1144
  const auto& wws = variance_reduction::weight_windows[index];
44✔
1145
  *size = wws->lower_ww_bounds().size();
44✔
1146
  *lower_bounds = wws->lower_ww_bounds().data();
44✔
1147
  *upper_bounds = wws->upper_ww_bounds().data();
44✔
1148
  return 0;
44✔
1149
}
1150

1151
extern "C" int openmc_weight_windows_set_bounds(int32_t index,
1✔
1152
  const double* lower_bounds, const double* upper_bounds, size_t size)
1153
{
1154
  if (int err = verify_ww_index(index))
1!
1155
    return err;
×
1156

1157
  const auto& wws = variance_reduction::weight_windows[index];
1✔
1158
  wws->set_bounds({lower_bounds, size}, {upper_bounds, size});
1✔
1159
  return 0;
1✔
1160
}
1161

1162
extern "C" int openmc_weight_windows_get_survival_ratio(
3✔
1163
  int32_t index, double* ratio)
1164
{
1165
  if (int err = verify_ww_index(index))
3!
1166
    return err;
×
1167
  const auto& wws = variance_reduction::weight_windows[index];
3✔
1168
  *ratio = wws->survival_ratio();
3✔
1169
  return 0;
3✔
1170
}
1171

1172
extern "C" int openmc_weight_windows_set_survival_ratio(
1✔
1173
  int32_t index, double ratio)
1174
{
1175
  if (int err = verify_ww_index(index))
1!
1176
    return err;
×
1177
  const auto& wws = variance_reduction::weight_windows[index];
1✔
1178
  wws->survival_ratio() = ratio;
1✔
1179
  std::cout << "Survival ratio: " << wws->survival_ratio() << std::endl;
1✔
1180
  return 0;
1✔
1181
}
1182

1183
extern "C" int openmc_weight_windows_get_max_lower_bound_ratio(
3✔
1184
  int32_t index, double* lb_ratio)
1185
{
1186
  if (int err = verify_ww_index(index))
3!
1187
    return err;
×
1188
  const auto& wws = variance_reduction::weight_windows[index];
3✔
1189
  *lb_ratio = wws->max_lower_bound_ratio();
3✔
1190
  return 0;
3✔
1191
}
1192

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

1203
extern "C" int openmc_weight_windows_get_weight_cutoff(
3✔
1204
  int32_t index, double* cutoff)
1205
{
1206
  if (int err = verify_ww_index(index))
3!
1207
    return err;
×
1208
  const auto& wws = variance_reduction::weight_windows[index];
3✔
1209
  *cutoff = wws->weight_cutoff();
3✔
1210
  return 0;
3✔
1211
}
1212

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

1223
extern "C" int openmc_weight_windows_get_max_split(
3✔
1224
  int32_t index, int* max_split)
1225
{
1226
  if (int err = verify_ww_index(index))
3!
1227
    return err;
×
1228
  const auto& wws = variance_reduction::weight_windows[index];
3✔
1229
  *max_split = wws->max_split();
3✔
1230
  return 0;
3✔
1231
}
1232

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

1242
extern "C" int openmc_extend_weight_windows(
14✔
1243
  int32_t n, int32_t* index_start, int32_t* index_end)
1244
{
1245
  if (index_start)
14!
1246
    *index_start = variance_reduction::weight_windows.size();
14✔
1247
  if (index_end)
14!
1248
    *index_end = variance_reduction::weight_windows.size() + n - 1;
×
1249
  for (int i = 0; i < n; ++i)
28✔
1250
    variance_reduction::weight_windows.push_back(make_unique<WeightWindows>());
14✔
1251
  return 0;
14✔
1252
}
1253

1254
extern "C" size_t openmc_weight_windows_size()
14✔
1255
{
1256
  return variance_reduction::weight_windows.size();
14✔
1257
}
1258

1259
extern "C" int openmc_weight_windows_export(const char* filename)
18✔
1260
{
1261

1262
  if (!mpi::master)
18✔
1263
    return 0;
6✔
1264

1265
  std::string name = filename ? filename : "weight_windows.h5";
24✔
1266

1267
  write_message(fmt::format("Exporting weight windows to {}...", name), 5);
12✔
1268

1269
  hid_t ww_file = file_open(name, 'w');
12✔
1270

1271
  // Write file type
1272
  write_attribute(ww_file, "filetype", "weight_windows");
12✔
1273

1274
  // Write revisiion number for state point file
1275
  write_attribute(ww_file, "version", VERSION_WEIGHT_WINDOWS);
12✔
1276

1277
  hid_t weight_windows_group = create_group(ww_file, "weight_windows");
12✔
1278

1279
  hid_t mesh_group = create_group(ww_file, "meshes");
12✔
1280

1281
  std::vector<int32_t> mesh_ids;
12✔
1282
  std::vector<int32_t> ww_ids;
12✔
1283
  for (const auto& ww : variance_reduction::weight_windows) {
24✔
1284

1285
    ww->to_hdf5(weight_windows_group);
12✔
1286
    ww_ids.push_back(ww->id());
12✔
1287

1288
    // if the mesh has already been written, move on
1289
    int32_t mesh_id = ww->mesh()->id();
12✔
1290
    if (std::find(mesh_ids.begin(), mesh_ids.end(), mesh_id) != mesh_ids.end())
12!
1291
      continue;
×
1292

1293
    mesh_ids.push_back(mesh_id);
12✔
1294
    ww->mesh()->to_hdf5(mesh_group);
12✔
1295
  }
1296

1297
  write_attribute(mesh_group, "n_meshes", mesh_ids.size());
12✔
1298
  write_attribute(mesh_group, "ids", mesh_ids);
12✔
1299
  close_group(mesh_group);
12✔
1300

1301
  write_attribute(weight_windows_group, "n_weight_windows", ww_ids.size());
12✔
1302
  write_attribute(weight_windows_group, "ids", ww_ids);
12✔
1303
  close_group(weight_windows_group);
12✔
1304

1305
  file_close(ww_file);
12✔
1306

1307
  return 0;
12✔
1308
}
12✔
1309

1310
extern "C" int openmc_weight_windows_import(const char* filename)
1✔
1311
{
1312
  std::string name = filename ? filename : "weight_windows.h5";
1!
1313

1314
  if (mpi::master)
1!
1315
    write_message(fmt::format("Importing weight windows from {}...", name), 5);
1✔
1316

1317
  if (!file_exists(name)) {
1!
1318
    set_errmsg(fmt::format("File '{}' does not exist", name));
×
1319
  }
1320

1321
  hid_t ww_file = file_open(name, 'r');
1✔
1322

1323
  // Check that filetype is correct
1324
  std::string filetype;
1✔
1325
  read_attribute(ww_file, "filetype", filetype);
1✔
1326
  if (filetype != "weight_windows") {
1!
1327
    file_close(ww_file);
×
1328
    set_errmsg(fmt::format("File '{}' is not a weight windows file.", name));
×
1329
    return OPENMC_E_INVALID_ARGUMENT;
×
1330
  }
1331

1332
  // Check that the file version is compatible
1333
  std::array<int, 2> file_version;
1334
  read_attribute(ww_file, "version", file_version);
1✔
1335
  if (file_version[0] != VERSION_WEIGHT_WINDOWS[0]) {
1!
1336
    std::string err_msg =
1337
      fmt::format("File '{}' has version {} which is incompatible with the "
1338
                  "expected version ({}).",
1339
        name, file_version, VERSION_WEIGHT_WINDOWS);
×
1340
    set_errmsg(err_msg);
×
1341
    return OPENMC_E_INVALID_ARGUMENT;
×
1342
  }
×
1343

1344
  hid_t weight_windows_group = open_group(ww_file, "weight_windows");
1✔
1345

1346
  hid_t mesh_group = open_group(ww_file, "meshes");
1✔
1347

1348
  read_meshes(mesh_group);
1✔
1349

1350
  std::vector<std::string> names = group_names(weight_windows_group);
1✔
1351

1352
  for (const auto& name : names) {
2✔
1353
    WeightWindows::from_hdf5(weight_windows_group, name);
1✔
1354
  }
1355

1356
  close_group(weight_windows_group);
1✔
1357

1358
  file_close(ww_file);
1✔
1359

1360
  return 0;
1✔
1361
}
1✔
1362

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