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

openmc-dev / openmc / 15371300071

01 Jun 2025 05:04AM UTC coverage: 85.143% (+0.3%) from 84.827%
15371300071

Pull #3176

github

web-flow
Merge 4f739184a into cb95c784b
Pull Request #3176: MeshFilter rotation - solution to issue #3166

86 of 99 new or added lines in 4 files covered. (86.87%)

3707 existing lines in 117 files now uncovered.

52212 of 61323 relevant lines covered (85.14%)

42831974.38 hits per line

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

80.43
/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)
103,507,613✔
62
    return;
10,944,120✔
63

64
  // skip dead or no energy
65
  if (p.E() <= 0 || !p.alive())
92,563,493✔
66
    return;
9,266,986✔
67

68
  bool in_domain = false;
83,296,507✔
69
  // TODO: this is a linear search - should do something more clever
70
  WeightWindow weight_window;
83,296,507✔
71
  for (const auto& ww : variance_reduction::weight_windows) {
106,839,720✔
72
    weight_window = ww->get_weight_window(p);
88,892,827✔
73
    if (weight_window.is_valid())
88,892,827✔
74
      break;
65,349,614✔
75
  }
76
  // particle is not in any of the ww domains, do nothing
77
  if (!weight_window.is_valid())
83,296,507✔
78
    return;
17,946,893✔
79

80
  // get the paramters
81
  double weight = p.wgt();
65,349,614✔
82

83
  // first check to see if particle should be killed for weight cutoff
84
  if (p.wgt() < weight_window.weight_cutoff) {
65,349,614✔
85
    p.wgt() = 0.0;
×
UNCOV
86
    return;
×
87
  }
88

89
  // check if particle is far above current weight window
90
  // only do this if the factor is not already set on the particle and a
91
  // maximum lower bound ratio is specified
92
  if (p.ww_factor() == 0.0 && weight_window.max_lb_ratio > 1.0 &&
65,353,484✔
93
      p.wgt() > weight_window.lower_weight * weight_window.max_lb_ratio) {
3,870✔
94
    p.ww_factor() =
3,870✔
95
      p.wgt() / (weight_window.lower_weight * weight_window.max_lb_ratio);
3,870✔
96
  }
97

98
  // move weight window closer to the particle weight if needed
99
  if (p.ww_factor() > 1.0)
65,349,614✔
100
    weight_window.scale(p.ww_factor());
1,960,575✔
101

102
  // if particle's weight is above the weight window split until they are within
103
  // the window
104
  if (weight > weight_window.upper_weight) {
65,349,614✔
105
    // do not further split the particle if above the limit
106
    if (p.n_split() >= settings::max_history_splits)
14,723,487✔
107
      return;
12,468,974✔
108

109
    double n_split = std::ceil(weight / weight_window.upper_weight);
2,254,513✔
110
    double max_split = weight_window.max_split;
2,254,513✔
111
    n_split = std::min(n_split, max_split);
2,254,513✔
112

113
    p.n_split() += n_split;
2,254,513✔
114

115
    // Create secondaries and divide weight among all particles
116
    int i_split = std::round(n_split);
2,254,513✔
117
    for (int l = 0; l < i_split - 1; l++) {
11,492,729✔
118
      p.split(weight / n_split);
9,238,216✔
119
    }
120
    // remaining weight is applied to current particle
121
    p.wgt() = weight / n_split;
2,254,513✔
122

123
  } else if (weight <= weight_window.lower_weight) {
50,626,127✔
124
    // if the particle weight is below the window, play Russian roulette
125
    double weight_survive =
126
      std::min(weight * weight_window.max_split, weight_window.survival_weight);
1,781,902✔
127
    russian_roulette(p, weight_survive);
1,781,902✔
128
  } // else particle is in the window, continue as normal
129
}
130

131
void free_memory_weight_windows()
9,188✔
132
{
133
  variance_reduction::ww_map.clear();
9,188✔
134
  variance_reduction::weight_windows.clear();
9,188✔
135
}
9,188✔
136

137
//==============================================================================
138
// WeightWindowSettings implementation
139
//==============================================================================
140

141
WeightWindows::WeightWindows(int32_t id)
336✔
142
{
143
  index_ = variance_reduction::weight_windows.size();
336✔
144
  set_id(id);
336✔
145
  set_defaults();
336✔
146
}
336✔
147

148
WeightWindows::WeightWindows(pugi::xml_node node)
107✔
149
{
150
  // Make sure required elements are present
151
  const vector<std::string> required_elems {
152
    "id", "particle_type", "lower_ww_bounds", "upper_ww_bounds"};
749✔
153
  for (const auto& elem : required_elems) {
535✔
154
    if (!check_for_node(node, elem.c_str())) {
428✔
UNCOV
155
      fatal_error(fmt::format("Must specify <{}> for weight windows.", elem));
×
156
    }
157
  }
158

159
  // Get weight windows ID
160
  int32_t id = std::stoi(get_node_value(node, "id"));
107✔
161
  this->set_id(id);
107✔
162

163
  // get the particle type
164
  auto particle_type_str = std::string(get_node_value(node, "particle_type"));
107✔
165
  particle_type_ = openmc::str_to_particle_type(particle_type_str);
107✔
166

167
  // Determine associated mesh
168
  int32_t mesh_id = std::stoi(get_node_value(node, "mesh"));
107✔
169
  set_mesh(model::mesh_map.at(mesh_id));
107✔
170

171
  // energy bounds
172
  if (check_for_node(node, "energy_bounds"))
107✔
173
    energy_bounds_ = get_node_array<double>(node, "energy_bounds");
89✔
174

175
  // get the survival value - optional
176
  if (check_for_node(node, "survival_ratio")) {
107✔
177
    survival_ratio_ = std::stod(get_node_value(node, "survival_ratio"));
107✔
178
    if (survival_ratio_ <= 1)
107✔
UNCOV
179
      fatal_error("Survival to lower weight window ratio must bigger than 1 "
×
180
                  "and less than the upper to lower weight window ratio.");
181
  }
182

183
  // get the max lower bound ratio - optional
184
  if (check_for_node(node, "max_lower_bound_ratio")) {
107✔
185
    max_lb_ratio_ = std::stod(get_node_value(node, "max_lower_bound_ratio"));
44✔
186
    if (max_lb_ratio_ < 1.0) {
44✔
UNCOV
187
      fatal_error("Maximum lower bound ratio must be larger than 1");
×
188
    }
189
  }
190

191
  // get the max split - optional
192
  if (check_for_node(node, "max_split")) {
107✔
193
    max_split_ = std::stod(get_node_value(node, "max_split"));
107✔
194
    if (max_split_ <= 1)
107✔
UNCOV
195
      fatal_error("max split must be larger than 1");
×
196
  }
197

198
  // weight cutoff - optional
199
  if (check_for_node(node, "weight_cutoff")) {
107✔
200
    weight_cutoff_ = std::stod(get_node_value(node, "weight_cutoff"));
107✔
201
    if (weight_cutoff_ <= 0)
107✔
UNCOV
202
      fatal_error("weight_cutoff must be larger than 0");
×
203
    if (weight_cutoff_ > 1)
107✔
UNCOV
204
      fatal_error("weight_cutoff must be less than 1");
×
205
  }
206

207
  // read the lower/upper weight bounds
208
  this->set_bounds(get_node_array<double>(node, "lower_ww_bounds"),
107✔
209
    get_node_array<double>(node, "upper_ww_bounds"));
214✔
210

211
  set_defaults();
107✔
212
}
107✔
213

