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

openmc-dev / openmc / 11406622535

18 Oct 2024 03:32PM UTC coverage: 84.82% (-0.007%) from 84.827%
11406622535

Pull #3176

github

web-flow
Merge 7280d8c39 into dcb25575c
Pull Request #3176: MeshFilter rotation - solution to issue #3166

84 of 102 new or added lines in 5 files covered. (82.35%)

8 existing lines in 2 files now uncovered.

49720 of 58618 relevant lines covered (84.82%)

33846876.46 hits per line

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

78.19
/src/weight_windows.cpp
1
#include "openmc/weight_windows.h"
2

3
#include <algorithm>
4
#include <cmath>
5
#include <set>
6
#include <string>
7

8
#include "xtensor/xindex_view.hpp"
9
#include "xtensor/xio.hpp"
10
#include "xtensor/xmasked_view.hpp"
11
#include "xtensor/xnoalias.hpp"
12
#include "xtensor/xstrided_view.hpp"
13
#include "xtensor/xview.hpp"
14

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

33
#include <fmt/core.h>
34
#include <gsl/gsl-lite.hpp>
35

36
namespace openmc {
37

38
//==============================================================================
39
// Global variables
40
//==============================================================================
41

42
namespace variance_reduction {
43

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

48
} // namespace variance_reduction
49

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

54
void apply_weight_windows(Particle& p)
795,245,214✔
55
{
56
  if (!settings::weight_windows_on)
795,245,214✔
57
    return;
757,645,227✔
58

59
  // WW on photon and neutron only
60
  if (p.type() != ParticleType::neutron && p.type() != ParticleType::photon)
68,214,410✔
61
    return;
4,927,608✔
62

63
  // skip dead or no energy
64
  if (p.E() <= 0 || !p.alive())
63,286,802✔
65
    return;
1,372,097✔
66

67
  bool in_domain = false;
61,914,705✔
68
  // TODO: this is a linear search - should do something more clever
69
  WeightWindow weight_window;
61,914,705✔
70
  for (const auto& ww : variance_reduction::weight_windows) {
80,848,634✔
71
    weight_window = ww->get_weight_window(p);
66,550,893✔
72
    if (weight_window.is_valid())
66,550,893✔
73
      break;
47,616,964✔
74
  }
75
  // particle is not in any of the ww domains, do nothing
76
  if (!weight_window.is_valid())
61,914,705✔
77
    return;
14,297,741✔
78

79
  // get the paramters
80
  double weight = p.wgt();
47,616,964✔
81

82
  // first check to see if particle should be killed for weight cutoff
83
  if (p.wgt() < weight_window.weight_cutoff) {
47,616,964✔
84
    p.wgt() = 0.0;
×
85
    return;
×
86
  }
87

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

97
  // move weight window closer to the particle weight if needed
98
  if (p.ww_factor() > 1.0)
47,616,964✔
99
    weight_window.scale(p.ww_factor());
1,579,560✔
100

101
  // if particle's weight is above the weight window split until they are within
102
  // the window
103
  if (weight > weight_window.upper_weight) {
47,616,964✔
104
    // do not further split the particle if above the limit
105
    if (p.n_split() >= settings::max_history_splits)
10,700,762✔
106
      return;
10,016,977✔
107

108
    double n_split = std::ceil(weight / weight_window.upper_weight);
683,785✔
109
    double max_split = weight_window.max_split;
683,785✔
110
    n_split = std::min(n_split, max_split);
683,785✔
111

112
    p.n_split() += n_split;
683,785✔
113

114
    // Create secondaries and divide weight among all particles
115
    int i_split = std::round(n_split);
683,785✔
116
    for (int l = 0; l < i_split - 1; l++) {
2,475,613✔
117
      p.create_secondary(weight / n_split, p.u(), p.E(), p.type());
1,791,828✔
118
    }
119
    // remaining weight is applied to current particle
120
    p.wgt() = weight / n_split;
683,785✔
121

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

130
void free_memory_weight_windows()
6,740✔
131
{
132
  variance_reduction::ww_map.clear();
6,740✔
133
  variance_reduction::weight_windows.clear();
6,740✔
134
}
6,740✔
135

136
//==============================================================================
137
// WeightWindowSettings implementation
138
//==============================================================================
139

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

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

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

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

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

170
  // energy bounds
171
  if (check_for_node(node, "energy_bounds"))
73✔
172
    energy_bounds_ = get_node_array<double>(node, "energy_bounds");
70✔
173

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

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

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

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

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

210
  set_defaults();
73✔
211
}
73✔
212

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

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

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

233
  auto wws = WeightWindows::create();
12✔
234

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

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

241
  int32_t mesh_id;
242
  read_dataset(ww_group, "mesh", mesh_id);
12✔
243

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

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

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

260
  close_group(ww_group);
12✔
261

262
  return wws;
12✔
263
}
12✔
264

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

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

289
void WeightWindows::set_id(int32_t id)
445✔
290
{
291
  Expects(id >= 0 || id == C_NONE);
445✔
292

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

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

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

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

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

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

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

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

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

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

357
WeightWindow WeightWindows::get_weight_window(const Particle& p) const
66,550,893✔
358
{
359
  // check for particle type
360
  if (particle_type_ != p.type()) {
66,550,893✔
361
    return {};
4,377,696✔
362
  }
363

364
  // Get mesh index for particle's position
365
  const auto& mesh = this->mesh();
62,173,197✔
366
  int mesh_bin = mesh->get_bin(p.r());
62,173,197✔
367

368
  // particle is outside the weight window mesh
369
  if (mesh_bin < 0)
62,173,197✔
370
    return {};
×
371

372
  // particle energy
373
  double E = p.E();
62,173,197✔
374

375
  // check to make sure energy is in range, expects sorted energy values
376
  if (E < energy_bounds_.front() || E > energy_bounds_.back())
62,173,197✔
377
    return {};
58,224✔
378

379
  // get the mesh bin in energy group
380
  int energy_bin =
381
    lower_bound_index(energy_bounds_.begin(), energy_bounds_.end(), E);
62,114,973✔
382

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

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

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

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

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

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

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

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

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

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

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

468
void WeightWindows::set_bounds(
×
469
  gsl::span<const double> lower_bounds, double ratio)
×
470
{
×
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

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

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

493
  lower_ww_.fill(-1);
×
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) {
×
499
    fatal_error(fmt::format("Invalid value '{}' specified for weight window "
500
                            "generation. Must be one of: 'mean' or 'rel_err'",
501
      value));
85✔
502
  }
503

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

170✔
513
  ///////////////////////////
255✔
514
  // Extract tally data
85✔
515
  //
516
  // At the end of this section, the mean and rel_err array
×
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};
156✔
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))
156✔
540
    filter_types.push_back(FilterType::PARTICLE);
