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

openmc-dev / openmc / 15788487381

20 Jun 2025 09:56PM UTC coverage: 85.241% (+0.002%) from 85.239%
15788487381

Pull #3459

github

web-flow
Merge 36a935648 into 3d16d4b10
Pull Request #3459: Weight Window Birth Scaling

9 of 9 new or added lines in 2 files covered. (100.0%)

90 existing lines in 1 file now uncovered.

52535 of 61631 relevant lines covered (85.24%)

36674125.86 hits per line

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

80.6
/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/xindex_view.hpp"
10
#include "xtensor/xio.hpp"
11
#include "xtensor/xmasked_view.hpp"
12
#include "xtensor/xnoalias.hpp"
13
#include "xtensor/xstrided_view.hpp"
14
#include "xtensor/xview.hpp"
15

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

35
#include <fmt/core.h>
36

37
namespace openmc {
38

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

43
namespace variance_reduction {
44

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

49
} // namespace variance_reduction
50

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

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

60
  // WW on photon and neutron only
61
  if (p.type() != ParticleType::neutron && p.type() != ParticleType::photon)
86,366,794✔
62
    return;
11,693,462✔
63

64
  // skip dead or no energy
65
  if (p.E() <= 0 || !p.alive())
74,673,332✔
66
    return;
4,468,246✔
67

68
  bool in_domain = false;
70,205,086✔
69
  // TODO: this is a linear search - should do something more clever
70
  WeightWindow weight_window;
70,205,086✔
71
  for (const auto& ww : variance_reduction::weight_windows) {
87,788,278✔
72
    weight_window = ww->get_weight_window(p);
74,324,454✔
73
    if (weight_window.is_valid())
74,324,454✔
74
      break;
56,741,262✔
75
  }
76

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

89
  // particle is not in any of the ww domains, do nothing
90
  if (!weight_window.is_valid())
70,205,086✔
91
    return;
13,463,824✔
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.ww_born());
56,741,262✔
96

97
  // get the paramters
98
  double weight = p.wgt();
56,741,262✔
99

100
  // first check to see if particle should be killed for weight cutoff
101
  if (p.wgt() < weight_window.weight_cutoff) {
56,741,262✔
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 &&
56,744,100✔
110
      p.wgt() > weight_window.lower_weight * weight_window.max_lb_ratio) {
2,838✔
111
    p.ww_factor() =
2,838✔
112
      p.wgt() / (weight_window.lower_weight * weight_window.max_lb_ratio);
2,838✔
113
  }
114

115
  // move weight window closer to the particle weight if needed
116
  if (p.ww_factor() > 1.0)
56,741,262✔
117
    weight_window.scale(p.ww_factor());
1,437,755✔
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) {
56,741,262✔
122
    // do not further split the particle if above the limit
123
    if (p.n_split() >= settings::max_history_splits)
15,089,960✔
124
      return;
13,665,191✔
125

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

130
    p.n_split() += n_split;
1,424,769✔
131

132
    // Create secondaries and divide weight among all particles
133
    int i_split = std::round(n_split);
1,424,769✔
134
    for (int l = 0; l < i_split - 1; l++) {
5,933,020✔
135
      p.split(weight / n_split);
4,508,251✔
136
    }
137
    // remaining weight is applied to current particle
138
    p.wgt() = weight / n_split;
1,424,769✔
139

140
  } else if (weight <= weight_window.lower_weight) {
41,651,302✔
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,412,005✔
144
    russian_roulette(p, weight_survive);
1,412,005✔
145
  } // else particle is in the window, continue as normal
146
}
147

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

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

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

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

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

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

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

192
  // get the survival value - optional
193
  if (check_for_node(node, "survival_ratio")) {
79✔
194
    survival_ratio_ = std::stod(get_node_value(node, "survival_ratio"));
79✔
195
    if (survival_ratio_ <= 1)
79✔
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")) {
79✔
202
    max_lb_ratio_ = std::stod(get_node_value(node, "max_lower_bound_ratio"));
32✔
203
    if (max_lb_ratio_ < 1.0) {
32✔
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")) {
79✔
210
    max_split_ = std::stod(get_node_value(node, "max_split"));
79✔
211
    if (max_split_ <= 1)
79✔
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")) {
79✔
217
    weight_cutoff_ = std::stod(get_node_value(node, "weight_cutoff"));
79✔
218
    if (weight_cutoff_ <= 0)
79✔
UNCOV
219
      fatal_error("weight_cutoff must be larger than 0");
×
220
    if (weight_cutoff_ > 1)
79✔
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"),
79✔
226
    get_node_array<double>(node, "upper_ww_bounds"));
158✔
227

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

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

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

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

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

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

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

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

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

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

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

278
  close_group(ww_group);
11✔
279

280
  return wws;
11✔
281
}
11✔
282

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

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

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

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

317
  // Ensure no other mesh has the same ID
318
  if (variance_reduction::ww_map.find(id) != variance_reduction::ww_map.end()) {
479✔
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) {
479✔
325
    id = 0;
246✔
326
    for (const auto& m : variance_reduction::weight_windows) {
268✔
327
      id = std::max(id, m->id_);
22✔
328
    }
329
    ++id;
246✔
330
  }
331

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

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

346
void WeightWindows::set_particle_type(ParticleType p_type)
257✔
347
{
348
  if (p_type != ParticleType::neutron && p_type != ParticleType::photon)
257✔
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;
257✔
353
}
257✔
354

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

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

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

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

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

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