214
WeightWindows::~WeightWindows()
443✔
215
{
216
  variance_reduction::ww_map.erase(id());
443✔
217
}
443✔
218

219
WeightWindows* WeightWindows::create(int32_t id)
126✔
220
{
221
  variance_reduction::weight_windows.push_back(make_unique<WeightWindows>());
126✔
222
  auto wws = variance_reduction::weight_windows.back().get();
126✔
223
  variance_reduction::ww_map[wws->id()] =
126✔
224
    variance_reduction::weight_windows.size() - 1;
126✔
225
  return wws;
126✔
226
}
227

228
WeightWindows* WeightWindows::from_hdf5(
15✔
229
  hid_t wws_group, const std::string& group_name)
230
{
231
  // collect ID from the name of this group
232
  hid_t ww_group = open_group(wws_group, group_name);
15✔
233

234
  auto wws = WeightWindows::create();
15✔
235

236
  std::string particle_type;
15✔
237
  read_dataset(ww_group, "particle_type", particle_type);
15✔
238
  wws->particle_type_ = openmc::str_to_particle_type(particle_type);
15✔
239

240
  read_dataset<double>(ww_group, "energy_bounds", wws->energy_bounds_);
15✔
241

242
  int32_t mesh_id;
243
  read_dataset(ww_group, "mesh", mesh_id);
15✔
244

245
  if (model::mesh_map.count(mesh_id) == 0) {
15✔
246
    fatal_error(
×
UNCOV
247
      fmt::format("Mesh {} used in weight windows does not exist.", mesh_id));
×
248
  }
249
  wws->set_mesh(model::mesh_map[mesh_id]);
15✔
250

251
  wws->lower_ww_ = xt::empty<double>(wws->bounds_size());
15✔
252
  wws->upper_ww_ = xt::empty<double>(wws->bounds_size());
15✔
253

254
  read_dataset<double>(ww_group, "lower_ww_bounds", wws->lower_ww_);
15✔
255
  read_dataset<double>(ww_group, "upper_ww_bounds", wws->upper_ww_);
15✔
256
  read_dataset(ww_group, "survival_ratio", wws->survival_ratio_);
15✔
257
  read_dataset(ww_group, "max_lower_bound_ratio", wws->max_lb_ratio_);
15✔
258
  read_dataset(ww_group, "max_split", wws->max_split_);
15✔
259
  read_dataset(ww_group, "weight_cutoff", wws->weight_cutoff_);
15✔
260

261
  close_group(ww_group);
15✔
262

263
  return wws;
15✔
264
}
15✔
265

266
void WeightWindows::set_defaults()
554✔
267
{
268
  // set energy bounds to the min/max energy supported by the data
269
  if (energy_bounds_.size() == 0) {
554✔
270
    int p_type = static_cast<int>(particle_type_);
354✔
271
    energy_bounds_.push_back(data::energy_min[p_type]);
354✔
272
    energy_bounds_.push_back(data::energy_max[p_type]);
354✔
273
  }
274
}
554✔
275

276
void WeightWindows::allocate_ww_bounds()
734✔
277
{
278
  auto shape = bounds_size();
734✔
279
  if (shape[0] * shape[1] == 0) {
734✔
280
    auto msg = fmt::format(
281
      "Size of weight window bounds is zero for WeightWindows {}", id());
×
UNCOV
282
    warning(msg);
×
283
  }
284
  lower_ww_ = xt::empty<double>(shape);
734✔
285
  lower_ww_.fill(-1);
734✔
286
  upper_ww_ = xt::empty<double>(shape);
734✔
287
  upper_ww_.fill(-1);
734✔
288
}
734✔
289

290
void WeightWindows::set_id(int32_t id)
653✔
291
{
292
  assert(id >= 0 || id == C_NONE);
564✔
293

294
  // Clear entry in mesh map in case one was already assigned
295
  if (id_ != C_NONE) {
653✔
296
    variance_reduction::ww_map.erase(id_);
653✔
297
    id_ = C_NONE;
653✔
298
  }
299

300
  // Ensure no other mesh has the same ID
301
  if (variance_reduction::ww_map.find(id) != variance_reduction::ww_map.end()) {
653✔
302
    throw std::runtime_error {
×
UNCOV
303
      fmt::format("Two weight windows have the same ID: {}", id)};
×
304
  }
305

306
  // If no ID is specified, auto-assign the next ID in the sequence
307
  if (id == C_NONE) {
653✔
308
    id = 0;
336✔
309
    for (const auto& m : variance_reduction::weight_windows) {
366✔
310
      id = std::max(id, m->id_);
30✔
311
    }
312
    ++id;
336✔
313
  }
314

315
  // Update ID and entry in the mesh map
316
  id_ = id;
653✔
317
  variance_reduction::ww_map[id] = index_;
653✔
318
}
653✔
319

320
void WeightWindows::set_energy_bounds(span<const double> bounds)
291✔
321
{
322
  energy_bounds_.clear();
291✔
323
  energy_bounds_.insert(energy_bounds_.begin(), bounds.begin(), bounds.end());
291✔
324
  // if the mesh is set, allocate space for weight window bounds
325
  if (mesh_idx_ != C_NONE)
291✔
326
    allocate_ww_bounds();
291✔
327
}
291✔
328

329
void WeightWindows::set_particle_type(ParticleType p_type)
351✔
330
{
331
  if (p_type != ParticleType::neutron && p_type != ParticleType::photon)
351✔
332
    fatal_error(
×
333
      fmt::format("Particle type '{}' cannot be applied to weight windows.",
×
UNCOV
334
        particle_type_to_str(p_type)));
×
335
  particle_type_ = p_type;
351✔
336
}
351✔
337

338
void WeightWindows::set_mesh(int32_t mesh_idx)
443✔
339
{
340
  if (mesh_idx < 0 || mesh_idx >= model::meshes.size())
443✔
UNCOV
341
    fatal_error(fmt::format("Could not find a mesh for index {}", mesh_idx));
×
342

343
  mesh_idx_ = mesh_idx;
443✔
344
  model::meshes[mesh_idx_]->prepare_for_point_location();
443✔
345
  allocate_ww_bounds();
443✔
346
}
443✔
347

UNCOV
348
void WeightWindows::set_mesh(const std::unique_ptr<Mesh>& mesh)
×
349
{
UNCOV
350
  set_mesh(mesh.get());
×
351
}
352

UNCOV
353
void WeightWindows::set_mesh(const Mesh* mesh)
×
354
{
UNCOV
355
  set_mesh(model::mesh_map[mesh->id_]);
×
356
}
357