541

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

545
  // particle axis mapping
780✔
546
  transpose[0] =
156✔
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();
156✔
554

156✔
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>();
156✔
572
    const auto& particles = pf->particles();
156✔
573

574
    // find the index of the particle that matches these weight windows
575
    auto p_it =
576✔
576
      std::find(particles.begin(), particles.end(), this->particle_type_);
420✔
577
    // if the particle filter doesn't have particle data for the particle
420✔
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 {}",
156✔
582
        particle_type_to_str(this->particle_type_), pf->id(), tally->id(),
583
        this->id());
584
      fatal_error(msg);
156✔
585
    }
586

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

156✔
591
  // down-select data based on particle and score
24✔
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(),
156✔
595
    score_index, static_cast<int>(TallyResult::SUM_SQ));
156✔
596
  int n = tally->n_realizations_;
156✔
597

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

156✔
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_;
156✔
611
  auto& rel_err = this->upper_ww_;
612

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

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

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

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

626
  int e_bins = new_bounds.shape()[0];
627
  for (int e = 0; e < e_bins; e++) {
132✔
628
    // select all
629
    auto group_view = xt::view(new_bounds, e);
630

×
631
    // divide by volume of mesh elements
×
632
    for (int i = 0; i < group_view.size(); i++) {
×
633
      group_view[i] /= mesh_vols[i];
×
634
    }
635

636
    double group_max = *std::max_element(group_view.begin(), group_view.end());
132✔
637
    // normalize values in this energy group by the maximum value for this
638
    // group
639
    if (group_max > 0.0)
640
      group_view /= 2.0 * group_max;
156✔
641
  }