386
  // particle is outside the weight window mesh
387
  if (mesh_bin < 0)
70,420,378✔
UNCOV
388
    return {};
×
389

390
  // particle energy
391
  double E = p.E();
70,420,378✔
392

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

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

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

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

421
template<class T>
422
void WeightWindows::check_bounds(const T& lower, const T& upper) const
90✔
423
{
424
  // make sure that the upper and lower bounds have the same size
425
  if (lower.size() != upper.size()) {
90✔
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);
90✔
432
}
90✔
433

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

×
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;
90✔
458
}
459

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

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

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

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

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);
492
  upper_ww_ = xt::empty<double>(shape);
×
493

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

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

510
  lower_ww_.fill(-1);
UNCOV
511
  upper_ww_.fill(-1);
×
512

513
  // determine which value to use
514
  const std::set<std::string> allowed_values = {"mean", "rel_err"};
×
515
  if (allowed_values.count(value) == 0) {
×
UNCOV
516
    fatal_error(fmt::format("Invalid value '{}' specified for weight window "
×
517
                            "generation. Must be one of: 'mean' or 'rel_err'",
518
      value));
519
  }
90✔
520

521
  // determine the index of the specified score
522
  int score_index = tally->score_index("flux");
90✔
523
  if (score_index == C_NONE) {
90✔
524
    fatal_error(
90✔
525
      fmt::format("A 'flux' score required for weight window generation "
90✔
526
                  "is not present on tally {}.",
527
        tally->id()));
528
  }
180✔
529

270✔
530
  ///////////////////////////
180✔
531
  // Extract tally data
270✔
532
  //
90✔
533
  // At the end of this section, the mean and rel_err array
UNCOV
534
  // is a 2D view of tally data (n_e_groups, n_mesh_bins)
×
535
  //
UNCOV
536
  ///////////////////////////
×
537

538
  // build a shape for a view of the tally results, this will always be
×
539
  // dimension 5 (3 filter dimensions, 1 score dimension, 1 results dimension)
×
UNCOV
540
  std::array<int, 5> shape = {
×
541
    1, 1, 1, tally->n_scores(), static_cast<int>(TallyResult::SIZE)};
542

543
  // set the shape for the filters applied on the tally
×
544
  for (int i = 0; i < tally->filters().size(); i++) {
×
545
    const auto& filter = model::tally_filters[tally->filters(i)];
×
546
    shape[i] = filter->n_bins();
×
UNCOV
547
  }
×
548

549
  // build the transpose information to re-order data according to filter type
550
  std::array<int, 5> transpose = {0, 1, 2, 3, 4};
2,394✔
551

552
  // track our filter types and where we've added new ones
553
  std::vector<FilterType> filter_types = tally->filter_types();
554

555
  // assign other filter types to dummy positions if needed
556
  if (!tally->has_filter(FilterType::PARTICLE))
2,394✔
557
    filter_types.push_back(FilterType::PARTICLE);
558

2,394✔
559
  if (!tally->has_filter(FilterType::ENERGY))
2,394✔
560
    filter_types.push_back(FilterType::ENERGY);
561

562
  // particle axis mapping
11,970✔
563
  transpose[0] =
2,394✔
UNCOV
564
    std::find(filter_types.begin(), filter_types.end(), FilterType::PARTICLE) -
×
565
    filter_types.begin();
566

567
  // energy axis mapping
568
  transpose[1] =
569
    std::find(filter_types.begin(), filter_types.end(), FilterType::ENERGY) -
570
    filter_types.begin();
2,394✔
571

2,394✔
572
  // mesh axis mapping
×
UNCOV
573
  transpose[2] =
×
574
    std::find(filter_types.begin(), filter_types.end(), FilterType::MESH) -
UNCOV
575
    filter_types.begin();
×
576

577
  // get a fully reshaped view of the tally according to tally ordering of
578
  // filters
579
  auto tally_values = xt::reshape_view(tally->results(), shape);
580

581
  // get a that is (particle, energy, mesh, scores, values)
582
  auto transposed_view = xt::transpose(tally_values, transpose);
583

584
  // determine the dimension and index of the particle data
585
  int particle_idx = 0;
586
  if (tally->has_filter(FilterType::PARTICLE)) {
587
    // get the particle filter
588
    auto pf = tally->get_filter<ParticleFilter>();
2,394✔
589
    const auto& particles = pf->particles();
2,394✔
590

591
    // find the index of the particle that matches these weight windows
592
    auto p_it =
9,532✔
593
      std::find(particles.begin(), particles.end(), this->particle_type_);
7,138✔
594
    // if the particle filter doesn't have particle data for the particle
7,138✔
595
    // used on this weight windows instance, report an error
596
    if (p_it == particles.end()) {
597
      auto msg = fmt::format("Particle type '{}' not present on Filter {} for "
598
                             "Tally {} used to update WeightWindows {}",
2,394✔
599
        particle_type_to_str(this->particle_type_), pf->id(), tally->id(),
600
        this->id());
601
      fatal_error(msg);
2,394✔
602
    }
603

604
    // use the index of the particle in the filter to down-select data later
2,394✔
605
    particle_idx = p_it - particles.begin();
22✔
606
  }
607

2,394✔
608
  // down-select data based on particle and score