358
WeightWindow WeightWindows::get_weight_window(const Particle& p) const
88,892,827✔
359
{
360
  // check for particle type
361
  if (particle_type_ != p.type()) {
88,892,827✔
362
    return {};
5,323,740✔
363
  }
364

365
  // Get mesh index for particle's position
366
  const auto& mesh = this->mesh();
83,569,087✔
367
  int mesh_bin = mesh->get_bin(p.r());
83,569,087✔
368

369
  // particle is outside the weight window mesh
370
  if (mesh_bin < 0)
83,569,087✔
UNCOV
371
    return {};
×
372

373
  // particle energy
374
  double E = p.E();
83,569,087✔
375

376
  // check to make sure energy is in range, expects sorted energy values
377
  if (E < energy_bounds_.front() || E > energy_bounds_.back())
83,569,087✔
378
    return {};
72,780✔
379

380
  // get the mesh bin in energy group
381
  int energy_bin =
382
    lower_bound_index(energy_bounds_.begin(), energy_bounds_.end(), E);
83,496,307✔
383

384
  // mesh_bin += energy_bin * mesh->n_bins();
385
  // Create individual weight window
386
  WeightWindow ww;
83,496,307✔
387
  ww.lower_weight = lower_ww_(energy_bin, mesh_bin);
83,496,307✔
388
  ww.upper_weight = upper_ww_(energy_bin, mesh_bin);
83,496,307✔
389
  ww.survival_weight = ww.lower_weight * survival_ratio_;
83,496,307✔
390
  ww.max_lb_ratio = max_lb_ratio_;
83,496,307✔
391
  ww.max_split = max_split_;
83,496,307✔
392
  ww.weight_cutoff = weight_cutoff_;
83,496,307✔
393
  return ww;
83,496,307✔
394
}
395

396
std::array<int, 2> WeightWindows::bounds_size() const
1,008✔
397
{
398
  int num_spatial_bins = this->mesh()->n_bins();
1,008✔
399
  int num_energy_bins =
400
    energy_bounds_.size() > 0 ? energy_bounds_.size() - 1 : 1;
1,008✔
401
  return {num_energy_bins, num_spatial_bins};
1,008✔
402
}
403

404
template<class T>
405
void WeightWindows::check_bounds(const T& lower, const T& upper) const
122✔
406
{
407
  // make sure that the upper and lower bounds have the same size
408
  if (lower.size() != upper.size()) {
122✔
UNCOV
409
    auto msg = fmt::format("The upper and lower weight window lengths do not "
×
410
                           "match.\n Lower size: {}\n Upper size: {}",
411
      lower.size(), upper.size());
×
412
    fatal_error(msg);
×
UNCOV
413
  }
×
414
  this->check_bounds(lower);
122✔
415
}
122✔
416

122✔
417
template<class T>
418
void WeightWindows::check_bounds(const T& bounds) const
419
{
122✔
UNCOV
420
  // check that the number of weight window entries is correct
×
421
  auto dims = this->bounds_size();
422
  if (bounds.size() != dims[0] * dims[1]) {
×
423
    auto err_msg =
×
UNCOV
424
      fmt::format("In weight window domain {} the number of spatial "
×
425
                  "energy/spatial bins ({}) does not match the number "
122✔
426
                  "of weight bins ({})",
122✔
UNCOV
427
        id_, dims, bounds.size());
×
428
    fatal_error(err_msg);
429
  }
430
}
×
UNCOV
431

×
432
void WeightWindows::set_bounds(const xt::xtensor<double, 2>& lower_bounds,
433
  const xt::xtensor<double, 2>& upper_bounds)
×
434
{
×
435

×
UNCOV
436
  this->check_bounds(lower_bounds, upper_bounds);
×
437

438
  // set new weight window values
439
  lower_ww_ = lower_bounds;
440
  upper_ww_ = upper_bounds;
122✔
441
}
442

443
void WeightWindows::set_bounds(
122✔
444
  const xt::xtensor<double, 2>& lower_bounds, double ratio)
122✔
UNCOV
445
{
×
446
  this->check_bounds(lower_bounds);
447

448
  // set new weight window values
449
  lower_ww_ = lower_bounds;
×
450
  upper_ww_ = lower_bounds;
×
UNCOV
451
  upper_ww_ *= ratio;
×
452
}
122✔
453

122✔
454
void WeightWindows::set_bounds(
455
  span<const double> lower_bounds, span<const double> upper_bounds)
456
{
122✔
457
  check_bounds(lower_bounds, upper_bounds);
122✔
UNCOV
458
  auto shape = this->bounds_size();
×
459
  lower_ww_ = xt::empty<double>(shape);
460
  upper_ww_ = xt::empty<double>(shape);
461

462
  // set new weight window values
×
463
  xt::view(lower_ww_, xt::all()) =
×
UNCOV
464
    xt::adapt(lower_bounds.data(), lower_ww_.shape());
×
465
  xt::view(upper_ww_, xt::all()) =
122✔
UNCOV
466
    xt::adapt(upper_bounds.data(), upper_ww_.shape());
×
467
}
468

469
void WeightWindows::set_bounds(span<const double> lower_bounds, double ratio)
×
470
{
×
UNCOV
471
  this->check_bounds(lower_bounds);
×
472

473
  auto shape = this->bounds_size();
474
  lower_ww_ = xt::empty<double>(shape);
475
  upper_ww_ = xt::empty<double>(shape);
×
476

×
UNCOV
477
  // set new weight window values
×
478
  xt::view(lower_ww_, xt::all()) =
479
    xt::adapt(lower_bounds.data(), lower_ww_.shape());
UNCOV
480
  xt::view(upper_ww_, xt::all()) =
×
481
    xt::adapt(lower_bounds.data(), upper_ww_.shape());
482
  upper_ww_ *= ratio;
483
}
UNCOV
484

×
485
void WeightWindows::update_weights(const Tally* tally, const std::string& value,
486
  double threshold, double ratio, WeightWindowUpdateMethod method)