312✔
642

156✔
643
  // make sure that values where the mean is zero are set s.t. the weight window
312✔
644
  // value will be ignored
156✔
645
  xt::filter(new_bounds, sum <= 0.0).fill(-1.0);
646

647
  // make sure the weight windows are ignored for any locations where the
648
  // relative error is higher than the specified relative error threshold
649
  xt::filter(new_bounds, rel_err > threshold).fill(-1.0);
650

651
  // update the bounds of this weight window class
652
  // noalias avoids additional memory allocation
653
  xt::noalias(upper_ww_) = ratio * lower_ww_;
654
}
655

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

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

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

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

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

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

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

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

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

719
  write_dataset(ww_group, "mesh", this->mesh()->id());
720
  write_dataset(
156✔
721
    ww_group, "particle_type", openmc::particle_type_to_str(particle_type_));
722
  write_dataset(ww_group, "energy_bounds", energy_bounds_);
723
  write_dataset(ww_group, "lower_ww_bounds", lower_ww_);
576✔
724
  write_dataset(ww_group, "upper_ww_bounds", upper_ww_);
420✔
725
  write_dataset(ww_group, "survival_ratio", survival_ratio_);
×
726
  write_dataset(ww_group, "max_lower_bound_ratio", max_lb_ratio_);
727
  write_dataset(ww_group, "max_split", max_split_);
×
728
  write_dataset(ww_group, "weight_cutoff", weight_cutoff_);
729

730
  close_group(ww_group);
731
}
156✔
732

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

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

×
751
  update_interval_ = std::stoi(get_node_value(node, "update_interval"));
752
  on_the_fly_ = get_node_value_bool(node, "on_the_fly");
753

2,100✔
754
  std::vector<double> e_bounds;
1,968✔
755
  if (check_for_node(node, "energy_bounds")) {
×
756
    e_bounds = get_node_array<double>(node, "energy_bounds");
757
  } else {
×
758
    int p_type = static_cast<int>(particle_type);
759
    e_bounds.push_back(data::energy_min[p_type]);
760
    e_bounds.push_back(data::energy_max[p_type]);
132✔
761
  }
156✔
762

763
  // set method and parameters for updates
48✔
764
  method_ = get_node_value(node, "method");
765
  if (method_ == "magic") {
96✔
766
    // parse non-default update parameters if specified
767
    if (check_for_node(node, "update_parameters")) {
48✔
768
      pugi::xml_node params_node = node.child("update_parameters");
48✔
769
      if (check_for_node(params_node, "value"))
96✔
770
        tally_value_ = get_node_value(params_node, "value");
48✔
771
      if (check_for_node(params_node, "threshold"))
48✔
772
        threshold_ = std::stod(get_node_value(params_node, "threshold"));
48✔
773
      if (check_for_node(params_node, "ratio")) {
48✔
774
        ratio_ = std::stod(get_node_value(params_node, "ratio"));
48✔
775
      }
48✔
776
    }
48✔
777
    // check update parameter values
778
    if (tally_value_ != "mean" && tally_value_ != "rel_err") {
48✔
779
      fatal_error(fmt::format("Unsupported tally value '{}' specified for "
48✔
780
                              "weight window generation.",
781
        tally_value_));
24✔
782
    }
783
    if (threshold_ <= 0.0)
784
      fatal_error(fmt::format("Invalid relative error threshold '{}' (<= 0.0) "
24✔
785
                              "specified for weight window generation",
24✔
786
        ratio_));
24✔
787
    if (ratio_ <= 1.0)
788
      fatal_error(fmt::format("Invalid weight window ratio '{}' (<= 1.0) "
24✔
789
                              "specified for weight window generation"));
24✔
790
  } else {
791
    fatal_error(fmt::format(
792
      "Unknown weight window update method '{}' specified", method_));
793
  }