22✔
609
  auto sum = xt::view(transposed_view, particle_idx, xt::all(), xt::all(),
610
    score_index, static_cast<int>(TallyResult::SUM));
611
  auto sum_sq = xt::view(transposed_view, particle_idx, xt::all(), xt::all(),
2,394✔
612
    score_index, static_cast<int>(TallyResult::SUM_SQ));
2,394✔
613
  int n = tally->n_realizations_;
2,394✔
614

615
  //////////////////////////////////////////////
616
  //
2,394✔
617
  // Assign new weight windows
2,394✔
618
  //
2,394✔
619
  // Use references to the existing weight window data
620
  // to store and update the values
621
  //
2,394✔
622
  //////////////////////////////////////////////
2,394✔
623

2,394✔
624
  // up to this point the data arrays are views into the tally results (no
625
  // computation has been performed) now we'll switch references to the tally's
626
  // bounds to avoid allocating additional memory
627
  auto& new_bounds = this->lower_ww_;
2,394✔
628
  auto& rel_err = this->upper_ww_;
629

630
  // noalias avoids memory allocation here
2,394✔
631
  xt::noalias(new_bounds) = sum / n;
632

633
  xt::noalias(rel_err) =
2,394✔
634
    xt::sqrt(((sum_sq / n) - xt::square(new_bounds)) / (n - 1)) / new_bounds;
2,394✔
635
  xt::filter(rel_err, sum <= 0.0).fill(INFTY);
636

2,372✔
637
  if (value == "rel_err")
2,372✔
638
    xt::noalias(new_bounds) = 1 / rel_err;
639

640
  // get mesh volumes
641
  auto mesh_vols = this->mesh()->volumes();
2,372✔
642

643
  int e_bins = new_bounds.shape()[0];
644

2,372✔
645
  if (method == WeightWindowUpdateMethod::MAGIC) {
646
    // If we are computing weight windows with forward fluxes derived from a
647
    // Monte Carlo or forward random ray solve, we use the MAGIC algorithm.
×
648
    for (int e = 0; e < e_bins; e++) {
×
649
      // select all
×
UNCOV
650
      auto group_view = xt::view(new_bounds, e);
×
651

652
      // divide by volume of mesh elements
653
      for (int i = 0; i < group_view.size(); i++) {
2,372✔
654
        group_view[i] /= mesh_vols[i];
655
      }
656

657
      double group_max =
2,394✔
658
        *std::max_element(group_view.begin(), group_view.end());
4,788✔
659
      // normalize values in this energy group by the maximum value for this
2,394✔
660
      // group
4,788✔
661
      if (group_max > 0.0)
2,394✔
662
        group_view /= 2.0 * group_max;
663
    }
664
  } else {
665
    // If we are computing weight windows with adjoint fluxes derived from an
666
    // adjoint random ray solve, we use the FW-CADIS algorithm.
667
    for (int e = 0; e < e_bins; e++) {
668
      // select all
669
      auto group_view = xt::view(new_bounds, e);
670

671
      // divide by volume of mesh elements
672
      for (int i = 0; i < group_view.size(); i++) {
673
        group_view[i] /= mesh_vols[i];
674
      }
675
    }
2,394✔
676

2,394✔
677
    // We take the inverse, but are careful not to divide by zero e.g. if some
678
    // mesh bins are not reachable in the physical geometry.
679
    xt::noalias(new_bounds) =
2,394✔
680
      xt::where(xt::not_equal(new_bounds, 0.0), 1.0 / new_bounds, 0.0);
681
    auto max_val = xt::amax(new_bounds)();
2,394✔
682
    xt::noalias(new_bounds) = new_bounds / (2.0 * max_val);
4,788✔
683

2,394✔
684
    // For bins that were missed, we use the minimum weight window value. This
685
    // shouldn't matter except for plotting.
2,394✔
686
    auto min_val = xt::amin(new_bounds)();
11✔
687
    xt::noalias(new_bounds) =
688
      xt::where(xt::not_equal(new_bounds, 0.0), new_bounds, min_val);
689
  }
2,394✔
690

691
  // make sure that values where the mean is zero are set s.t. the weight window
2,394✔
692
  // value will be ignored
693
  xt::filter(new_bounds, sum <= 0.0).fill(-1.0);
2,394✔
694

695
  // make sure the weight windows are ignored for any locations where the
696
  // relative error is higher than the specified relative error threshold
1,870✔
697
  xt::filter(new_bounds, rel_err > threshold).fill(-1.0);
698

1,716✔
699
  // update the bounds of this weight window class
700
  // noalias avoids additional memory allocation
701
  xt::noalias(upper_ww_) = ratio * lower_ww_;
2,403,511✔
702
}
2,401,795✔
703