487
{
×
UNCOV
488
  ///////////////////////////
×
489
  // Setup and checks
490
  ///////////////////////////
UNCOV
491
  this->check_tally_update_compatibility(tally);
×
492

493
  lower_ww_.fill(-1);
UNCOV
494
  upper_ww_.fill(-1);
×
495

496
  // determine which value to use
497
  const std::set<std::string> allowed_values = {"mean", "rel_err"};
×
498
  if (allowed_values.count(value) == 0) {
×
UNCOV
499
    fatal_error(fmt::format("Invalid value '{}' specified for weight window "
×
500
                            "generation. Must be one of: 'mean' or 'rel_err'",
501
      value));
502
  }
122✔
503

504
  // determine the index of the specified score
505
  int score_index = tally->score_index("flux");
122✔
506
  if (score_index == C_NONE) {
122✔
507
    fatal_error(
122✔
508
      fmt::format("A 'flux' score required for weight window generation "
122✔
509
                  "is not present on tally {}.",
510
        tally->id()));
511
  }
244✔
512

366✔
513
  ///////////////////////////
244✔
514
  // Extract tally data
366✔
515
  //
122✔
516
  // At the end of this section, the mean and rel_err array
UNCOV
517
  // is a 2D view of tally data (n_e_groups, n_mesh_bins)
×
518
  //
519
  ///////////////////////////
×
520

521
  // build a shape for a view of the tally results, this will always be
×
522
  // dimension 5 (3 filter dimensions, 1 score dimension, 1 results dimension)
×
523
  std::array<int, 5> shape = {
×
524
    1, 1, 1, tally->n_scores(), static_cast<int>(TallyResult::SIZE)};
525

526
  // set the shape for the filters applied on the tally
×
527
  for (int i = 0; i < tally->filters().size(); i++) {
×
528
    const auto& filter = model::tally_filters[tally->filters(i)];
×
529
    shape[i] = filter->n_bins();
×
530
  }
×
531

532
  // build the transpose information to re-order data according to filter type
533
  std::array<int, 5> transpose = {0, 1, 2, 3, 4};
3,290✔
534

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

538
  // assign other filter types to dummy positions if needed
539
  if (!tally->has_filter(FilterType::PARTICLE))
3,290✔
540
    filter_types.push_back(FilterType::PARTICLE);
541

3,290✔
542
  if (!tally->has_filter(FilterType::ENERGY))
3,290✔
543
    filter_types.push_back(FilterType::ENERGY);
544

545
  // particle axis mapping
16,450✔
546
  transpose[0] =
3,290✔
547
    std::find(filter_types.begin(), filter_types.end(), FilterType::PARTICLE) -
×
548
    filter_types.begin();
549

550
  // energy axis mapping
551
  transpose[1] =
552
    std::find(filter_types.begin(), filter_types.end(), FilterType::ENERGY) -
553
    filter_types.begin();
3,290✔
554

3,290✔
555
  // mesh axis mapping
×
556
  transpose[2] =
×
557
    std::find(filter_types.begin(), filter_types.end(), FilterType::MESH) -
558
    filter_types.begin();
×
559

560
  // get a fully reshaped view of the tally according to tally ordering of
561
  // filters
562
  auto tally_values = xt::reshape_view(tally->results(), shape);
563

564
  // get a that is (particle, energy, mesh, scores, values)
565
  auto transposed_view = xt::transpose(tally_values, transpose);
566

567
  // determine the dimension and index of the particle data
568
  int particle_idx = 0;
569
  if (tally->has_filter(FilterType::PARTICLE)) {
570
    // get the particle filter
571
    auto pf = tally->get_filter<ParticleFilter>();
3,290✔
572
    const auto& particles = pf->particles();
3,290✔
573

574
    // find the index of the particle that matches these weight windows
575
    auto p_it =
13,100✔
576
      std::find(particles.begin(), particles.end(), this->particle_type_);
9,810✔
577
    // if the particle filter doesn't have particle data for the particle
9,810✔
578
    // used on this weight windows instance, report an error
579
    if (p_it == particles.end()) {
580
      auto msg = fmt::format("Particle type '{}' not present on Filter {} for "
581
                             "Tally {} used to update WeightWindows {}",
3,290✔
582
        particle_type_to_str(this->particle_type_), pf->id(), tally->id(),
583
        this->id());
584
      fatal_error(msg);
3,290✔
585
    }
586

587
    // use the index of the particle in the filter to down-select data later
3,290✔
588
    particle_idx = p_it - particles.begin();
30✔
589
  }
590

3,290✔
591
  // down-select data based on particle and score
30✔
592
  auto sum = xt::view(transposed_view, particle_idx, xt::all(), xt::all(),
593
    score_index, static_cast<int>(TallyResult::SUM));
594
  auto sum_sq = xt::view(transposed_view, particle_idx, xt::all(), xt::all(),
3,290✔
595
    score_index, static_cast<int>(TallyResult::SUM_SQ));
3,290✔
596
  int n = tally->n_realizations_;
3,290✔
597

598
  //////////////////////////////////////////////
599
  //
3,290✔
600
  // Assign new weight windows
3,290✔
601
  //
3,290✔
602
  // Use references to the existing weight window data
603
  // to store and update the values
604
  //
3,290✔
605
  //////////////////////////////////////////////
3,290✔
606

3,290✔
607
  // up to this point the data arrays are views into the tally results (no
608
  // computation has been performed) now we'll switch references to the tally's
609
  // bounds to avoid allocating additional memory
610
  auto& new_bounds = this->lower_ww_;
3,290✔
611
  auto& rel_err = this->upper_ww_;
612

613
  // noalias avoids memory allocation here
3,290✔
614
  xt::noalias(new_bounds) = sum / n;
615

616
  xt::noalias(rel_err) =
3,290✔
617
    xt::sqrt(((sum_sq / n) - xt::square(new_bounds)) / (n - 1)) / new_bounds;
3,290✔
618
  xt::filter(rel_err, sum <= 0.0).fill(INFTY);
619

3,260✔
620
  if (value == "rel_err")
3,260✔
621
    xt::noalias(new_bounds) = 1 / rel_err;
622

623
  // get mesh volumes
624
  auto mesh_vols = this->mesh()->volumes();
3,260✔
625

626
  int e_bins = new_bounds.shape()[0];
627

3,260✔
628
  if (method == WeightWindowUpdateMethod::MAGIC) {
629
    // If we are computing weight windows with forward fluxes derived from a
630
    // Monte Carlo or forward random ray solve, we use the MAGIC algorithm.
×
631
    for (int e = 0; e < e_bins; e++) {
×
632
      // select all
×
633
      auto group_view = xt::view(new_bounds, e);
×
634

635
      // divide by volume of mesh elements
636
      for (int i = 0; i < group_view.size(); i++) {
3,260✔
637
        group_view[i] /= mesh_vols[i];
638
      }
639

640
      double group_max =
3,290✔
641
        *std::max_element(group_view.begin(), group_view.end());
6,580✔
642
      // normalize values in this energy group by the maximum value for this
3,290✔
643
      // group
6,580✔
644
      if (group_max > 0.0)
3,290✔
645
        group_view /= 2.0 * group_max;
646
    }
647
  } else {
648
    // If we are computing weight windows with adjoint fluxes derived from an
649
    // adjoint random ray solve, we use the FW-CADIS algorithm.
650
    for (int e = 0; e < e_bins; e++) {
651
      // select all
652
      auto group_view = xt::view(new_bounds, e);
653

654
      // divide by volume of mesh elements
655
      for (int i = 0; i < group_view.size(); i++) {
656
        group_view[i] /= mesh_vols[i];
657
      }
658
    }
3,290✔
659

3,290✔
660
    // We take the inverse, but are careful not to divide by zero e.g. if some
661
    // mesh bins are not reachable in the physical geometry.
662
    xt::noalias(new_bounds) =
3,290✔
663
      xt::where(xt::not_equal(new_bounds, 0.0), 1.0 / new_bounds, 0.0);
664
    auto max_val = xt::amax(new_bounds)();
3,290✔
665
    xt::noalias(new_bounds) = new_bounds / (2.0 * max_val);
6,580✔
666

3,290✔
667
    // For bins that were missed, we use the minimum weight window value. This
668
    // shouldn't matter except for plotting.
3,290✔
669
    auto min_val = xt::amin(new_bounds)();
15✔
670
    xt::noalias(new_bounds) =
671
      xt::where(xt::not_equal(new_bounds, 0.0), new_bounds, min_val);
672
  }
3,290✔
673

674
  // make sure that values where the mean is zero are set s.t. the weight window
3,290✔
675
  // value will be ignored
676
  xt::filter(new_bounds, sum <= 0.0).fill(-1.0);
3,290✔
677

678
  // make sure the weight windows are ignored for any locations where the
679
  // relative error is higher than the specified relative error threshold
2,550✔
680
  xt::filter(new_bounds, rel_err > threshold).fill(-1.0);
681

2,340✔
682
  // update the bounds of this weight window class
683
  // noalias avoids additional memory allocation
684
  xt::noalias(upper_ww_) = ratio * lower_ww_;
3,277,515✔
685
}
3,275,175✔
686