×
794

×
795
  // create a matching weight windows object
796
  auto wws = WeightWindows::create();
24✔
797
  ww_idx_ = wws->index();
24✔
798
  wws->set_mesh(mesh_idx);
799
  if (e_bounds.size() > 0)
24✔
800
    wws->set_energy_bounds(e_bounds);
24✔
801
  wws->set_particle_type(particle_type);
802
  wws->set_defaults();
24✔
803
}
24✔
804

24✔
805
void WeightWindowsGenerator::create_tally()
806
{
×
807
  const auto& wws = variance_reduction::weight_windows[ww_idx_];
×
808

×
809
  // create a tally based on the WWG information
810
  Tally* ww_tally = Tally::create();
811
  tally_idx_ = model::tally_map[ww_tally->id()];
812
  ww_tally->set_scores({"flux"});
24✔
813

24✔
814
  int32_t mesh_id = wws->mesh()->id();
815
  int32_t mesh_idx = model::mesh_map.at(mesh_id);
24✔
816
  // see if there's already a mesh filter using this mesh
24✔
817
  bool found_mesh_filter = false;
24✔
818
  for (const auto& f : model::tally_filters) {
24✔
819
    if (f->type() == FilterType::MESH) {
24✔
820
      const auto* mesh_filter = dynamic_cast<MeshFilter*>(f.get());
24✔
821
      if (mesh_filter->mesh() == mesh_idx && !mesh_filter->translated() &&
24✔
822
          !mesh_filter->rotated()) {
24✔
823
        ww_tally->add_filter(f.get());
824
        found_mesh_filter = true;
825
        break;
826
      }
24✔
UNCOV
827
    }
×
828
  }
UNCOV
829

×
830
  if (!found_mesh_filter) {
831
    auto mesh_filter = Filter::create("mesh");
24✔
UNCOV
832
    openmc_mesh_filter_set_mesh(mesh_filter->index(), model::mesh_map[mesh_id]);
×
833
    ww_tally->add_filter(mesh_filter);
UNCOV
834
  }
×
835

24✔
UNCOV
836
  const auto& e_bounds = wws->energy_bounds();
×
837
  if (e_bounds.size() > 0) {
838
    auto energy_filter = Filter::create("energy");
UNCOV
839
    openmc_energy_filter_set_bins(
×
840
      energy_filter->index(), e_bounds.size(), e_bounds.data());
×
841
    ww_tally->add_filter(energy_filter);
842
  }
843

844
  // add a particle filter
24✔
845
  auto particle_type = wws->particle_type();
24✔
846
  auto particle_filter = Filter::create("particle");
24✔
847
  auto pf = dynamic_cast<ParticleFilter*>(particle_filter);
24✔
848
  pf->set_particles({&particle_type, 1});
24✔
849
  ww_tally->add_filter(particle_filter);
24✔
850
}
24✔
851

24✔
852
void WeightWindowsGenerator::update() const
853
{
24✔
854
  const auto& wws = variance_reduction::weight_windows[ww_idx_];
855

24✔
856
  Tally* tally = model::tallies[tally_idx_].get();
857

858
  // if we're beyond the number of max realizations or not at the corrrect
24✔
859
  // update interval, skip the update
24✔
860
  if (max_realizations_ < tally->n_realizations_ ||
48✔
861
      tally->n_realizations_ % update_interval_ != 0)
862
    return;
24✔
863

24✔
864
  wws->update_magic(tally, tally_value_, threshold_, ratio_);
865

24✔
866
  // if we're not doing on the fly generation, reset the tally results once
48✔
867
  // we're done with the update
24✔
UNCOV
868
  if (!on_the_fly_)
×
869
    tally->reset();
×
870

×
871
  // TODO: deactivate or remove tally once weight window generation is
×
872
  // complete
×
873
}
×
874