704
void WeightWindows::check_tally_update_compatibility(const Tally* tally)
705
{
706
  // define the set of allowed filters for the tally
1,716✔
707
  const std::set<FilterType> allowed_filters = {
708
    FilterType::MESH, FilterType::ENERGY, FilterType::PARTICLE};
709

1,716✔
710
  // retrieve a mapping of filter type to filter index for the tally
1,683✔
711
  auto filter_indices = tally->filter_indices();
1,716✔
712

713
  // a mesh filter is required for a tally used to update weight windows
714
  if (!filter_indices.count(FilterType::MESH)) {
715
    fatal_error(
4,480✔
716
      "A mesh filter is required for a tally to update weight window bounds");
717
  }
2,240✔
718

719
  // ensure the mesh filter is using the same mesh as this weight window object
720
  auto mesh_filter = tally->get_filter<MeshFilter>();
6,551,360✔
721

6,549,120✔
722
  // make sure that all of the filters present on the tally are allowed
723
  for (auto filter_pair : filter_indices) {
2,240✔
724
    if (allowed_filters.find(filter_pair.first) == allowed_filters.end()) {
725
      fatal_error(fmt::format("Invalid filter type '{}' found on tally "
726
                              "used for weight window generation.",
727
        model::tally_filters[tally->filters(filter_pair.second)]->type_str()));
2,240✔
728
    }
4,480✔
729
  }
2,240✔
730

2,240✔
731
  if (mesh_filter->mesh() != mesh_idx_) {
732
    int32_t mesh_filter_id = model::meshes[mesh_filter->mesh()]->id();
733
    int32_t ww_mesh_id = model::meshes[this->mesh_idx_]->id();
734
    fatal_error(fmt::format("Mesh filter {} uses a different mesh ({}) than "
2,240✔
735
                            "weight window {} mesh ({})",
2,240✔
736
      mesh_filter->id(), mesh_filter_id, id_, ww_mesh_id));
4,480✔
737
  }
738

739
  // if an energy filter exists, make sure the energy grid matches that of this
740
  // weight window object
741
  if (auto energy_filter = tally->get_filter<EnergyFilter>()) {
2,394✔
742
    std::vector<double> filter_bins = energy_filter->bins();
743
    std::set<double> filter_e_bounds(
744
      energy_filter->bins().begin(), energy_filter->bins().end());
745
    if (filter_e_bounds.size() != energy_bounds().size()) {
2,394✔
746
      fatal_error(
747
        fmt::format("Energy filter {} does not have the same number of energy "
748
                    "bounds ({}) as weight window object {} ({})",
749
          energy_filter->id(), filter_e_bounds.size(), id_,
2,394✔
750
          energy_bounds().size()));
2,394✔
751
    }
752

2,394✔
753
    for (auto e : energy_bounds()) {
754
      if (filter_e_bounds.count(e) == 0) {
755
        fatal_error(fmt::format(
756
          "Energy bounds of filter {} and weight windows {} do not match",
2,394✔
757
          energy_filter->id(), id_));
758
      }
759
    }
2,394✔
760
  }
761
}
762

2,394✔
UNCOV
763
void WeightWindows::to_hdf5(hid_t group) const
×
764
{
765
  hid_t ww_group = create_group(group, fmt::format("weight_windows_{}", id()));
766

767
  write_dataset(ww_group, "mesh", this->mesh()->id());
768
  write_dataset(
2,394✔
769
    ww_group, "particle_type", openmc::particle_type_to_str(particle_type_));
770
  write_dataset(ww_group, "energy_bounds", energy_bounds_);
771
  write_dataset(ww_group, "lower_ww_bounds", lower_ww_);
9,532✔
772
  write_dataset(ww_group, "upper_ww_bounds", upper_ww_);
7,138✔
UNCOV
773
  write_dataset(ww_group, "survival_ratio", survival_ratio_);
×
774
  write_dataset(ww_group, "max_lower_bound_ratio", max_lb_ratio_);
UNCOV
775
  write_dataset(ww_group, "max_split", max_split_);
×
776
  write_dataset(ww_group, "weight_cutoff", weight_cutoff_);
777

778
  close_group(ww_group);
779
}
2,394✔
780