687
void WeightWindows::check_tally_update_compatibility(const Tally* tally)
688
{
689
  // define the set of allowed filters for the tally
2,340✔
690
  const std::set<FilterType> allowed_filters = {
691
    FilterType::MESH, FilterType::ENERGY, FilterType::PARTICLE};
692

2,340✔
693
  // retrieve a mapping of filter type to filter index for the tally
2,295✔
694
  auto filter_indices = tally->filter_indices();
2,340✔
695

696
  // a mesh filter is required for a tally used to update weight windows
697
  if (!filter_indices.count(FilterType::MESH)) {
698
    fatal_error(
6,160✔
699
      "A mesh filter is required for a tally to update weight window bounds");
700
  }
3,080✔
701

702
  // ensure the mesh filter is using the same mesh as this weight window object
703
  auto mesh_filter = tally->get_filter<MeshFilter>();
9,008,120✔
704

9,005,040✔
705
  // make sure that all of the filters present on the tally are allowed
706
  for (auto filter_pair : filter_indices) {
3,080✔
707
    if (allowed_filters.find(filter_pair.first) == allowed_filters.end()) {
708
      fatal_error(fmt::format("Invalid filter type '{}' found on tally "
709
                              "used for weight window generation.",
710
        model::tally_filters[tally->filters(filter_pair.second)]->type_str()));
3,080✔
711
    }
6,160✔
712
  }
3,080✔
713

3,080✔
714
  if (mesh_filter->mesh() != mesh_idx_) {
715
    int32_t mesh_filter_id = model::meshes[mesh_filter->mesh()]->id();
716
    int32_t ww_mesh_id = model::meshes[this->mesh_idx_]->id();
717
    fatal_error(fmt::format("Mesh filter {} uses a different mesh ({}) than "
3,080✔
718
                            "weight window {} mesh ({})",
3,080✔
719
      mesh_filter->id(), mesh_filter_id, id_, ww_mesh_id));
6,160✔
720
  }
721

722
  // if an energy filter exists, make sure the energy grid matches that of this
723
  // weight window object
724
  if (auto energy_filter = tally->get_filter<EnergyFilter>()) {
3,290✔
725
    std::vector<double> filter_bins = energy_filter->bins();
726
    std::set<double> filter_e_bounds(
727
      energy_filter->bins().begin(), energy_filter->bins().end());
728
    if (filter_e_bounds.size() != energy_bounds().size()) {
3,290✔
729
      fatal_error(
730
        fmt::format("Energy filter {} does not have the same number of energy "
731
                    "bounds ({}) as weight window object {} ({})",
732
          energy_filter->id(), filter_e_bounds.size(), id_,
3,290✔
733
          energy_bounds().size()));
3,290✔
734
    }
735

3,290✔
736
    for (auto e : energy_bounds()) {
737
      if (filter_e_bounds.count(e) == 0) {
738
        fatal_error(fmt::format(
739
          "Energy bounds of filter {} and weight windows {} do not match",
3,290✔
740
          energy_filter->id(), id_));
741
      }
742
    }
3,290✔
743
  }
744
}
745

3,290✔
746
void WeightWindows::to_hdf5(hid_t group) const
×
747
{
748
  hid_t ww_group = create_group(group, fmt::format("weight_windows_{}", id()));
749

750
  write_dataset(ww_group, "mesh", this->mesh()->id());
751
  write_dataset(
3,290✔
752
    ww_group, "particle_type", openmc::particle_type_to_str(particle_type_));
753
  write_dataset(ww_group, "energy_bounds", energy_bounds_);
754
  write_dataset(ww_group, "lower_ww_bounds", lower_ww_);
13,100✔
755
  write_dataset(ww_group, "upper_ww_bounds", upper_ww_);
9,810✔
UNCOV
756
  write_dataset(ww_group, "survival_ratio", survival_ratio_);
×
757
  write_dataset(ww_group, "max_lower_bound_ratio", max_lb_ratio_);
UNCOV
758
  write_dataset(ww_group, "max_split", max_split_);
×
759
  write_dataset(ww_group, "weight_cutoff", weight_cutoff_);
760

761
  close_group(ww_group);
762
}
3,290✔
UNCOV
763