875
//==============================================================================
876
// Non-member functions
877
//==============================================================================
878

24✔
879
void finalize_variance_reduction()
24✔
880
{
24✔
881
  for (const auto& wwg : variance_reduction::weight_windows_generators) {
24✔
882
    wwg->create_tally();
883
  }
884
}
24✔
885

24✔
886
//==============================================================================
24✔
887
// C API
48✔
888
//==============================================================================
24✔
889

24✔
890
int verify_ww_index(int32_t index)
891
{
892
  if (index < 0 || index >= variance_reduction::weight_windows.size()) {
893
    set_errmsg(fmt::format("Index '{}' for weight windows is invalid", index));
24✔
894
    return OPENMC_E_OUT_OF_BOUNDS;
24✔
895
  }
24✔
896
  return 0;
24✔
897
}
24✔
898

24✔
899
extern "C" int openmc_get_weight_windows_index(int32_t id, int32_t* idx)
900
{
120✔
901
  auto it = variance_reduction::ww_map.find(id);
902
  if (it == variance_reduction::ww_map.end()) {
120✔
903
    set_errmsg(fmt::format("No weight windows exist with ID={}", id));
904
    return OPENMC_E_INVALID_ID;
120✔
905
  }
906

907
  *idx = it->second;
908
  return 0;
120✔
909
}
24✔
910

96✔
911
extern "C" int openmc_weight_windows_get_id(int32_t index, int32_t* id)
912
{
24✔
913
  if (int err = verify_ww_index(index))
914
    return err;
915

916
  const auto& wws = variance_reduction::weight_windows.at(index);
24✔
917
  *id = wws->id();
×
918
  return 0;
919
}
920

921
extern "C" int openmc_weight_windows_set_id(int32_t index, int32_t id)
922
{
923
  if (int err = verify_ww_index(index))
924
    return err;
925

926
  const auto& wws = variance_reduction::weight_windows.at(index);
927
  wws->set_id(id);
6,626✔
928
  return 0;
929
}
6,650✔
930

24✔
931
extern "C" int openmc_weight_windows_update_magic(int32_t ww_idx,
932
  int32_t tally_idx, const char* value, double threshold, double ratio)
6,626✔
933
{
934
  if (int err = verify_ww_index(ww_idx))
935
    return err;
936

937
  if (tally_idx < 0 || tally_idx >= model::tallies.size()) {
938
    set_errmsg(fmt::format("Index '{}' for tally is invalid", tally_idx));
2,172✔
939
    return OPENMC_E_OUT_OF_BOUNDS;
940
  }
2,172✔
941

×
942
  // get the requested tally
×
943
  const Tally* tally = model::tallies.at(tally_idx).get();
944

2,172✔
945
  // get the WeightWindows object
946
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
947

180✔
948
  wws->update_magic(tally, value, threshold, ratio);
949

180✔
950
  return 0;
180✔
951
}
×
952

×
953
extern "C" int openmc_weight_windows_set_mesh(int32_t ww_idx, int32_t mesh_idx)
954
{
955
  if (int err = verify_ww_index(ww_idx))
180✔
956
    return err;
180✔
957
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
958
  wws->set_mesh(mesh_idx);
959
  return 0;
564✔
960
}
961

564✔
962
extern "C" int openmc_weight_windows_get_mesh(int32_t ww_idx, int32_t* mesh_idx)
×
963
{
964
  if (int err = verify_ww_index(ww_idx))
564✔
965
    return err;
564✔
966
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
564✔
967
  *mesh_idx = model::mesh_map.at(wws->mesh()->id());
968
  return 0;
969
}
168✔
970