×
781
WeightWindowsGenerator::WeightWindowsGenerator(pugi::xml_node node)
×
UNCOV
782
{
×
783
  // read information from the XML node
UNCOV
784
  int32_t mesh_id = std::stoi(get_node_value(node, "mesh"));
×
785
  int32_t mesh_idx = model::mesh_map[mesh_id];
786
  max_realizations_ = std::stoi(get_node_value(node, "max_realizations"));
787

788
  int32_t active_batches = settings::n_batches - settings::n_inactive;
789
  if (max_realizations_ > active_batches) {
2,394✔
790
    auto msg =
2,372✔
791
      fmt::format("The maximum number of specified tally realizations ({}) is "
792
                  "greater than the number of active batches ({}).",
2,372✔
793
        max_realizations_, active_batches);
2,372✔
794
    warning(msg);
×
UNCOV
795
  }
×
796
  auto tmp_str = get_node_value(node, "particle_type", true, true);
797
  auto particle_type = str_to_particle_type(tmp_str);
×
UNCOV
798

×
799
  update_interval_ = std::stoi(get_node_value(node, "update_interval"));
800
  on_the_fly_ = get_node_value_bool(node, "on_the_fly");
801

8,678✔
802
  std::vector<double> e_bounds;
6,306✔
UNCOV
803
  if (check_for_node(node, "energy_bounds")) {
×
804
    e_bounds = get_node_array<double>(node, "energy_bounds");
UNCOV
805
  } else {
×
806
    int p_type = static_cast<int>(particle_type);
807
    e_bounds.push_back(data::energy_min[p_type]);
808
    e_bounds.push_back(data::energy_max[p_type]);
2,372✔
809
  }
2,394✔
810

811
  // set method
121✔
812
  std::string method_string = get_node_value(node, "method");
813
  if (method_string == "magic") {
242✔
814
    method_ = WeightWindowUpdateMethod::MAGIC;
815
    if (settings::solver_type == SolverType::RANDOM_RAY &&
121✔
816
        FlatSourceDomain::adjoint_) {
121✔
817
      fatal_error("Random ray weight window generation with MAGIC cannot be "
242✔
818
                  "done in adjoint mode.");
121✔
819
    }
121✔
820
  } else if (method_string == "fw_cadis") {
121✔
821
    method_ = WeightWindowUpdateMethod::FW_CADIS;
121✔
822
    if (settings::solver_type != SolverType::RANDOM_RAY) {
121✔
823
      fatal_error("FW-CADIS can only be run in random ray solver mode.");
121✔
824
    }
121✔
825
    FlatSourceDomain::adjoint_ = true;
826
  } else {
121✔
827
    fatal_error(fmt::format(
121✔
828
      "Unknown weight window update method '{}' specified", method_string));
829
  }
81✔
830

831
  // parse non-default update parameters if specified
832
  if (check_for_node(node, "update_parameters")) {
81✔
833
    pugi::xml_node params_node = node.child("update_parameters");
81✔
834
    if (check_for_node(params_node, "value"))
81✔
835
      tally_value_ = get_node_value(params_node, "value");
836
    if (check_for_node(params_node, "threshold"))
81✔
837
      threshold_ = std::stod(get_node_value(params_node, "threshold"));
81✔
838
    if (check_for_node(params_node, "ratio")) {
839
      ratio_ = std::stod(get_node_value(params_node, "ratio"));
840
    }
841
  }
29✔
842

16✔
843
  // check update parameter values
16✔
844
  if (tally_value_ != "mean" && tally_value_ != "rel_err") {
81✔
845
    fatal_error(fmt::format("Unsupported tally value '{}' specified for "
81✔
846
                            "weight window generation.",
847
      tally_value_));
81✔
848
  }
81✔
849
  if (threshold_ <= 0.0)
850
    fatal_error(fmt::format("Invalid relative error threshold '{}' (<= 0.0) "
81✔
851
                            "specified for weight window generation",
81✔
852
      ratio_));
22✔
853
  if (ratio_ <= 1.0)
854
    fatal_error(fmt::format("Invalid weight window ratio '{}' (<= 1.0) "
59✔
855
                            "specified for weight window generation"));
59✔
856

59✔
857
  // create a matching weight windows object
858
  auto wws = WeightWindows::create();
859
  ww_idx_ = wws->index();
860
  wws->set_mesh(mesh_idx);
81✔
861
  if (e_bounds.size() > 0)
81✔
862
    wws->set_energy_bounds(e_bounds);
33✔
863
  wws->set_particle_type(particle_type);
33✔
864
  wws->set_defaults();
UNCOV
865
}
×
866

867
void WeightWindowsGenerator::create_tally()
868
{
48✔
869
  const auto& wws = variance_reduction::weight_windows[ww_idx_];
48✔
870

48✔
UNCOV
871
  // create a tally based on the WWG information
×
872
  Tally* ww_tally = Tally::create();
873
  tally_idx_ = model::tally_map[ww_tally->id()];
48✔
874
  ww_tally->set_scores({"flux"});
UNCOV
875

×
876
  int32_t mesh_id = wws->mesh()->id();
877
  int32_t mesh_idx = model::mesh_map.at(mesh_id);
878
  // see if there's already a mesh filter using this mesh
879
  bool found_mesh_filter = false;
880
  for (const auto& f : model::tally_filters) {
81✔
881
    if (f->type() == FilterType::MESH) {
22✔
882
      const auto* mesh_filter = dynamic_cast<MeshFilter*>(f.get());
22✔
883
      if (mesh_filter->mesh() == mesh_idx && !mesh_filter->translated()) {
22✔
884
        ww_tally->add_filter(f.get());
22✔
885
        found_mesh_filter = true;
22✔
886
        break;
22✔
887
      }
22✔
888
    }
889
  }
890

891
  if (!found_mesh_filter) {
892
    auto mesh_filter = Filter::create("mesh");
81✔
UNCOV
893
    openmc_mesh_filter_set_mesh(mesh_filter->index(), model::mesh_map[mesh_id]);
×
894
    ww_tally->add_filter(mesh_filter);
UNCOV
895
  }
×
896

897
  const auto& e_bounds = wws->energy_bounds();
81✔
UNCOV
898
  if (e_bounds.size() > 0) {
×
899
    auto energy_filter = Filter::create("energy");
UNCOV
900
    openmc_energy_filter_set_bins(
×
901
      energy_filter->index(), e_bounds.size(), e_bounds.data());
81✔
UNCOV
902
    ww_tally->add_filter(energy_filter);
×
903
  }
904

905
  // add a particle filter
906
  auto particle_type = wws->particle_type();
81✔
907
  auto particle_filter = Filter::create("particle");
81✔
908
  auto pf = dynamic_cast<ParticleFilter*>(particle_filter);
81✔
909
  pf->set_particles({&particle_type, 1});
81✔
910
  ww_tally->add_filter(particle_filter);
81✔
911
}
81✔
912