×
UNCOV
764
WeightWindowsGenerator::WeightWindowsGenerator(pugi::xml_node node)
×
UNCOV
765
{
×
766
  // read information from the XML node
UNCOV
767
  int32_t mesh_id = std::stoi(get_node_value(node, "mesh"));
×
768
  int32_t mesh_idx = model::mesh_map[mesh_id];
769
  max_realizations_ = std::stoi(get_node_value(node, "max_realizations"));
770

771
  int32_t active_batches = settings::n_batches - settings::n_inactive;
772
  if (max_realizations_ > active_batches) {
3,290✔
773
    auto msg =
3,260✔
774
      fmt::format("The maximum number of specified tally realizations ({}) is "
775
                  "greater than the number of active batches ({}).",
3,260✔
776
        max_realizations_, active_batches);
3,260✔
UNCOV
777
    warning(msg);
×
UNCOV
778
  }
×
779
  auto tmp_str = get_node_value(node, "particle_type", true, true);
UNCOV
780
  auto particle_type = str_to_particle_type(tmp_str);
×
UNCOV
781

×
782
  update_interval_ = std::stoi(get_node_value(node, "update_interval"));
783
  on_the_fly_ = get_node_value_bool(node, "on_the_fly");
784

11,910✔
785
  std::vector<double> e_bounds;
8,650✔
UNCOV
786
  if (check_for_node(node, "energy_bounds")) {
×
787
    e_bounds = get_node_array<double>(node, "energy_bounds");
UNCOV
788
  } else {
×
789
    int p_type = static_cast<int>(particle_type);
790
    e_bounds.push_back(data::energy_min[p_type]);
791
    e_bounds.push_back(data::energy_max[p_type]);
3,260✔
792
  }
3,290✔
793

794
  // set method
165✔
795
  std::string method_string = get_node_value(node, "method");
796
  if (method_string == "magic") {
330✔
797
    method_ = WeightWindowUpdateMethod::MAGIC;
798
    if (settings::solver_type == SolverType::RANDOM_RAY &&
165✔
799
        FlatSourceDomain::adjoint_) {
165✔
800
      fatal_error("Random ray weight window generation with MAGIC cannot be "
330✔
801
                  "done in adjoint mode.");
165✔
802
    }
165✔
803
  } else if (method_string == "fw_cadis") {
165✔
804
    method_ = WeightWindowUpdateMethod::FW_CADIS;
165✔
805
    if (settings::solver_type != SolverType::RANDOM_RAY) {
165✔
806
      fatal_error("FW-CADIS can only be run in random ray solver mode.");
165✔
807
    }
165✔
808
    FlatSourceDomain::adjoint_ = true;
809
  } else {
165✔
810
    fatal_error(fmt::format(
165✔
811
      "Unknown weight window update method '{}' specified", method_string));
812
  }
111✔
813

814
  // parse non-default update parameters if specified
815
  if (check_for_node(node, "update_parameters")) {
111✔
816
    pugi::xml_node params_node = node.child("update_parameters");
111✔
817
    if (check_for_node(params_node, "value"))
111✔
818
      tally_value_ = get_node_value(params_node, "value");
819
    if (check_for_node(params_node, "threshold"))
111✔
820
      threshold_ = std::stod(get_node_value(params_node, "threshold"));
111✔
821
    if (check_for_node(params_node, "ratio")) {
822
      ratio_ = std::stod(get_node_value(params_node, "ratio"));
823
    }
824
  }
41✔
825

22✔
826
  // check update parameter values
22✔
827
  if (tally_value_ != "mean" && tally_value_ != "rel_err") {
111✔
828
    fatal_error(fmt::format("Unsupported tally value '{}' specified for "
111✔
829
                            "weight window generation.",
830
      tally_value_));
111✔
831
  }
111✔
832
  if (threshold_ <= 0.0)
833
    fatal_error(fmt::format("Invalid relative error threshold '{}' (<= 0.0) "
111✔
834
                            "specified for weight window generation",
111✔
835
      ratio_));
30✔
836
  if (ratio_ <= 1.0)
837
    fatal_error(fmt::format("Invalid weight window ratio '{}' (<= 1.0) "
81✔
838
                            "specified for weight window generation"));
81✔
839

81✔
840
  // create a matching weight windows object
841
  auto wws = WeightWindows::create();
842
  ww_idx_ = wws->index();
843
  wws->set_mesh(mesh_idx);
111✔
844
  if (e_bounds.size() > 0)
111✔
845
    wws->set_energy_bounds(e_bounds);
45✔
846
  wws->set_particle_type(particle_type);
45✔
847
  wws->set_defaults();
UNCOV
848
}
×
849

850
void WeightWindowsGenerator::create_tally()
851
{
66✔
852
  const auto& wws = variance_reduction::weight_windows[ww_idx_];
66✔
853

66✔
UNCOV
854
  // create a tally based on the WWG information
×
855
  Tally* ww_tally = Tally::create();
856
  tally_idx_ = model::tally_map[ww_tally->id()];
66✔
857
  ww_tally->set_scores({"flux"});
UNCOV
858

×
859
  int32_t mesh_id = wws->mesh()->id();
860
  int32_t mesh_idx = model::mesh_map.at(mesh_id);
861
  // see if there's already a mesh filter using this mesh
862
  bool found_mesh_filter = false;
863
  for (const auto& f : model::tally_filters) {
111✔
864
    if (f->type() == FilterType::MESH) {
30✔
865
      const auto* mesh_filter = dynamic_cast<MeshFilter*>(f.get());
30✔
866
      if (mesh_filter->mesh() == mesh_idx && !mesh_filter->translated() &&
30✔
867
          !mesh_filter->rotated()) {
30✔
868
        ww_tally->add_filter(f.get());
30✔
869
        found_mesh_filter = true;
30✔
870
        break;
30✔
871
      }
872
    }
873
  }
874

875
  if (!found_mesh_filter) {
111✔
UNCOV
876
    auto mesh_filter = Filter::create("mesh");
×
877
    openmc_mesh_filter_set_mesh(mesh_filter->index(), model::mesh_map[mesh_id]);
UNCOV
878
    ww_tally->add_filter(mesh_filter);
×
879
  }
880

111✔
UNCOV
881
  const auto& e_bounds = wws->energy_bounds();
×
882
  if (e_bounds.size() > 0) {
UNCOV
883
    auto energy_filter = Filter::create("energy");
×
884
    openmc_energy_filter_set_bins(
111✔
UNCOV
885
      energy_filter->index(), e_bounds.size(), e_bounds.data());
×
886
    ww_tally->add_filter(energy_filter);
887
  }
888

889
  // add a particle filter
111✔
890
  auto particle_type = wws->particle_type();
111✔
891
  auto particle_filter = Filter::create("particle");
111✔
892
  auto pf = dynamic_cast<ParticleFilter*>(particle_filter);
111✔
893
  pf->set_particles({&particle_type, 1});
111✔
894
  ww_tally->add_filter(particle_filter);
111✔
895
}
111✔
896

111✔
897
void WeightWindowsGenerator::update() const
898
{
111✔
899
  const auto& wws = variance_reduction::weight_windows[ww_idx_];
900

111✔
901
  Tally* tally = model::tallies[tally_idx_].get();
902

903
  // if we're beyond the number of max realizations or not at the corrrect
111✔
904
  // update interval, skip the update
111✔
905
  if (max_realizations_ < tally->n_realizations_ ||
222✔
906
      tally->n_realizations_ % update_interval_ != 0)
907
    return;
111✔
908

111✔
909
  wws->update_weights(tally, tally_value_, threshold_, ratio_, method_);
910

111✔
911
  // if we're not doing on the fly generation, reset the tally results once
354✔
912
  // we're done with the update
258✔
913
  if (!on_the_fly_)
15✔
914
    tally->reset();
30✔
915

15✔
916
  // TODO: deactivate or remove tally once weight window generation is
15✔
917
  // complete
15✔
918
}
15✔
919

920
//==============================================================================
921
// Non-member functions
922
//==============================================================================
923

111✔
924
void finalize_variance_reduction()
96✔
925
{
96✔
926
  for (const auto& wwg : variance_reduction::weight_windows_generators) {
96✔
927
    wwg->create_tally();
928
  }
929
}
111✔
930

111✔
931
//==============================================================================
111✔
932
// C API
222✔
933
//==============================================================================
111✔
934

111✔
935
int verify_ww_index(int32_t index)
936
{
937
  if (index < 0 || index >= variance_reduction::weight_windows.size()) {
938
    set_errmsg(fmt::format("Index '{}' for weight windows is invalid", index));
111✔
939
    return OPENMC_E_OUT_OF_BOUNDS;
111✔
940
  }
111✔
941
  return 0;
111✔
942
}
111✔
943

111✔
944
extern "C" int openmc_get_weight_windows_index(int32_t id, int32_t* idx)
945
{
3,305✔
946
  auto it = variance_reduction::ww_map.find(id);
947
  if (it == variance_reduction::ww_map.end()) {
3,305✔
948
    set_errmsg(fmt::format("No weight windows exist with ID={}", id));
949
    return OPENMC_E_INVALID_ID;
3,305✔
950
  }
951

952
  *idx = it->second;
953
  return 0;
3,305✔
954
}
3,125✔
955