971
extern "C" int openmc_weight_windows_set_energy_bounds(
168✔
972
  int32_t ww_idx, double* e_bounds, size_t e_bounds_size)
×
973
{
974
  if (int err = verify_ww_index(ww_idx))
168✔
975
    return err;
168✔
976
  const auto& wws = variance_reduction::weight_windows.at(ww_idx);
168✔
977
  wws->set_energy_bounds({e_bounds, e_bounds_size});
978
  return 0;
979
}
132✔
980

981
extern "C" int openmc_weight_windows_get_energy_bounds(
982
  int32_t ww_idx, const double** e_bounds, size_t* e_bounds_size)
132✔
983
{
×
984
  if (int err = verify_ww_index(ww_idx))
985
    return err;
132✔
986
  const auto& wws = variance_reduction::weight_windows[ww_idx].get();
×
987
  *e_bounds = wws->energy_bounds().data();
×
988
  *e_bounds_size = wws->energy_bounds().size();
989
  return 0;
990
}
991

132✔
992
extern "C" int openmc_weight_windows_set_particle(int32_t index, int particle)
993
{
994
  if (int err = verify_ww_index(index))
132✔
995
    return err;
996

132✔
997
  const auto& wws = variance_reduction::weight_windows.at(index);
998
  wws->set_particle_type(static_cast<ParticleType>(particle));
132✔
999
  return 0;
1000
}
1001

168✔
1002
extern "C" int openmc_weight_windows_get_particle(int32_t index, int* particle)
1003
{
168✔
1004
  if (int err = verify_ww_index(index))
×
1005
    return err;
168✔
1006

168✔
1007
  const auto& wws = variance_reduction::weight_windows.at(index);
168✔
1008
  *particle = static_cast<int>(wws->particle_type());
1009
  return 0;
1010
}
12✔
1011

1012
extern "C" int openmc_weight_windows_get_bounds(int32_t index,
12✔
1013
  const double** lower_bounds, const double** upper_bounds, size_t* size)
×
1014
{
12✔
1015
  if (int err = verify_ww_index(index))
12✔
1016
    return err;
12✔
1017

1018
  const auto& wws = variance_reduction::weight_windows[index];
1019
  *size = wws->lower_ww_bounds().size();
144✔
1020
  *lower_bounds = wws->lower_ww_bounds().data();
1021
  *upper_bounds = wws->upper_ww_bounds().data();
1022
  return 0;
144✔
1023
}
×
1024

144✔
1025
extern "C" int openmc_weight_windows_set_bounds(int32_t index,
144✔
1026
  const double* lower_bounds, const double* upper_bounds, size_t size)
144✔
1027
{
1028
  if (int err = verify_ww_index(index))
1029
    return err;
12✔
1030

1031
  const auto& wws = variance_reduction::weight_windows[index];
1032
  wws->set_bounds({lower_bounds, size}, {upper_bounds, size});
12✔
1033
  return 0;
×
1034
}
12✔
1035

12✔
1036
extern "C" int openmc_weight_windows_get_survival_ratio(
12✔
1037
  int32_t index, double* ratio)
12✔
1038
{
1039
  if (int err = verify_ww_index(index))
1040
    return err;
192✔
1041
  const auto& wws = variance_reduction::weight_windows[index];
1042
  *ratio = wws->survival_ratio();
192✔
1043
  return 0;
×
1044
}
1045

192✔
1046
extern "C" int openmc_weight_windows_set_survival_ratio(
192✔
1047
  int32_t index, double ratio)
192✔
1048
{
1049
  if (int err = verify_ww_index(index))
1050
    return err;
48✔
1051
  const auto& wws = variance_reduction::weight_windows[index];
1052
  wws->survival_ratio() = ratio;
48✔
1053
  std::cout << "Survival ratio: " << wws->survival_ratio() << std::endl;
×
1054
  return 0;
1055
}
48✔
1056