81✔
913
void WeightWindowsGenerator::update() const
81✔
914
{
915
  const auto& wws = variance_reduction::weight_windows[ww_idx_];
81✔
916

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

919
  // if we're beyond the number of max realizations or not at the corrrect
920
  // update interval, skip the update
81✔
921
  if (max_realizations_ < tally->n_realizations_ ||
81✔
922
      tally->n_realizations_ % update_interval_ != 0)
162✔
923
    return;
924

81✔
925
  wws->update_weights(tally, tally_value_, threshold_, ratio_, method_);
81✔
926

927
  // if we're not doing on the fly generation, reset the tally results once
81✔
928
  // we're done with the update
258✔
929
  if (!on_the_fly_)
188✔
930
    tally->reset();
11✔
931

11✔
932
  // TODO: deactivate or remove tally once weight window generation is
11✔
933
  // complete
11✔
934
}
11✔
935

936
//==============================================================================
937
// Non-member functions
938
//==============================================================================
939

81✔
940
void finalize_variance_reduction()
70✔
941
{
70✔
942
  for (const auto& wwg : variance_reduction::weight_windows_generators) {
70✔
943
    wwg->create_tally();
944
  }
945
}
81✔
946

81✔
947
//==============================================================================
81✔
948
// C API
162✔
949
//==============================================================================
81✔
950

81✔
951
int verify_ww_index(int32_t index)
952
{
953
  if (index < 0 || index >= variance_reduction::weight_windows.size()) {
954
    set_errmsg(fmt::format("Index '{}' for weight windows is invalid", index));
81✔
955
    return OPENMC_E_OUT_OF_BOUNDS;
81✔
956
  }
81✔
957
  return 0;
81✔
958
}
81✔
959

81✔
960
extern "C" int openmc_get_weight_windows_index(int32_t id, int32_t* idx)
961
{
2,405✔
962
  auto it = variance_reduction::ww_map.find(id);
963
  if (it == variance_reduction::ww_map.end()) {
2,405✔
964
    set_errmsg(fmt::format("No weight windows exist with ID={}", id));
965
    return OPENMC_E_INVALID_ID;
2,405✔
966
  }
967

968
  *idx = it->second;
969
  return 0;
2,405✔
970
}
2,273✔
971

132✔
972
extern "C" int openmc_weight_windows_get_id(int32_t index, int32_t* id)
973
{
2,273✔
974
  if (int err = verify_ww_index(index))
975
    return err;
976

977
  const auto& wws = variance_reduction::weight_windows.at(index);
2,273✔
UNCOV
978
  *id = wws->id();
×
979
  return 0;
980
}
981

982
extern "C" int openmc_weight_windows_set_id(int32_t index, int32_t id)
983
{
984
  if (int err = verify_ww_index(index))
985
    return err;
986

987
  const auto& wws = variance_reduction::weight_windows.at(index);
988
  wws->set_id(id);
6,654✔
989
  return 0;
990
}
6,735✔
991

81✔
992
extern "C" int openmc_weight_windows_update_magic(int32_t ww_idx,
993
  int32_t tally_idx, const char* value, double threshold, double ratio)
6,654✔
994
{
995
  if (int err = verify_ww_index(ww_idx))
996
    return err;
997

998
  if (tally_idx < 0 || tally_idx >= model::tallies.size()) {
999
    set_errmsg(fmt::format("Index '{}' for tally is invalid", tally_idx));
1,991✔
1000
    return OPENMC_E_OUT_OF_BOUNDS;
1001
  }
1,991✔
1002

×
UNCOV
1003
  // get the requested tally
×
1004
  const Tally* tally = model::tallies.at(tally_idx).get();
1005

1,991✔
1006
  // get the WeightWindows object
1007
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
1008

165✔
1009
  wws->update_weights(tally, value, threshold, ratio);
1010

165✔
1011
  return 0;
165✔
1012
}
×
UNCOV
1013

×
1014
extern "C" int openmc_weight_windows_set_mesh(int32_t ww_idx, int32_t mesh_idx)
1015
{
1016
  if (int err = verify_ww_index(ww_idx))
165✔
1017
    return err;
165✔
1018
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
1019
  wws->set_mesh(mesh_idx);
1020
  return 0;
517✔
1021
}
1022

517✔
UNCOV
1023
extern "C" int openmc_weight_windows_get_mesh(int32_t ww_idx, int32_t* mesh_idx)
×
1024
{
1025
  if (int err = verify_ww_index(ww_idx))
517✔
1026
    return err;
517✔
1027
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
517✔
1028
  *mesh_idx = model::mesh_map.at(wws->mesh()->id());
1029
  return 0;
1030
}
154✔
1031

1032
extern "C" int openmc_weight_windows_set_energy_bounds(
154✔
UNCOV
1033
  int32_t ww_idx, double* e_bounds, size_t e_bounds_size)
×
1034
{
1035
  if (int err = verify_ww_index(ww_idx))
154✔
1036
    return err;
154✔
1037
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
154✔
1038
  wws->set_energy_bounds({e_bounds, e_bounds_size});
1039
  return 0;
1040
}
121✔
1041

1042
extern "C" int openmc_weight_windows_get_energy_bounds(
1043
  int32_t ww_idx, const double** e_bounds, size_t* e_bounds_size)
121✔
UNCOV
1044
{
×
1045
  if (int err = verify_ww_index(ww_idx))
1046
    return err;
121✔
1047
  const auto& wws = variance_reduction::weight_windows[ww_idx].get();
×
UNCOV
1048
  *e_bounds = wws->energy_bounds().data();
×
1049
  *e_bounds_size = wws->energy_bounds().size();
1050
  return 0;
1051
}
1052