180✔
956
extern "C" int openmc_weight_windows_get_id(int32_t index, int32_t* id)
957
{
3,125✔
958
  if (int err = verify_ww_index(index))
959
    return err;
960

961
  const auto& wws = variance_reduction::weight_windows.at(index);
3,125✔
962
  *id = wws->id();
×
963
  return 0;
964
}
965

966
extern "C" int openmc_weight_windows_set_id(int32_t index, int32_t id)
967
{
968
  if (int err = verify_ww_index(index))
969
    return err;
970

971
  const auto& wws = variance_reduction::weight_windows.at(index);
972
  wws->set_id(id);
9,013✔
973
  return 0;
974
}
9,124✔
975

111✔
976
extern "C" int openmc_weight_windows_update_magic(int32_t ww_idx,
977
  int32_t tally_idx, const char* value, double threshold, double ratio)
9,013✔
978
{
979
  if (int err = verify_ww_index(ww_idx))
980
    return err;
981

982
  if (tally_idx < 0 || tally_idx >= model::tallies.size()) {
983
    set_errmsg(fmt::format("Index '{}' for tally is invalid", tally_idx));
2,715✔
984
    return OPENMC_E_OUT_OF_BOUNDS;
985
  }
2,715✔
986

×
987
  // get the requested tally
×
988
  const Tally* tally = model::tallies.at(tally_idx).get();
989

2,715✔
990
  // get the WeightWindows object
991
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
992

225✔
993
  wws->update_weights(tally, value, threshold, ratio);
994

225✔
995
  return 0;
225✔
UNCOV
996
}
×
UNCOV
997

×
998
extern "C" int openmc_weight_windows_set_mesh(int32_t ww_idx, int32_t mesh_idx)
999
{
1000
  if (int err = verify_ww_index(ww_idx))
225✔
1001
    return err;
225✔
1002
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
1003
  wws->set_mesh(mesh_idx);
1004
  return 0;
705✔
1005
}
1006

705✔
UNCOV
1007
extern "C" int openmc_weight_windows_get_mesh(int32_t ww_idx, int32_t* mesh_idx)
×
1008
{
1009
  if (int err = verify_ww_index(ww_idx))
705✔
1010
    return err;
705✔
1011
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
705✔
1012
  *mesh_idx = model::mesh_map.at(wws->mesh()->id());
1013
  return 0;
1014
}
210✔
1015

1016
extern "C" int openmc_weight_windows_set_energy_bounds(
210✔
UNCOV
1017
  int32_t ww_idx, double* e_bounds, size_t e_bounds_size)
×
1018
{
1019
  if (int err = verify_ww_index(ww_idx))
210✔
1020
    return err;
210✔
1021
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
210✔
1022
  wws->set_energy_bounds({e_bounds, e_bounds_size});
1023
  return 0;
1024
}
165✔
1025

1026
extern "C" int openmc_weight_windows_get_energy_bounds(
1027
  int32_t ww_idx, const double** e_bounds, size_t* e_bounds_size)
165✔
UNCOV
1028
{
×
1029
  if (int err = verify_ww_index(ww_idx))
1030
    return err;
165✔
UNCOV
1031
  const auto& wws = variance_reduction::weight_windows[ww_idx].get();
×
UNCOV
1032
  *e_bounds = wws->energy_bounds().data();
×
1033
  *e_bounds_size = wws->energy_bounds().size();
1034
  return 0;
1035
}
1036

165✔
1037
extern "C" int openmc_weight_windows_set_particle(int32_t index, int particle)
1038
{
1039
  if (int err = verify_ww_index(index))
165✔
1040
    return err;
1041

165✔
1042
  const auto& wws = variance_reduction::weight_windows.at(index);
1043
  wws->set_particle_type(static_cast<ParticleType>(particle));
165✔
1044
  return 0;
1045
}
1046

210✔
1047
extern "C" int openmc_weight_windows_get_particle(int32_t index, int* particle)
1048
{
210✔
UNCOV
1049
  if (int err = verify_ww_index(index))
×
1050
    return err;
210✔
1051

210✔
1052
  const auto& wws = variance_reduction::weight_windows.at(index);
210✔
1053
  *particle = static_cast<int>(wws->particle_type());
1054
  return 0;
1055
}
15✔
1056

1057
extern "C" int openmc_weight_windows_get_bounds(int32_t index,
15✔
UNCOV
1058
  const double** lower_bounds, const double** upper_bounds, size_t* size)
×
1059
{
15✔
1060
  if (int err = verify_ww_index(index))
15✔
1061
    return err;
15✔
1062

1063
  const auto& wws = variance_reduction::weight_windows[index];
1064
  *size = wws->lower_ww_bounds().size();
180✔
1065
  *lower_bounds = wws->lower_ww_bounds().data();
1066
  *upper_bounds = wws->upper_ww_bounds().data();
1067
  return 0;
180✔
UNCOV
1068
}
×
1069

180✔
1070
extern "C" int openmc_weight_windows_set_bounds(int32_t index,
180✔
1071
  const double* lower_bounds, const double* upper_bounds, size_t size)
180✔
1072
{
1073
  if (int err = verify_ww_index(index))
1074
    return err;
15✔
1075

1076
  const auto& wws = variance_reduction::weight_windows[index];
1077
  wws->set_bounds({lower_bounds, size}, {upper_bounds, size});
15✔
UNCOV
1078
  return 0;
×
1079
}
15✔
1080

15✔
1081
extern "C" int openmc_weight_windows_get_survival_ratio(
15✔
1082
  int32_t index, double* ratio)
15✔
1083
{
1084
  if (int err = verify_ww_index(index))
1085
    return err;
240✔
1086
  const auto& wws = variance_reduction::weight_windows[index];
1087
  *ratio = wws->survival_ratio();
240✔
1088
  return 0;
×
1089
}
1090

240✔
1091
extern "C" int openmc_weight_windows_set_survival_ratio(
240✔
1092
  int32_t index, double ratio)
240✔
1093
{
1094
  if (int err = verify_ww_index(index))
1095
    return err;
60✔
1096
  const auto& wws = variance_reduction::weight_windows[index];
1097
  wws->survival_ratio() = ratio;
60✔
1098
  std::cout << "Survival ratio: " << wws->survival_ratio() << std::endl;
×
1099
  return 0;
1100
}
60✔
1101

60✔
1102
extern "C" int openmc_weight_windows_get_max_lower_bound_ratio(
60✔
1103
  int32_t index, double* lb_ratio)
1104
{
1105
  if (int err = verify_ww_index(index))
660✔
1106
    return err;
1107
  const auto& wws = variance_reduction::weight_windows[index];
1108
  *lb_ratio = wws->max_lower_bound_ratio();
660✔
1109
  return 0;
×
1110
}
1111

660✔
1112
extern "C" int openmc_weight_windows_set_max_lower_bound_ratio(
660✔
1113
  int32_t index, double lb_ratio)
660✔
1114
{
660✔
1115
  if (int err = verify_ww_index(index))
660✔
1116
    return err;
1117
  const auto& wws = variance_reduction::weight_windows[index];
1118
  wws->max_lower_bound_ratio() = lb_ratio;
15✔
1119
  return 0;
1120
}
1121