48✔
1057
extern "C" int openmc_weight_windows_get_max_lower_bound_ratio(
48✔
1058
  int32_t index, double* lb_ratio)
1059
{
1060
  if (int err = verify_ww_index(index))
528✔
1061
    return err;
1062
  const auto& wws = variance_reduction::weight_windows[index];
1063
  *lb_ratio = wws->max_lower_bound_ratio();
528✔
1064
  return 0;
×
1065
}
1066

528✔
1067
extern "C" int openmc_weight_windows_set_max_lower_bound_ratio(
528✔
1068
  int32_t index, double lb_ratio)
528✔
1069
{
528✔
1070
  if (int err = verify_ww_index(index))
528✔
1071
    return err;
1072
  const auto& wws = variance_reduction::weight_windows[index];
1073
  wws->max_lower_bound_ratio() = lb_ratio;
12✔
1074
  return 0;
1075
}
1076

12✔
1077
extern "C" int openmc_weight_windows_get_weight_cutoff(
×
1078
  int32_t index, double* cutoff)
1079
{
12✔
1080
  if (int err = verify_ww_index(index))
12✔
1081
    return err;
12✔
1082
  const auto& wws = variance_reduction::weight_windows[index];
1083
  *cutoff = wws->weight_cutoff();
1084
  return 0;
36✔
1085
}
1086

1087
extern "C" int openmc_weight_windows_set_weight_cutoff(
36✔
1088
  int32_t index, double cutoff)
×
1089
{
36✔
1090
  if (int err = verify_ww_index(index))
36✔
1091
    return err;
36✔
1092
  const auto& wws = variance_reduction::weight_windows[index];
1093
  wws->weight_cutoff() = cutoff;
1094
  return 0;
12✔
1095
}
1096

1097
extern "C" int openmc_weight_windows_get_max_split(
12✔
1098
  int32_t index, int* max_split)
×
1099
{
12✔
1100
  if (int err = verify_ww_index(index))
12✔
1101
    return err;
12✔
1102
  const auto& wws = variance_reduction::weight_windows[index];
12✔
1103
  *max_split = wws->max_split();
1104
  return 0;
1105
}
36✔
1106

1107
extern "C" int openmc_weight_windows_set_max_split(int32_t index, int max_split)
1108
{
36✔
1109
  if (int err = verify_ww_index(index))
×
1110
    return err;
36✔
1111
  const auto& wws = variance_reduction::weight_windows[index];
36✔
1112
  wws->max_split() = max_split;
36✔
1113
  return 0;
1114
}
1115

12✔
1116
extern "C" int openmc_extend_weight_windows(
1117
  int32_t n, int32_t* index_start, int32_t* index_end)
1118
{
12✔
1119
  if (index_start)
×
1120
    *index_start = variance_reduction::weight_windows.size();
12✔
1121
  if (index_end)
12✔
1122
    *index_end = variance_reduction::weight_windows.size() + n - 1;
12✔
1123
  for (int i = 0; i < n; ++i)
1124
    variance_reduction::weight_windows.push_back(make_unique<WeightWindows>());
1125
  return 0;
36✔
1126
}
1127

1128
extern "C" size_t openmc_weight_windows_size()
36✔
1129
{
×
1130
  return variance_reduction::weight_windows.size();
36✔
1131
}
36✔
1132