121✔
1053
extern "C" int openmc_weight_windows_set_particle(int32_t index, int particle)
1054
{
1055
  if (int err = verify_ww_index(index))
121✔
1056
    return err;
1057

121✔
1058
  const auto& wws = variance_reduction::weight_windows.at(index);
1059
  wws->set_particle_type(static_cast<ParticleType>(particle));
121✔
1060
  return 0;
1061
}
1062

154✔
1063
extern "C" int openmc_weight_windows_get_particle(int32_t index, int* particle)
1064
{
154✔
UNCOV
1065
  if (int err = verify_ww_index(index))
×
1066
    return err;
154✔
1067

154✔
1068
  const auto& wws = variance_reduction::weight_windows.at(index);
154✔
1069
  *particle = static_cast<int>(wws->particle_type());
1070
  return 0;
1071
}
11✔
1072

1073
extern "C" int openmc_weight_windows_get_bounds(int32_t index,
11✔
UNCOV
1074
  const double** lower_bounds, const double** upper_bounds, size_t* size)
×
1075
{
11✔
1076
  if (int err = verify_ww_index(index))
11✔
1077
    return err;
11✔
1078

1079
  const auto& wws = variance_reduction::weight_windows[index];
1080
  *size = wws->lower_ww_bounds().size();
132✔
1081
  *lower_bounds = wws->lower_ww_bounds().data();
1082
  *upper_bounds = wws->upper_ww_bounds().data();
1083
  return 0;
132✔
UNCOV
1084
}
×
1085

132✔
1086
extern "C" int openmc_weight_windows_set_bounds(int32_t index,
132✔
1087
  const double* lower_bounds, const double* upper_bounds, size_t size)
132✔
1088
{
1089
  if (int err = verify_ww_index(index))
1090
    return err;
11✔
1091

1092
  const auto& wws = variance_reduction::weight_windows[index];
1093
  wws->set_bounds({lower_bounds, size}, {upper_bounds, size});
11✔
UNCOV
1094
  return 0;
×
1095
}
11✔
1096

11✔
1097
extern "C" int openmc_weight_windows_get_survival_ratio(
11✔
1098
  int32_t index, double* ratio)
11✔
1099
{
1100
  if (int err = verify_ww_index(index))
1101
    return err;
176✔
1102
  const auto& wws = variance_reduction::weight_windows[index];
1103
  *ratio = wws->survival_ratio();
176✔
UNCOV
1104
  return 0;
×
1105
}
1106

176✔
1107
extern "C" int openmc_weight_windows_set_survival_ratio(
176✔
1108
  int32_t index, double ratio)
176✔
1109
{
1110
  if (int err = verify_ww_index(index))
1111
    return err;
44✔
1112
  const auto& wws = variance_reduction::weight_windows[index];
1113
  wws->survival_ratio() = ratio;
44✔
UNCOV
1114
  std::cout << "Survival ratio: " << wws->survival_ratio() << std::endl;
×
1115
  return 0;
1116
}
44✔
1117

44✔
1118
extern "C" int openmc_weight_windows_get_max_lower_bound_ratio(
44✔
1119
  int32_t index, double* lb_ratio)
1120
{
1121
  if (int err = verify_ww_index(index))
484✔
1122
    return err;
1123
  const auto& wws = variance_reduction::weight_windows[index];
1124
  *lb_ratio = wws->max_lower_bound_ratio();
484✔
UNCOV
1125
  return 0;
×
1126
}
1127

484✔
1128
extern "C" int openmc_weight_windows_set_max_lower_bound_ratio(
484✔
1129
  int32_t index, double lb_ratio)
484✔
1130
{
484✔
1131
  if (int err = verify_ww_index(index))
484✔
1132
    return err;
1133
  const auto& wws = variance_reduction::weight_windows[index];
1134
  wws->max_lower_bound_ratio() = lb_ratio;
11✔
1135
  return 0;
1136
}
1137

11✔
UNCOV
1138
extern "C" int openmc_weight_windows_get_weight_cutoff(
×
1139
  int32_t index, double* cutoff)
1140
{
11✔
1141
  if (int err = verify_ww_index(index))
11✔
1142
    return err;
11✔
1143
  const auto& wws = variance_reduction::weight_windows[index];
1144
  *cutoff = wws->weight_cutoff();
1145
  return 0;
33✔
1146
}
1147

1148
extern "C" int openmc_weight_windows_set_weight_cutoff(
33✔
UNCOV
1149
  int32_t index, double cutoff)
×
1150
{
33✔
1151
  if (int err = verify_ww_index(index))
33✔
1152
    return err;
33✔
1153
  const auto& wws = variance_reduction::weight_windows[index];
1154
  wws->weight_cutoff() = cutoff;
1155
  return 0;
11✔
1156
}
1157

1158
extern "C" int openmc_weight_windows_get_max_split(
11✔
UNCOV
1159
  int32_t index, int* max_split)
×
1160
{
11✔
1161
  if (int err = verify_ww_index(index))
11✔
1162
    return err;
11✔
1163
  const auto& wws = variance_reduction::weight_windows[index];
11✔
1164
  *max_split = wws->max_split();
1165
  return 0;
1166
}
33✔
1167