15✔
UNCOV
1122
extern "C" int openmc_weight_windows_get_weight_cutoff(
×
1123
  int32_t index, double* cutoff)
1124
{
15✔
1125
  if (int err = verify_ww_index(index))
15✔
1126
    return err;
15✔
1127
  const auto& wws = variance_reduction::weight_windows[index];
1128
  *cutoff = wws->weight_cutoff();
1129
  return 0;
45✔
1130
}
1131

1132
extern "C" int openmc_weight_windows_set_weight_cutoff(
45✔
UNCOV
1133
  int32_t index, double cutoff)
×
1134
{
45✔
1135
  if (int err = verify_ww_index(index))
45✔
1136
    return err;
45✔
1137
  const auto& wws = variance_reduction::weight_windows[index];
1138
  wws->weight_cutoff() = cutoff;
1139
  return 0;
15✔
1140
}
1141

1142
extern "C" int openmc_weight_windows_get_max_split(
15✔
UNCOV
1143
  int32_t index, int* max_split)
×
1144
{
15✔
1145
  if (int err = verify_ww_index(index))
15✔
1146
    return err;
15✔
1147
  const auto& wws = variance_reduction::weight_windows[index];
15✔
1148
  *max_split = wws->max_split();
1149
  return 0;
1150
}
45✔
1151

1152
extern "C" int openmc_weight_windows_set_max_split(int32_t index, int max_split)
1153
{
45✔
UNCOV
1154
  if (int err = verify_ww_index(index))
×
1155
    return err;
45✔
1156
  const auto& wws = variance_reduction::weight_windows[index];
45✔
1157
  wws->max_split() = max_split;
45✔
1158
  return 0;
1159
}
1160

15✔
1161
extern "C" int openmc_extend_weight_windows(
1162
  int32_t n, int32_t* index_start, int32_t* index_end)
1163
{
15✔
UNCOV
1164
  if (index_start)
×
1165
    *index_start = variance_reduction::weight_windows.size();
15✔
1166
  if (index_end)
15✔
1167
    *index_end = variance_reduction::weight_windows.size() + n - 1;
15✔
1168
  for (int i = 0; i < n; ++i)
1169
    variance_reduction::weight_windows.push_back(make_unique<WeightWindows>());
1170
  return 0;
45✔
1171
}
1172

1173
extern "C" size_t openmc_weight_windows_size()
45✔
UNCOV
1174
{
×
1175
  return variance_reduction::weight_windows.size();
45✔
1176
}
45✔
1177

45✔
1178
extern "C" int openmc_weight_windows_export(const char* filename)
1179
{
1180

15✔
1181
  if (!mpi::master)
1182
    return 0;
1183

15✔
UNCOV
1184
  std::string name = filename ? filename : "weight_windows.h5";
×
1185

15✔
1186
  write_message(fmt::format("Exporting weight windows to {}...", name), 5);
15✔
1187

15✔
1188
  hid_t ww_file = file_open(name, 'w');
1189

1190
  // Write file type
45✔
1191
  write_attribute(ww_file, "filetype", "weight_windows");
1192

1193
  // Write revisiion number for state point file
45✔
UNCOV
1194
  write_attribute(ww_file, "version", VERSION_WEIGHT_WINDOWS);
×
1195

45✔
1196
  hid_t weight_windows_group = create_group(ww_file, "weight_windows");
45✔
1197

45✔
1198
  hid_t mesh_group = create_group(ww_file, "meshes");
1199

1200
  std::vector<int32_t> mesh_ids;
15✔
1201
  std::vector<int32_t> ww_ids;
1202
  for (const auto& ww : variance_reduction::weight_windows) {
15✔
UNCOV
1203

×
1204
    ww->to_hdf5(weight_windows_group);
15✔
1205
    ww_ids.push_back(ww->id());
15✔
1206

15✔
1207
    // if the mesh has already been written, move on
1208
    int32_t mesh_id = ww->mesh()->id();
1209
    if (std::find(mesh_ids.begin(), mesh_ids.end(), mesh_id) != mesh_ids.end())
210✔
1210
      continue;
1211

1212
    mesh_ids.push_back(mesh_id);
210✔
1213
    ww->mesh()->to_hdf5(mesh_group);
210✔
1214
  }
210✔
UNCOV
1215

×
1216
  write_attribute(mesh_group, "n_meshes", mesh_ids.size());
420✔
1217
  write_attribute(mesh_group, "ids", mesh_ids);
210✔
1218
  close_group(mesh_group);
210✔
1219

1220
  write_attribute(weight_windows_group, "n_weight_windows", ww_ids.size());
1221
  write_attribute(weight_windows_group, "ids", ww_ids);
210✔
1222
  close_group(weight_windows_group);
1223

210✔
1224
  file_close(ww_file);
1225

1226
  return 0;
207✔
1227
}
1228

1229
extern "C" int openmc_weight_windows_import(const char* filename)
207✔
1230
{
42✔
1231
  std::string name = filename ? filename : "weight_windows.h5";
1232

330✔
1233
  if (mpi::master)
1234
    write_message(fmt::format("Importing weight windows from {}...", name), 5);
165✔
1235

1236
  if (!file_exists(name)) {
165✔
1237
    set_errmsg(fmt::format("File '{}' does not exist", name));
1238
  }
1239

165✔
1240
  hid_t ww_file = file_open(name, 'r');
1241

1242
  // Check that filetype is correct
165✔
1243
  std::string filetype;
1244
  read_attribute(ww_file, "filetype", filetype);
165✔
1245
  if (filetype != "weight_windows") {
1246
    file_close(ww_file);
165✔
1247
    set_errmsg(fmt::format("File '{}' is not a weight windows file.", name));
1248
    return OPENMC_E_INVALID_ARGUMENT;
165✔
1249
  }
165✔
1250

330✔
1251
  // Check that the file version is compatible
1252
  std::array<int, 2> file_version;
165✔
1253
  read_attribute(ww_file, "version", file_version);
165✔
1254
  if (file_version[0] != VERSION_WEIGHT_WINDOWS[0]) {
1255
    std::string err_msg =
1256
      fmt::format("File '{}' has version {} which is incompatible with the "
165✔
1257
                  "expected version ({}).",
165✔
UNCOV
1258
        name, file_version, VERSION_WEIGHT_WINDOWS);
×
1259
    set_errmsg(err_msg);
1260
    return OPENMC_E_INVALID_ARGUMENT;
165✔
1261
  }
165✔
1262

1263
  hid_t weight_windows_group = open_group(ww_file, "weight_windows");
1264

165✔
1265
  std::vector<std::string> names = group_names(weight_windows_group);
165✔
1266

165✔
1267
  for (const auto& name : names) {
1268
    WeightWindows::from_hdf5(weight_windows_group, name);
165✔
1269
  }
165✔
1270

165✔
1271
  close_group(weight_windows_group);
1272

165✔
1273
  file_close(ww_file);
1274

165✔
1275
  return 0;
165✔
1276
}
1277

15✔
1278
} // 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