36✔
1133
extern "C" int openmc_weight_windows_export(const char* filename)
1134
{
1135

12✔
1136
  if (!mpi::master)
1137
    return 0;
1138

12✔
1139
  std::string name = filename ? filename : "weight_windows.h5";
×
1140

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

12✔
1143
  hid_t ww_file = file_open(name, 'w');
1144

1145
  // Write file type
36✔
1146
  write_attribute(ww_file, "filetype", "weight_windows");
1147

1148
  // Write revisiion number for state point file
36✔
1149
  write_attribute(ww_file, "version", VERSION_WEIGHT_WINDOWS);
×
1150

36✔
1151
  hid_t weight_windows_group = create_group(ww_file, "weight_windows");
36✔
1152

36✔
1153
  hid_t mesh_group = create_group(ww_file, "meshes");
1154

1155
  std::vector<int32_t> mesh_ids;
12✔
1156
  std::vector<int32_t> ww_ids;
1157
  for (const auto& ww : variance_reduction::weight_windows) {
12✔
1158

×
1159
    ww->to_hdf5(weight_windows_group);
12✔
1160
    ww_ids.push_back(ww->id());
12✔
1161

12✔
1162
    // if the mesh has already been written, move on
1163
    int32_t mesh_id = ww->mesh()->id();
1164
    if (std::find(mesh_ids.begin(), mesh_ids.end(), mesh_id) != mesh_ids.end())
168✔
1165
      continue;
1166

1167
    mesh_ids.push_back(mesh_id);
168✔
1168
    ww->mesh()->to_hdf5(mesh_group);
168✔
1169
  }
168✔
1170

×
1171
  write_attribute(mesh_group, "n_meshes", mesh_ids.size());
336✔
1172
  write_attribute(mesh_group, "ids", mesh_ids);
168✔
1173
  close_group(mesh_group);
168✔
1174

1175
  write_attribute(weight_windows_group, "n_weight_windows", ww_ids.size());
1176
  write_attribute(weight_windows_group, "ids", ww_ids);
168✔
1177
  close_group(weight_windows_group);
1178

168✔
1179
  file_close(ww_file);
1180

1181
  return 0;
48✔
1182
}
1183

1184
extern "C" int openmc_weight_windows_import(const char* filename)
48✔
1185
{
×
1186
  std::string name = filename ? filename : "weight_windows.h5";
1187

96✔
1188
  if (mpi::master)
1189
    write_message(fmt::format("Importing weight windows from {}...", name), 5);
48✔
1190

1191
  if (!file_exists(name)) {
48✔
1192
    set_errmsg(fmt::format("File '{}' does not exist", name));
1193
  }
1194

48✔
1195
  hid_t ww_file = file_open(name, 'r');
1196

1197
  // Check that filetype is correct
48✔
1198
  std::string filetype;
1199
  read_attribute(ww_file, "filetype", filetype);
48✔
1200
  if (filetype != "weight_windows") {
1201
    file_close(ww_file);
48✔
1202
    set_errmsg(fmt::format("File '{}' is not a weight windows file.", name));
1203
    return OPENMC_E_INVALID_ARGUMENT;
48✔
1204
  }
48✔
1205

96✔
1206
  // Check that the file version is compatible
1207
  std::array<int, 2> file_version;
48✔
1208
  read_attribute(ww_file, "version", file_version);
48✔
1209
  if (file_version[0] != VERSION_WEIGHT_WINDOWS[0]) {
1210
    std::string err_msg =
1211
      fmt::format("File '{}' has version {} which is incompatible with the "
48✔
1212
                  "expected version ({}).",
48✔
1213
        name, file_version, VERSION_WEIGHT_WINDOWS);
×
1214
    set_errmsg(err_msg);
1215
    return OPENMC_E_INVALID_ARGUMENT;
48✔
1216
  }
48✔
1217

1218
  hid_t weight_windows_group = open_group(ww_file, "weight_windows");
1219

48✔
1220
  std::vector<std::string> names = group_names(weight_windows_group);
48✔
1221

48✔
1222
  for (const auto& name : names) {
1223
    WeightWindows::from_hdf5(weight_windows_group, name);
48✔
1224
  }
48✔
1225

48✔
1226
  close_group(weight_windows_group);
1227

48✔
1228
  file_close(ww_file);
1229

48✔
1230
  return 0;
48✔
1231
}
1232

12✔
1233
} // 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