1168
extern "C" int openmc_weight_windows_set_max_split(int32_t index, int max_split)
1169
{
33✔
UNCOV
1170
  if (int err = verify_ww_index(index))
×
1171
    return err;
33✔
1172
  const auto& wws = variance_reduction::weight_windows[index];
33✔
1173
  wws->max_split() = max_split;
33✔
1174
  return 0;
1175
}
1176

11✔
1177
extern "C" int openmc_extend_weight_windows(
1178
  int32_t n, int32_t* index_start, int32_t* index_end)
1179
{
11✔
UNCOV
1180
  if (index_start)
×
1181
    *index_start = variance_reduction::weight_windows.size();
11✔
1182
  if (index_end)
11✔
1183
    *index_end = variance_reduction::weight_windows.size() + n - 1;
11✔
1184
  for (int i = 0; i < n; ++i)
1185
    variance_reduction::weight_windows.push_back(make_unique<WeightWindows>());
1186
  return 0;
33✔
1187
}
1188

1189
extern "C" size_t openmc_weight_windows_size()
33✔
UNCOV
1190
{
×
1191
  return variance_reduction::weight_windows.size();
33✔
1192
}
33✔
1193

33✔
1194
extern "C" int openmc_weight_windows_export(const char* filename)
1195
{
1196

11✔
1197
  if (!mpi::master)
1198
    return 0;
1199

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

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

11✔
1204
  hid_t ww_file = file_open(name, 'w');
1205

1206
  // Write file type
33✔
1207
  write_attribute(ww_file, "filetype", "weight_windows");
1208

1209
  // Write revisiion number for state point file
33✔
UNCOV
1210
  write_attribute(ww_file, "version", VERSION_WEIGHT_WINDOWS);
×
1211

33✔
1212
  hid_t weight_windows_group = create_group(ww_file, "weight_windows");
33✔
1213

33✔
1214
  hid_t mesh_group = create_group(ww_file, "meshes");
1215

1216
  std::vector<int32_t> mesh_ids;
11✔
1217
  std::vector<int32_t> ww_ids;
1218
  for (const auto& ww : variance_reduction::weight_windows) {
11✔
UNCOV
1219

×
1220
    ww->to_hdf5(weight_windows_group);
11✔
1221
    ww_ids.push_back(ww->id());
11✔
1222

11✔
1223
    // if the mesh has already been written, move on
1224
    int32_t mesh_id = ww->mesh()->id();
1225
    if (std::find(mesh_ids.begin(), mesh_ids.end(), mesh_id) != mesh_ids.end())
154✔
1226
      continue;
1227

1228
    mesh_ids.push_back(mesh_id);
154✔
1229
    ww->mesh()->to_hdf5(mesh_group);
154✔
1230
  }
154✔
UNCOV
1231

×
1232
  write_attribute(mesh_group, "n_meshes", mesh_ids.size());
308✔
1233
  write_attribute(mesh_group, "ids", mesh_ids);
154✔
1234
  close_group(mesh_group);
154✔
1235

1236
  write_attribute(weight_windows_group, "n_weight_windows", ww_ids.size());
1237
  write_attribute(weight_windows_group, "ids", ww_ids);
154✔
1238
  close_group(weight_windows_group);
1239

154✔
1240
  file_close(ww_file);
1241

1242
  return 0;
151✔
1243
}
1244

1245
extern "C" int openmc_weight_windows_import(const char* filename)
151✔
1246
{
30✔
1247
  std::string name = filename ? filename : "weight_windows.h5";
1248

242✔
1249
  if (mpi::master)
1250
    write_message(fmt::format("Importing weight windows from {}...", name), 5);
121✔
1251

1252
  if (!file_exists(name)) {
121✔
1253
    set_errmsg(fmt::format("File '{}' does not exist", name));
1254
  }
1255

121✔
1256
  hid_t ww_file = file_open(name, 'r');
1257

1258
  // Check that filetype is correct
121✔
1259
  std::string filetype;
1260
  read_attribute(ww_file, "filetype", filetype);
121✔
1261
  if (filetype != "weight_windows") {
1262
    file_close(ww_file);
121✔
1263
    set_errmsg(fmt::format("File '{}' is not a weight windows file.", name));
1264
    return OPENMC_E_INVALID_ARGUMENT;
121✔
1265
  }
121✔
1266

242✔
1267
  // Check that the file version is compatible
1268
  std::array<int, 2> file_version;
121✔
1269
  read_attribute(ww_file, "version", file_version);
121✔
1270
  if (file_version[0] != VERSION_WEIGHT_WINDOWS[0]) {
1271
    std::string err_msg =
1272
      fmt::format("File '{}' has version {} which is incompatible with the "
121✔
1273
                  "expected version ({}).",
121✔
UNCOV
1274
        name, file_version, VERSION_WEIGHT_WINDOWS);
×
1275
    set_errmsg(err_msg);
1276
    return OPENMC_E_INVALID_ARGUMENT;
121✔
1277
  }
121✔
1278

1279
  hid_t weight_windows_group = open_group(ww_file, "weight_windows");
1280

121✔
1281
  std::vector<std::string> names = group_names(weight_windows_group);
121✔
1282

121✔
1283
  for (const auto& name : names) {
1284
    WeightWindows::from_hdf5(weight_windows_group, name);
121✔
1285
  }
121✔
1286

121✔
1287
  close_group(weight_windows_group);
1288

121✔
1289
  file_close(ww_file);
1290

121✔
1291
  return 0;
121✔
1292
}
1293

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