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

openmc-dev / openmc / 12776996362

14 Jan 2025 09:49PM UTC coverage: 84.938% (+0.2%) from 84.729%
12776996362

Pull #3133

github

web-flow
Merge 0495246d9 into 549cc0973
Pull Request #3133: Kinetics parameters using Iterated Fission Probability

318 of 330 new or added lines in 10 files covered. (96.36%)

1658 existing lines in 66 files now uncovered.

50402 of 59340 relevant lines covered (84.94%)

33987813.96 hits per line

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

80.68
/src/tallies/tally.cpp
1
#include "openmc/tallies/tally.h"
2

3
#include "openmc/array.h"
4
#include "openmc/capi.h"
5
#include "openmc/constants.h"
6
#include "openmc/container_util.h"
7
#include "openmc/error.h"
8
#include "openmc/file_utils.h"
9
#include "openmc/mesh.h"
10
#include "openmc/message_passing.h"
11
#include "openmc/mgxs_interface.h"
12
#include "openmc/nuclide.h"
13
#include "openmc/particle.h"
14
#include "openmc/reaction.h"
15
#include "openmc/reaction_product.h"
16
#include "openmc/settings.h"
17
#include "openmc/simulation.h"
18
#include "openmc/source.h"
19
#include "openmc/tallies/derivative.h"
20
#include "openmc/tallies/filter.h"
21
#include "openmc/tallies/filter_cell.h"
22
#include "openmc/tallies/filter_cellborn.h"
23
#include "openmc/tallies/filter_cellfrom.h"
24
#include "openmc/tallies/filter_collision.h"
25
#include "openmc/tallies/filter_delayedgroup.h"
26
#include "openmc/tallies/filter_energy.h"
27
#include "openmc/tallies/filter_legendre.h"
28
#include "openmc/tallies/filter_mesh.h"
29
#include "openmc/tallies/filter_meshborn.h"
30
#include "openmc/tallies/filter_meshsurface.h"
31
#include "openmc/tallies/filter_particle.h"
32
#include "openmc/tallies/filter_sph_harm.h"
33
#include "openmc/tallies/filter_surface.h"
34
#include "openmc/xml_interface.h"
35

36
#include "xtensor/xadapt.hpp"
37
#include "xtensor/xbuilder.hpp" // for empty_like
38
#include "xtensor/xview.hpp"
39
#include <fmt/core.h>
40

41
#include <algorithm> // for max
42
#include <cstddef>   // for size_t
43
#include <string>
44

45
namespace openmc {
46

47
//==============================================================================
48
// Global variable definitions
49
//==============================================================================
50

51
namespace model {
52
//! a mapping of tally ID to index in the tallies vector
53
std::unordered_map<int, int> tally_map;
54
vector<unique_ptr<Tally>> tallies;
55
vector<int> active_tallies;
56
vector<int> active_analog_tallies;
57
vector<int> active_tracklength_tallies;
58
vector<int> active_collision_tallies;
59
vector<int> active_meshsurf_tallies;
60
vector<int> active_surface_tallies;
61
vector<int> active_pulse_height_tallies;
62
vector<int> pulse_height_cells;
63
} // namespace model
64

65
namespace simulation {
66
xt::xtensor_fixed<double, xt::xshape<N_GLOBAL_TALLIES, 3>> global_tallies;
67
int32_t n_realizations {0};
68
} // namespace simulation
69

70
double global_tally_absorption;
71
double global_tally_collision;
72
double global_tally_tracklength;
73
double global_tally_leakage;
74

75
//==============================================================================
76
// Tally object implementation
77
//==============================================================================
78

79
Tally::Tally(int32_t id)
2,030✔
80
{
81
  index_ = model::tallies.size(); // Avoids warning about narrowing
2,030✔
82
  this->set_id(id);
2,030✔
83
  this->set_filters({});
2,030✔
84
}
2,030✔
85

86
Tally::Tally(pugi::xml_node node)
24,835✔
87
{
88
  index_ = model::tallies.size(); // Avoids warning about narrowing
24,835✔
89

90
  // Copy and set tally id
91
  if (!check_for_node(node, "id")) {
24,835✔
92
    throw std::runtime_error {"Must specify id for tally in tally XML file."};
×
93
  }
94
  int32_t id = std::stoi(get_node_value(node, "id"));
24,835✔
95
  this->set_id(id);
24,835✔
96

97
  if (check_for_node(node, "name"))
24,835✔
98
    name_ = get_node_value(node, "name");
1,886✔
99

100
  if (check_for_node(node, "multiply_density")) {
24,835✔
101
    multiply_density_ = get_node_value_bool(node, "multiply_density");
36✔
102
  }
103

104
  // =======================================================================
105
  // READ DATA FOR FILTERS
106

107
  // Check if user is using old XML format and throw an error if so
108
  if (check_for_node(node, "filter")) {
24,835✔
109
    throw std::runtime_error {
×
110
      "Tally filters must be specified independently of "
111
      "tallies in a <filter> element. The <tally> element itself should "
112
      "have a list of filters that apply, e.g., <filters>1 2</filters> "
113
      "where 1 and 2 are the IDs of filters specified outside of "
114
      "<tally>."};
×
115
  }
116

117
  // Determine number of filters
118
  vector<int> filter_ids;
24,835✔
119
  if (check_for_node(node, "filters")) {
24,835✔
120
    filter_ids = get_node_array<int>(node, "filters");
24,019✔
121
  }
122

123
  // Allocate and store filter user ids
124
  vector<Filter*> filters;
24,835✔
125
  for (int filter_id : filter_ids) {
74,801✔
126
    // Determine if filter ID is valid
127
    auto it = model::filter_map.find(filter_id);
49,966✔
128
    if (it == model::filter_map.end()) {
49,966✔
129
      throw std::runtime_error {fmt::format(
×
130
        "Could not find filter {} specified on tally {}", filter_id, id_)};
×
131
    }
132

133
    // Store the index of the filter
134
    filters.push_back(model::tally_filters[it->second].get());
49,966✔
135
  }
136

137
  // Set the filters
138
  this->set_filters(filters);
24,835✔
139

140
  // Check for the presence of certain filter types
141
  bool has_energyout = energyout_filter_ >= 0;
24,835✔
142
  int particle_filter_index = C_NONE;
24,835✔
143
  for (gsl::index j = 0; j < filters_.size(); ++j) {
74,801✔
144
    int i_filter = filters_[j];
49,966✔
145
    const auto& f = model::tally_filters[i_filter].get();
49,966✔
146

147
    auto pf = dynamic_cast<ParticleFilter*>(f);
49,966✔
148
    if (pf)
49,966✔
149
      particle_filter_index = i_filter;
423✔
150

151
    // Change the tally estimator if a filter demands it
152
    FilterType filt_type = f->type();
49,966✔
153
    if (filt_type == FilterType::ENERGY_OUT ||
49,966✔
154
        filt_type == FilterType::LEGENDRE) {
155
      estimator_ = TallyEstimator::ANALOG;
6,661✔
156
    } else if (filt_type == FilterType::SPHERICAL_HARMONICS) {
43,305✔
157
      auto sf = dynamic_cast<SphericalHarmonicsFilter*>(f);
75✔
158
      if (sf->cosine() == SphericalHarmonicsCosine::scatter) {
75✔
159
        estimator_ = TallyEstimator::ANALOG;
12✔
160
      }
161
    } else if (filt_type == FilterType::SPATIAL_LEGENDRE ||
43,230✔
162
               filt_type == FilterType::ZERNIKE ||
43,170✔
163
               filt_type == FilterType::ZERNIKE_RADIAL) {
164
      estimator_ = TallyEstimator::COLLISION;
60✔
165
    }
166
  }
167

168
  // =======================================================================
169
  // READ DATA FOR NUCLIDES
170

171
  this->set_nuclides(node);
24,835✔
172

173
  // =======================================================================
174
  // READ DATA FOR SCORES
175

176
  this->set_scores(node);
24,835✔
177

178
  if (!check_for_node(node, "scores")) {
24,835✔
179
    fatal_error(fmt::format("No scores specified on tally {}.", id_));
×
180
  }
181

182
  // Check if tally is compatible with particle type
183
  if (!settings::photon_transport) {
24,835✔
184
    for (int score : scores_) {
57,906✔
185
      switch (score) {
33,451✔
186
      case SCORE_PULSE_HEIGHT:
×
187
        fatal_error(
×
188
          "For pulse-height tallies, photon transport needs to be activated.");
189
        break;
190
      }
191
    }
192
  }
193
  if (settings::photon_transport) {
24,835✔
194
    if (particle_filter_index == C_NONE) {
380✔
195
      for (int score : scores_) {
154✔
196
        switch (score) {
77✔
197
        case SCORE_INVERSE_VELOCITY:
×
198
          fatal_error("Particle filter must be used with photon "
×
199
                      "transport on and inverse velocity score");
200
          break;
201
        case SCORE_FLUX:
48✔
202
        case SCORE_TOTAL:
203
        case SCORE_SCATTER:
204
        case SCORE_NU_SCATTER:
205
        case SCORE_ABSORPTION:
206
        case SCORE_FISSION:
207
        case SCORE_NU_FISSION:
208
        case SCORE_CURRENT:
209
        case SCORE_EVENTS:
210
        case SCORE_DELAYED_NU_FISSION:
211
        case SCORE_PROMPT_NU_FISSION:
212
        case SCORE_DECAY_RATE:
213
          warning("You are tallying the '" + reaction_name(score) +
48✔
214
                  "' score and haven't used a particle filter. This score will "
215
                  "include contributions from all particles.");
216
          break;
48✔
217
        }
218
      }
219
    }
220
  } else {
221
    if (particle_filter_index >= 0) {
24,455✔
222
      const auto& f = model::tally_filters[particle_filter_index].get();
120✔
223
      auto pf = dynamic_cast<ParticleFilter*>(f);
120✔
224
      for (auto p : pf->particles()) {
360✔
225
        if (p != ParticleType::neutron) {
240✔
226
          warning(fmt::format(
120✔
227
            "Particle filter other than NEUTRON used with "
228
            "photon transport turned off. All tallies for particle type {}"
229
            " will have no scores",
230
            static_cast<int>(p)));
240✔
231
        }
232
      }
233
    }
234
  }
235

236
  // Check for a tally derivative.
237
  if (check_for_node(node, "derivative")) {
24,835✔
238
    int deriv_id = std::stoi(get_node_value(node, "derivative"));
340✔
239

240
    // Find the derivative with the given id, and store it's index.
241
    auto it = model::tally_deriv_map.find(deriv_id);
340✔
242
    if (it == model::tally_deriv_map.end()) {
340✔
243
      fatal_error(fmt::format(
×
244
        "Could not find derivative {} specified on tally {}", deriv_id, id_));
×
245
    }
246

247
    deriv_ = it->second;
340✔
248

249
    // Only analog or collision estimators are supported for differential
250
    // tallies.
251
    if (estimator_ == TallyEstimator::TRACKLENGTH) {
340✔
252
      estimator_ = TallyEstimator::COLLISION;
255✔
253
    }
254

255
    const auto& deriv = model::tally_derivs[deriv_];
340✔
256
    if (deriv.variable == DerivativeVariable::NUCLIDE_DENSITY ||
340✔
257
        deriv.variable == DerivativeVariable::TEMPERATURE) {
204✔
258
      for (int i_nuc : nuclides_) {
459✔
259
        if (has_energyout && i_nuc == -1) {
255✔
260
          fatal_error(fmt::format(
×
261
            "Error on tally {}: Cannot use a "
262
            "'nuclide_density' or 'temperature' derivative on a tally with an "
263
            "outgoing energy filter and 'total' nuclide rate. Instead, tally "
264
            "each nuclide in the material individually.",
265
            id_));
×
266
          // Note that diff tallies with these characteristics would work
267
          // correctly if no tally events occur in the perturbed material
268
          // (e.g. pertrubing moderator but only tallying fuel), but this
269
          // case would be hard to check for by only reading inputs.
270
        }
271
      }
272
    }
273
  }
274

275
  // If settings.xml trigger is turned on, create tally triggers
276
  if (settings::trigger_on) {
24,835✔
277
    this->init_triggers(node);
196✔
278
  }
279

280
  // =======================================================================
281
  // SET TALLY ESTIMATOR
282

283
  // Check if user specified estimator
284
  if (check_for_node(node, "estimator")) {
24,835✔
285
    std::string est = get_node_value(node, "estimator");
20,147✔
286
    if (est == "analog") {
20,147✔
287
      estimator_ = TallyEstimator::ANALOG;
8,300✔
288
    } else if (est == "tracklength" || est == "track-length" ||
12,707✔
289
               est == "pathlength" || est == "path-length") {
12,707✔
290
      // If the estimator was set to an analog estimator, this means the
291
      // tally needs post-collision information
292
      if (estimator_ == TallyEstimator::ANALOG ||
11,417✔
293
          estimator_ == TallyEstimator::COLLISION) {
11,417✔
294
        throw std::runtime_error {fmt::format("Cannot use track-length "
×
295
                                              "estimator for tally {}",
296
          id_)};
×
297
      }
298

299
      // Set estimator to track-length estimator
300
      estimator_ = TallyEstimator::TRACKLENGTH;
11,417✔
301

302
    } else if (est == "collision") {
430✔
303
      // If the estimator was set to an analog estimator, this means the
304
      // tally needs post-collision information
305
      if (estimator_ == TallyEstimator::ANALOG) {
430✔
306
        throw std::runtime_error {fmt::format("Cannot use collision estimator "
×
307
                                              "for tally ",
308
          id_)};
×
309
      }
310

311
      // Set estimator to collision estimator
312
      estimator_ = TallyEstimator::COLLISION;
430✔
313

314
    } else {
315
      throw std::runtime_error {
×
316
        fmt::format("Invalid estimator '{}' on tally {}", est, id_)};
×
317
    }
318
  }
20,147✔
319

320
#ifdef LIBMESH
321
  // ensure a tracklength tally isn't used with a libMesh filter
322
  for (auto i : this->filters_) {
13,205✔
323
    auto df = dynamic_cast<MeshFilter*>(model::tally_filters[i].get());
8,819✔
324
    if (df) {
8,819✔
325
      auto lm = dynamic_cast<LibMesh*>(model::meshes[df->mesh()].get());
1,095✔
326
      if (lm && estimator_ == TallyEstimator::TRACKLENGTH) {
1,095✔
327
        fatal_error("A tracklength estimator cannot be used with "
328
                    "an unstructured LibMesh tally.");
329
      }
330
    }
331
  }
332
#endif
333
}
24,835✔
334

335
Tally::~Tally()
26,865✔
336
{
337
  model::tally_map.erase(id_);
26,865✔
338
}
26,865✔
339

340
Tally* Tally::create(int32_t id)
53✔
341
{
342
  model::tallies.push_back(make_unique<Tally>(id));
53✔
343
  return model::tallies.back().get();
53✔
344
}
345

346
void Tally::set_id(int32_t id)
28,841✔
347
{
348
  Expects(id >= 0 || id == C_NONE);
28,841✔
349

350
  // Clear entry in tally map if an ID was already assigned before
351
  if (id_ != C_NONE) {
28,841✔
352
    model::tally_map.erase(id_);
1,976✔
353
    id_ = C_NONE;
1,976✔
354
  }
355

356
  // Make sure no other tally has the same ID
357
  if (model::tally_map.find(id) != model::tally_map.end()) {
28,841✔
358
    throw std::runtime_error {
×
359
      fmt::format("Two tallies have the same ID: {}", id)};
×
360
  }
361

362
  // If no ID specified, auto-assign next ID in sequence
363
  if (id == C_NONE) {
28,841✔
364
    id = 0;
2,030✔
365
    for (const auto& t : model::tallies) {
6,453✔
366
      id = std::max(id, t->id_);
4,423✔
367
    }
368
    ++id;
2,030✔
369
  }
370

371
  // Update ID and entry in tally map
372
  id_ = id;
28,841✔
373
  model::tally_map[id] = index_;
28,841✔
374
}
28,841✔
375

376
std::vector<FilterType> Tally::filter_types() const
156✔
377
{
378
  std::vector<FilterType> filter_types;
156✔
379
  for (auto idx : this->filters())
576✔
380
    filter_types.push_back(model::tally_filters[idx]->type());
420✔
381
  return filter_types;
156✔
382
}
×
383

384
std::unordered_map<FilterType, int32_t> Tally::filter_indices() const
156✔
385
{
386
  std::unordered_map<FilterType, int32_t> filter_indices;
156✔
387
  for (int i = 0; i < this->filters().size(); i++) {
576✔
388
    const auto& f = model::tally_filters[this->filters(i)];
420✔
389

390
    filter_indices[f->type()] = i;
420✔
391
  }
392
  return filter_indices;
156✔
393
}
×
394

395
bool Tally::has_filter(FilterType filter_type) const
468✔
396
{
397
  for (auto idx : this->filters()) {
1,044✔
398
    if (model::tally_filters[idx]->type() == filter_type)
972✔
399
      return true;
396✔
400
  }
401
  return false;
72✔
402
}
403

404
void Tally::set_filters(gsl::span<Filter*> filters)
28,955✔
405
{
406
  // Clear old data.
407
  filters_.clear();
28,955✔
408
  strides_.clear();
28,955✔
409

410
  // Copy in the given filter indices.
411
  auto n = filters.size();
28,955✔
412
  filters_.reserve(n);
28,955✔
413

414
  for (auto* filter : filters) {
81,816✔
415
    add_filter(filter);
52,861✔
416
  }
417
}
28,955✔
418

419
void Tally::add_filter(Filter* filter)
52,969✔
420
{
421
  int32_t filter_idx = model::filter_map.at(filter->id());
52,969✔
422
  // if this filter is already present, do nothing and return
423
  if (std::find(filters_.begin(), filters_.end(), filter_idx) != filters_.end())
52,969✔
424
    return;
24✔
425

426
  // Keep track of indices for special filters
427
  if (filter->type() == FilterType::ENERGY_OUT) {
52,945✔
428
    energyout_filter_ = filters_.size();
4,812✔
429
  } else if (filter->type() == FilterType::DELAYED_GROUP) {
48,133✔
430
    delayedgroup_filter_ = filters_.size();
850✔
431
  } else if (filter->type() == FilterType::CELL) {
47,283✔
432
    cell_filter_ = filters_.size();
1,062✔
433
  } else if (filter->type() == FilterType::ENERGY) {
46,221✔
434
    energy_filter_ = filters_.size();
17,828✔
435
  }
436
  filters_.push_back(filter_idx);
52,945✔
437
}
438

439
void Tally::set_strides()
27,209✔
440
{
441
  // Set the strides.  Filters are traversed in reverse so that the last filter
442
  // has the shortest stride in memory and the first filter has the longest
443
  // stride.
444
  auto n = filters_.size();
27,209✔
445
  strides_.resize(n, 0);
27,209✔
446
  int stride = 1;
27,209✔
447
  for (int i = n - 1; i >= 0; --i) {
80,407✔
448
    strides_[i] = stride;
53,198✔
449
    stride *= model::tally_filters[filters_[i]]->n_bins();
53,198✔
450
  }
451
  n_filter_bins_ = stride;
27,209✔
452
}
27,209✔
453

454
void Tally::set_scores(pugi::xml_node node)
24,835✔
455
{
456
  if (!check_for_node(node, "scores"))
24,835✔
457
    fatal_error(fmt::format("No scores specified on tally {}", id_));
×
458

459
  auto scores = get_node_array<std::string>(node, "scores");
24,835✔
460
  set_scores(scores);
24,835✔
461
}
24,835✔
462

463
void Tally::set_scores(const vector<std::string>& scores)
26,865✔
464
{
465
  // Reset state and prepare for the new scores.
466
  scores_.clear();
26,865✔
467
  scores_.reserve(scores.size());
26,865✔
468

469
  // Check for the presence of certain restrictive filters.
470
  bool energyout_present = energyout_filter_ != C_NONE;
26,865✔
471
  bool legendre_present = false;
26,865✔
472
  bool cell_present = false;
26,865✔
473
  bool cellfrom_present = false;
26,865✔
474
  bool surface_present = false;
26,865✔
475
  bool meshsurface_present = false;
26,865✔
476
  bool non_cell_energy_present = false;
26,865✔
477
  for (auto i_filt : filters_) {
79,082✔
478
    const auto* filt {model::tally_filters[i_filt].get()};
52,217✔
479
    // Checking for only cell and energy filters for pulse-height tally
480
    if (!(filt->type() == FilterType::CELL ||
103,420✔
481
          filt->type() == FilterType::ENERGY)) {
51,203✔
482
      non_cell_energy_present = true;
33,471✔
483
    }
484
    if (filt->type() == FilterType::LEGENDRE) {
52,217✔
485
      legendre_present = true;
2,274✔
486
    } else if (filt->type() == FilterType::CELLFROM) {
49,943✔
487
      cellfrom_present = true;
307✔
488
    } else if (filt->type() == FilterType::CELL) {
49,636✔
489
      cell_present = true;
1,014✔
490
    } else if (filt->type() == FilterType::SURFACE) {
48,622✔
491
      surface_present = true;
190✔
492
    } else if (filt->type() == FilterType::MESH_SURFACE) {
48,432✔
493
      meshsurface_present = true;
562✔
494
    }
495
  }
496

497
  // Iterate over the given scores.
498
  for (auto score_str : scores) {
64,008✔
499
    // Make sure a delayed group filter wasn't used with an incompatible score.
500
    if (delayedgroup_filter_ != C_NONE) {
37,143✔
501
      if (score_str != "delayed-nu-fission" && score_str != "decay-rate")
867✔
502
        fatal_error("Cannot tally " + score_str + "with a delayedgroup filter");
×
503
    }
504

505
    // Determine integer code for score
506
    int score = reaction_type(score_str);
37,143✔
507

508
    switch (score) {
37,143✔
509
    case SCORE_FLUX:
11,026✔
510
      if (!nuclides_.empty())
11,026✔
511
        if (!(nuclides_.size() == 1 && nuclides_[0] == -1))
11,026✔
512
          fatal_error("Cannot tally flux for an individual nuclide.");
×
513
      if (energyout_present)
11,026✔
514
        fatal_error("Cannot tally flux with an outgoing energy filter.");
×
515
      break;
11,026✔
516

517
    case SCORE_TOTAL:
7,059✔
518
    case SCORE_ABSORPTION:
519
    case SCORE_FISSION:
520
      if (energyout_present)
7,059✔
521
        fatal_error("Cannot tally " + score_str +
×
522
                    " reaction rate with an "
523
                    "outgoing energy filter");
524
      break;
7,059✔
525

526
    case SCORE_SCATTER:
4,084✔
527
      if (legendre_present)
4,084✔
528
        estimator_ = TallyEstimator::ANALOG;
1,560✔
529
    case SCORE_NU_FISSION:
530
    case SCORE_DELAYED_NU_FISSION:
531
    case SCORE_PROMPT_NU_FISSION:
532
      if (energyout_present)
9,061✔
533
        estimator_ = TallyEstimator::ANALOG;
3,607✔
534
      break;
9,061✔
535

536
    case SCORE_NU_SCATTER:
2,332✔
537
      if (settings::run_CE) {
2,332✔
538
        estimator_ = TallyEstimator::ANALOG;
2,196✔
539
      } else {
540
        if (energyout_present || legendre_present)
136✔
541
          estimator_ = TallyEstimator::ANALOG;
68✔
542
      }
543
      break;
2,332✔
544

545
    case SCORE_CURRENT:
786✔
546
      // Check which type of current is desired: mesh or surface currents.
547
      if (surface_present || cell_present || cellfrom_present) {
786✔
548
        if (meshsurface_present)
224✔
549
          fatal_error("Cannot tally mesh surface currents in the same tally as "
×
550
                      "normal surface currents");
551
        type_ = TallyType::SURFACE;
224✔
552
        estimator_ = TallyEstimator::ANALOG;
224✔
553
      } else if (meshsurface_present) {
562✔
554
        type_ = TallyType::MESH_SURFACE;
562✔
555
      } else {
556
        fatal_error("Cannot tally currents without surface type filters");
×
557
      }
558
      break;
786✔
559

560
    case HEATING:
318✔
561
      if (settings::photon_transport)
318✔
562
        estimator_ = TallyEstimator::COLLISION;
148✔
563
      break;
318✔
564

565
    case SCORE_PULSE_HEIGHT:
17✔
566
      if (non_cell_energy_present) {
17✔
567
        fatal_error("Pulse-height tallies are not compatible with filters "
×
568
                    "other than CellFilter and EnergyFilter");
569
      }
570
      type_ = TallyType::PULSE_HEIGHT;
17✔
571

572
      // Collecting indices of all cells covered by the filters in the pulse
573
      // height tally in global variable pulse_height_cells
574
      for (const auto& i_filt : filters_) {
51✔
575
        auto cell_filter =
576
          dynamic_cast<CellFilter*>(model::tally_filters[i_filt].get());
34✔
577
        if (cell_filter) {
34✔
578
          const auto& cells = cell_filter->cells();
17✔
579
          for (int i = 0; i < cell_filter->n_bins(); i++) {
34✔
580
            int cell_index = cells[i];
17✔
581
            if (!contains(model::pulse_height_cells, cell_index)) {
17✔
582
              model::pulse_height_cells.push_back(cell_index);
17✔
583
            }
584
          }
585
        }
586
      }
587
      break;
17✔
588

589
    case SCORE_IFP_TIME_NUM:
51✔
590
    case SCORE_IFP_BETA_NUM:
591
    case SCORE_IFP_DENOM:
592
      estimator_ = TallyEstimator::COLLISION;
51✔
593
      break;
51✔
594
    }
595

596
    scores_.push_back(score);
37,143✔
597
  }
37,143✔
598

599
  // Make sure that no duplicate scores exist.
600
  for (auto it1 = scores_.begin(); it1 != scores_.end(); ++it1) {
64,008✔
601
    for (auto it2 = it1 + 1; it2 != scores_.end(); ++it2) {
86,415✔
602
      if (*it1 == *it2)
49,272✔
603
        fatal_error(
×
604
          fmt::format("Duplicate score of type \"{}\" found in tally {}",
×
605
            reaction_name(*it1), id_));
×
606
    }
607
  }
608

609
  // Make sure all scores are compatible with multigroup mode.
610
  if (!settings::run_CE) {
26,865✔
611
    for (auto sc : scores_)
5,440✔
612
      if (sc > 0)
4,250✔
613
        fatal_error("Cannot tally " + reaction_name(sc) +
×
614
                    " reaction rate "
615
                    "in multi-group mode");
616
  }
617

618
  // Make sure current scores are not mixed in with volumetric scores.
619
  if (type_ == TallyType::SURFACE || type_ == TallyType::MESH_SURFACE) {
26,865✔
620
    if (scores_.size() != 1)
786✔
621
      fatal_error("Cannot tally other scores in the same tally as surface "
×
622
                  "currents.");
623
  }
624
  if ((surface_present || meshsurface_present) && scores_[0] != SCORE_CURRENT)
26,865✔
625
    fatal_error("Cannot tally score other than 'current' when using a surface "
×
626
                "or mesh-surface filter.");
627
}
26,865✔
628

629
void Tally::set_nuclides(pugi::xml_node node)
24,835✔
630
{
631
  nuclides_.clear();
24,835✔
632

633
  // By default, we tally just the total material rates.
634
  if (!check_for_node(node, "nuclides")) {
24,835✔
635
    nuclides_.push_back(-1);
5,801✔
636
    return;
5,801✔
637
  }
638

639
  // The user provided specifics nuclides.  Parse it as an array with either
640
  // "total" or a nuclide name like "U235" in each position.
641
  auto words = get_node_array<std::string>(node, "nuclides");
19,034✔
642
  this->set_nuclides(words);
19,034✔
643
}
19,034✔
644

645
void Tally::set_nuclides(const vector<std::string>& nuclides)
19,034✔
646
{
647
  nuclides_.clear();
19,034✔
648

649
  for (const auto& nuc : nuclides) {
49,636✔
650
    if (nuc == "total") {
30,602✔
651
      nuclides_.push_back(-1);
15,300✔
652
    } else {
653
      auto search = data::nuclide_map.find(nuc);
15,302✔
654
      if (search == data::nuclide_map.end()) {
15,302✔
655
        int err = openmc_load_nuclide(nuc.c_str(), nullptr, 0);
156✔
656
        if (err < 0)
156✔
657
          throw std::runtime_error {openmc_err_msg};
×
658
      }
659
      nuclides_.push_back(data::nuclide_map.at(nuc));
15,302✔
660
    }
661
  }
662
}
19,034✔
663

664
void Tally::init_triggers(pugi::xml_node node)
196✔
665
{
666
  for (auto trigger_node : node.children("trigger")) {
273✔
667
    // Read the trigger type.
668
    TriggerMetric metric;
669
    if (check_for_node(trigger_node, "type")) {
77✔
670
      auto type_str = get_node_value(trigger_node, "type");
77✔
671
      if (type_str == "std_dev") {
77✔
672
        metric = TriggerMetric::standard_deviation;
×
673
      } else if (type_str == "variance") {
77✔
674
        metric = TriggerMetric::variance;
×
675
      } else if (type_str == "rel_err") {
77✔
676
        metric = TriggerMetric::relative_error;
77✔
677
      } else {
678
        fatal_error(fmt::format(
×
679
          "Unknown trigger type \"{}\" in tally {}", type_str, id_));
×
680
      }
681
    } else {
77✔
682
      fatal_error(fmt::format(
×
683
        "Must specify trigger type for tally {} in tally XML file", id_));
×
684
    }
685

686
    // Read the trigger threshold.
687
    double threshold;
688
    if (check_for_node(trigger_node, "threshold")) {
77✔
689
      threshold = std::stod(get_node_value(trigger_node, "threshold"));
77✔
690
      if (threshold <= 0) {
77✔
691
        fatal_error("Tally trigger threshold must be positive");
×
692
      }
693
    } else {
694
      fatal_error(fmt::format(
×
695
        "Must specify trigger threshold for tally {} in tally XML file", id_));
×
696
    }
697

698
    // Read whether to allow zero-tally bins to be ignored.
699
    bool ignore_zeros = false;
77✔
700
    if (check_for_node(trigger_node, "ignore_zeros")) {
77✔
701
      ignore_zeros = get_node_value_bool(trigger_node, "ignore_zeros");
12✔
702
    }
703

704
    // Read the trigger scores.
705
    vector<std::string> trigger_scores;
77✔
706
    if (check_for_node(trigger_node, "scores")) {
77✔
707
      trigger_scores = get_node_array<std::string>(trigger_node, "scores");
77✔
708
    } else {
709
      trigger_scores.push_back("all");
×
710
    }
711

712
    // Parse the trigger scores and populate the triggers_ vector.
713
    for (auto score_str : trigger_scores) {
154✔
714
      if (score_str == "all") {
77✔
715
        triggers_.reserve(triggers_.size() + this->scores_.size());
17✔
716
        for (auto i_score = 0; i_score < this->scores_.size(); ++i_score) {
85✔
717
          triggers_.push_back({metric, threshold, ignore_zeros, i_score});
68✔
718
        }
719
      } else {
720
        int i_score = 0;
60✔
721
        for (; i_score < this->scores_.size(); ++i_score) {
84✔
722
          if (this->scores_[i_score] == reaction_type(score_str))
84✔
723
            break;
60✔
724
        }
725
        if (i_score == this->scores_.size()) {
60✔
726
          fatal_error(
×
727
            fmt::format("Could not find the score \"{}\" in tally "
×
728
                        "{} but it was listed in a trigger on that tally",
729
              score_str, id_));
×
730
        }
731
        triggers_.push_back({metric, threshold, ignore_zeros, i_score});
60✔
732
      }
733
    }
77✔
734
  }
77✔
735
}
196✔
736

737
void Tally::init_results()
27,209✔
738
{
739
  int n_scores = scores_.size() * nuclides_.size();
27,209✔
740
  results_ = xt::empty<double>({n_filter_bins_, n_scores, 3});
27,209✔
741
}
27,209✔
742

743
void Tally::reset()
61,583✔
744
{
745
  n_realizations_ = 0;
61,583✔
746
  if (results_.size() != 0) {
61,583✔
747
    xt::view(results_, xt::all()) = 0.0;
60,617✔
748
  }
749
}
61,583✔
750

751
void Tally::accumulate()
186,191✔
752
{
753
  // Increment number of realizations
754
  n_realizations_ += settings::reduce_tallies ? 1 : mpi::n_procs;
186,191✔
755

756
  if (mpi::master || !settings::reduce_tallies) {
186,191✔
757
    // Calculate total source strength for normalization
758
    double total_source = 0.0;
150,851✔
759
    if (settings::run_mode == RunMode::FIXED_SOURCE &&
150,851✔
760
        !settings::uniform_source_sampling) {
21,934✔
761
      for (const auto& s : model::external_sources) {
46,652✔
762
        total_source += s->strength();
24,742✔
763
      }
764
    } else {
21,910✔
765
      total_source = 1.0;
128,941✔
766
    }
767

768
    // Account for number of source particles in normalization
769
    double norm =
150,851✔
770
      total_source / (settings::n_particles * settings::gen_per_batch);
150,851✔
771

772
    if (settings::solver_type == SolverType::RANDOM_RAY) {
150,851✔
773
      norm = 1.0;
6,660✔
774
    }
775

776
// Accumulate each result
777
#pragma omp parallel for
77,658✔
778
    for (int i = 0; i < results_.shape()[0]; ++i) {
34,211,797✔
779
      for (int j = 0; j < results_.shape()[1]; ++j) {
80,828,582✔
780
        double val = results_(i, j, TallyResult::VALUE) * norm;
46,689,978✔
781
        results_(i, j, TallyResult::VALUE) = 0.0;
46,689,978✔
782
        results_(i, j, TallyResult::SUM) += val;
46,689,978✔
783
        results_(i, j, TallyResult::SUM_SQ) += val * val;
46,689,978✔
784
      }
785
    }
786
  }
787
}
186,191✔
788

789
int Tally::score_index(const std::string& score) const
156✔
790
{
791
  for (int i = 0; i < scores_.size(); i++) {
156✔
792
    if (this->score_name(i) == score)
156✔
793
      return i;
156✔
794
  }
UNCOV
795
  return -1;
×
796
}
797

UNCOV
798
xt::xarray<double> Tally::get_reshaped_data() const
×
799
{
800
  std::vector<uint64_t> shape;
×
801
  for (auto f : filters()) {
×
UNCOV
802
    shape.push_back(model::tally_filters[f]->n_bins());
×
803
  }
804

805
  // add number of scores and nuclides to tally
806
  shape.push_back(results_.shape()[1]);
×
UNCOV
807
  shape.push_back(results_.shape()[2]);
×
808

809
  xt::xarray<double> reshaped_results = results_;
×
810
  reshaped_results.reshape(shape);
×
UNCOV
811
  return reshaped_results;
×
812
}
813

814
std::string Tally::score_name(int score_idx) const
186✔
815
{
816
  if (score_idx < 0 || score_idx >= scores_.size()) {
186✔
UNCOV
817
    fatal_error("Index in scores array is out of bounds.");
×
818
  }
819
  return reaction_name(scores_[score_idx]);
186✔
820
}
821

UNCOV
822
std::vector<std::string> Tally::scores() const
×
823
{
824
  std::vector<std::string> score_names;
×
825
  for (int score : scores_)
×
826
    score_names.push_back(reaction_name(score));
×
827
  return score_names;
×
UNCOV
828
}
×
829

830
std::string Tally::nuclide_name(int nuclide_idx) const
30✔
831
{
832
  if (nuclide_idx < 0 || nuclide_idx >= nuclides_.size()) {
30✔
UNCOV
833
    fatal_error("Index in nuclides array is out of bounds");
×
834
  }
835

836
  int nuclide = nuclides_.at(nuclide_idx);
30✔
837
  if (nuclide == -1) {
30✔
838
    return "total";
30✔
839
  }
UNCOV
840
  return data::nuclides.at(nuclide)->name_;
×
841
}
842

843
//==============================================================================
844
// Non-member functions
845
//==============================================================================
846

847
void read_tallies_xml()
1,541✔
848
{
849
  // Check if tallies.xml exists. If not, just return since it is optional
850
  std::string filename = settings::path_input + "tallies.xml";
1,541✔
851
  if (!file_exists(filename))
1,541✔
852
    return;
1,019✔
853

854
  write_message("Reading tallies XML file...", 5);
522✔
855

856
  // Parse tallies.xml file
857
  pugi::xml_document doc;
522✔
858
  doc.load_file(filename.c_str());
522✔
859
  pugi::xml_node root = doc.document_element();
522✔
860

861
  read_tallies_xml(root);
522✔
862
}
1,541✔
863

864
void read_tallies_xml(pugi::xml_node root)
3,750✔
865
{
866
  // Check for <assume_separate> setting
867
  if (check_for_node(root, "assume_separate")) {
3,750✔
868
    settings::assume_separate = get_node_value_bool(root, "assume_separate");
17✔
869
  }
870

871
  // Check for user meshes and allocate
872
  read_meshes(root);
3,750✔
873

874
  // We only need the mesh info for plotting
875
  if (settings::run_mode == RunMode::PLOTTING)
3,750✔
876
    return;
36✔
877

878
  // Read data for tally derivatives
879
  read_tally_derivatives(root);
3,714✔
880

881
  // ==========================================================================
882
  // READ FILTER DATA
883

884
  // Check for user filters and allocate
885
  for (auto node_filt : root.children("filter")) {
11,018✔
886
    auto f = Filter::create(node_filt);
7,304✔
887
  }
888

889
  // ==========================================================================
890
  // READ TALLY DATA
891

892
  // Check for user tallies
893
  int n = 0;
3,714✔
894
  for (auto node : root.children("tally"))
28,549✔
895
    ++n;
24,835✔
896
  if (n == 0 && mpi::master) {
3,714✔
UNCOV
897
    warning("No tallies present in tallies.xml file.");
×
898
  }
899

900
  for (auto node_tal : root.children("tally")) {
28,549✔
901
    model::tallies.push_back(make_unique<Tally>(node_tal));
24,835✔
902
  }
903
}
904

905
#ifdef OPENMC_MPI
906
void reduce_tally_results()
32,480✔
907
{
908
  // Don't reduce tally is no_reduce option is on
909
  if (settings::reduce_tallies) {
32,480✔
910
    for (int i_tally : model::active_tallies) {
97,590✔
911
      // Skip any tallies that are not active
912
      auto& tally {model::tallies[i_tally]};
65,110✔
913

914
      // Get view of accumulated tally values
915
      auto values_view = xt::view(tally->results_, xt::all(), xt::all(),
65,110✔
916
        static_cast<int>(TallyResult::VALUE));
130,220✔
917

918
      // Make copy of tally values in contiguous array
919
      xt::xtensor<double, 2> values = values_view;
65,110✔
920
      xt::xtensor<double, 2> values_reduced = xt::empty_like(values);
65,110✔
921

922
      // Reduce contiguous set of tally results
923
      MPI_Reduce(values.data(), values_reduced.data(), values.size(),
65,110✔
924
        MPI_DOUBLE, MPI_SUM, 0, mpi::intracomm);
925

926
      // Transfer values on master and reset on other ranks
927
      if (mpi::master) {
65,110✔
928
        values_view = values_reduced;
32,545✔
929
      } else {
930
        values_view = 0.0;
32,565✔
931
      }
932
    }
65,110✔
933
  }
934

935
  // Note that global tallies are *always* reduced even when no_reduce option is
936
  // on.
937

938
  // Get view of global tally values
939
  auto& gt = simulation::global_tallies;
32,480✔
940
  auto gt_values_view =
941
    xt::view(gt, xt::all(), static_cast<int>(TallyResult::VALUE));
32,480✔
942

943
  // Make copy of values in contiguous array
944
  xt::xtensor<double, 1> gt_values = gt_values_view;
32,480✔
945
  xt::xtensor<double, 1> gt_values_reduced = xt::empty_like(gt_values);
32,480✔
946

947
  // Reduce contiguous data
948
  MPI_Reduce(gt_values.data(), gt_values_reduced.data(), N_GLOBAL_TALLIES,
32,480✔
949
    MPI_DOUBLE, MPI_SUM, 0, mpi::intracomm);
950

951
  // Transfer values on master and reset on other ranks
952
  if (mpi::master) {
32,480✔
953
    gt_values_view = gt_values_reduced;
16,235✔
954
  } else {
955
    gt_values_view = 0.0;
16,245✔
956
  }
957

958
  // We also need to determine the total starting weight of particles from the
959
  // last realization
960
  double weight_reduced;
961
  MPI_Reduce(&simulation::total_weight, &weight_reduced, 1, MPI_DOUBLE, MPI_SUM,
32,480✔
962
    0, mpi::intracomm);
963
  if (mpi::master)
32,480✔
964
    simulation::total_weight = weight_reduced;
16,235✔
965
}
32,480✔
966
#endif
967

968
void accumulate_tallies()
104,036✔
969
{
970
#ifdef OPENMC_MPI
971
  // Combine tally results onto master process
972
  if (mpi::n_procs > 1 && settings::solver_type == SolverType::MONTE_CARLO) {
57,697✔
973
    reduce_tally_results();
32,480✔
974
  }
975
#endif
976

977
  // Increase number of realizations (only used for global tallies)
978
  simulation::n_realizations += 1;
104,036✔
979

980
  // Accumulate on master only unless run is not reduced then do it on all
981
  if (mpi::master || !settings::reduce_tallies) {
104,036✔
982
    auto& gt = simulation::global_tallies;
84,441✔
983

984
    if (settings::run_mode == RunMode::EIGENVALUE) {
84,441✔
985
      if (simulation::current_batch > settings::n_inactive) {
63,905✔
986
        // Accumulate products of different estimators of k
987
        double k_col = gt(GlobalTally::K_COLLISION, TallyResult::VALUE) /
50,701✔
988
                       simulation::total_weight;
50,701✔
989
        double k_abs = gt(GlobalTally::K_ABSORPTION, TallyResult::VALUE) /
50,701✔
990
                       simulation::total_weight;
50,701✔
991
        double k_tra = gt(GlobalTally::K_TRACKLENGTH, TallyResult::VALUE) /
50,701✔
992
                       simulation::total_weight;
50,701✔
993
        simulation::k_col_abs += k_col * k_abs;
50,701✔
994
        simulation::k_col_tra += k_col * k_tra;
50,701✔
995
        simulation::k_abs_tra += k_abs * k_tra;
50,701✔
996
      }
997
    }
998

999
    // Accumulate results for global tallies
1000
    for (int i = 0; i < N_GLOBAL_TALLIES; ++i) {
422,205✔
1001
      double val = gt(i, TallyResult::VALUE) / simulation::total_weight;
337,764✔
1002
      gt(i, TallyResult::VALUE) = 0.0;
337,764✔
1003
      gt(i, TallyResult::SUM) += val;
337,764✔
1004
      gt(i, TallyResult::SUM_SQ) += val * val;
337,764✔
1005
    }
1006
  }
1007

1008
  // Accumulate results for each tally
1009
  for (int i_tally : model::active_tallies) {
290,227✔
1010
    auto& tally {model::tallies[i_tally]};
186,191✔
1011
    tally->accumulate();
186,191✔
1012
  }
1013
}
104,036✔
1014

1015
void setup_active_tallies()
104,050✔
1016
{
1017
  model::active_tallies.clear();
104,050✔
1018
  model::active_analog_tallies.clear();
104,050✔
1019
  model::active_tracklength_tallies.clear();
104,050✔
1020
  model::active_collision_tallies.clear();
104,050✔
1021
  model::active_meshsurf_tallies.clear();
104,050✔
1022
  model::active_surface_tallies.clear();
104,050✔
1023
  model::active_pulse_height_tallies.clear();
104,050✔
1024

1025
  for (auto i = 0; i < model::tallies.size(); ++i) {
398,474✔
1026
    const auto& tally {*model::tallies[i]};
294,424✔
1027

1028
    if (tally.active_) {
294,424✔
1029
      model::active_tallies.push_back(i);
186,191✔
1030
      switch (tally.type_) {
186,191✔
1031

1032
      case TallyType::VOLUME:
175,957✔
1033
        switch (tally.estimator_) {
175,957✔
1034
        case TallyEstimator::ANALOG:
65,251✔
1035
          model::active_analog_tallies.push_back(i);
65,251✔
1036
          break;
65,251✔
1037
        case TallyEstimator::TRACKLENGTH:
105,557✔
1038
          model::active_tracklength_tallies.push_back(i);
105,557✔
1039
          break;
105,557✔
1040
        case TallyEstimator::COLLISION:
5,149✔
1041
          model::active_collision_tallies.push_back(i);
5,149✔
1042
        }
1043
        break;
175,957✔
1044

1045
      case TallyType::MESH_SURFACE:
8,060✔
1046
        model::active_meshsurf_tallies.push_back(i);
8,060✔
1047
        break;
8,060✔
1048

1049
      case TallyType::SURFACE:
2,089✔
1050
        model::active_surface_tallies.push_back(i);
2,089✔
1051
        break;
2,089✔
1052

1053
      case TallyType::PULSE_HEIGHT:
85✔
1054
        model::active_pulse_height_tallies.push_back(i);
85✔
1055
        break;
85✔
1056
      }
1057
    }
1058
  }
1059
}
104,050✔
1060

1061
void free_memory_tally()
6,931✔
1062
{
1063
  model::tally_derivs.clear();
6,931✔
1064
  model::tally_deriv_map.clear();
6,931✔
1065

1066
  model::tally_filters.clear();
6,931✔
1067
  model::filter_map.clear();
6,931✔
1068

1069
  model::tallies.clear();
6,931✔
1070

1071
  model::active_tallies.clear();
6,931✔
1072
  model::active_analog_tallies.clear();
6,931✔
1073
  model::active_tracklength_tallies.clear();
6,931✔
1074
  model::active_collision_tallies.clear();
6,931✔
1075
  model::active_meshsurf_tallies.clear();
6,931✔
1076
  model::active_surface_tallies.clear();
6,931✔
1077
  model::active_pulse_height_tallies.clear();
6,931✔
1078

1079
  model::tally_map.clear();
6,931✔
1080
}
6,931✔
1081

1082
//==============================================================================
1083
// C-API functions
1084
//==============================================================================
1085

1086
extern "C" int openmc_extend_tallies(
1,976✔
1087
  int32_t n, int32_t* index_start, int32_t* index_end)
1088
{
1089
  if (index_start)
1,976✔
1090
    *index_start = model::tallies.size();
1,976✔
1091
  if (index_end)
1,976✔
UNCOV
1092
    *index_end = model::tallies.size() + n - 1;
×
1093
  for (int i = 0; i < n; ++i) {
3,952✔
1094
    model::tallies.push_back(make_unique<Tally>(-1));
1,976✔
1095
  }
1096
  return 0;
1,976✔
1097
}
1098

1099
extern "C" int openmc_get_tally_index(int32_t id, int32_t* index)
46,807✔
1100
{
1101
  auto it = model::tally_map.find(id);
46,807✔
1102
  if (it == model::tally_map.end()) {
46,807✔
1103
    set_errmsg(fmt::format("No tally exists with ID={}.", id));
24✔
1104
    return OPENMC_E_INVALID_ID;
24✔
1105
  }
1106

1107
  *index = it->second;
46,783✔
1108
  return 0;
46,783✔
1109
}
1110

UNCOV
1111
extern "C" void openmc_get_tally_next_id(int32_t* id)
×
1112
{
1113
  int32_t largest_tally_id = 0;
×
1114
  for (const auto& t : model::tallies) {
×
UNCOV
1115
    largest_tally_id = std::max(largest_tally_id, t->id_);
×
1116
  }
UNCOV
1117
  *id = largest_tally_id + 1;
×
1118
}
1119

UNCOV
1120
extern "C" int openmc_tally_get_estimator(int32_t index, int* estimator)
×
1121
{
1122
  if (index < 0 || index >= model::tallies.size()) {
×
1123
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1124
    return OPENMC_E_OUT_OF_BOUNDS;
×
1125
  }
1126

1127
  *estimator = static_cast<int>(model::tallies[index]->estimator_);
×
UNCOV
1128
  return 0;
×
1129
}
1130

1131
extern "C" int openmc_tally_set_estimator(int32_t index, const char* estimator)
1,500✔
1132
{
1133
  if (index < 0 || index >= model::tallies.size()) {
1,500✔
1134
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1135
    return OPENMC_E_OUT_OF_BOUNDS;
×
1136
  }
1137

1138
  auto& t {model::tallies[index]};
1,500✔
1139

1140
  std::string est = estimator;
1,500✔
1141
  if (est == "analog") {
1,500✔
1142
    t->estimator_ = TallyEstimator::ANALOG;
1,500✔
1143
  } else if (est == "collision") {
×
1144
    t->estimator_ = TallyEstimator::COLLISION;
×
1145
  } else if (est == "tracklength") {
×
UNCOV
1146
    t->estimator_ = TallyEstimator::TRACKLENGTH;
×
1147
  } else {
1148
    set_errmsg("Unknown tally estimator: " + est);
×
UNCOV
1149
    return OPENMC_E_INVALID_ARGUMENT;
×
1150
  }
1151
  return 0;
1,500✔
1152
}
1,500✔
1153

1154
extern "C" int openmc_tally_get_id(int32_t index, int32_t* id)
58,848✔
1155
{
1156
  if (index < 0 || index >= model::tallies.size()) {
58,848✔
1157
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1158
    return OPENMC_E_OUT_OF_BOUNDS;
×
1159
  }
1160

1161
  *id = model::tallies[index]->id_;
58,848✔
1162
  return 0;
58,848✔
1163
}
1164

1165
extern "C" int openmc_tally_set_id(int32_t index, int32_t id)
1,976✔
1166
{
1167
  if (index < 0 || index >= model::tallies.size()) {
1,976✔
1168
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1169
    return OPENMC_E_OUT_OF_BOUNDS;
×
1170
  }
1171

1172
  model::tallies[index]->set_id(id);
1,976✔
1173
  return 0;
1,976✔
1174
}
1175

1176
extern "C" int openmc_tally_get_type(int32_t index, int32_t* type)
12✔
1177
{
1178
  if (index < 0 || index >= model::tallies.size()) {
12✔
1179
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1180
    return OPENMC_E_OUT_OF_BOUNDS;
×
1181
  }
1182
  *type = static_cast<int>(model::tallies[index]->type_);
12✔
1183

1184
  return 0;
12✔
1185
}
1186

1187
extern "C" int openmc_tally_set_type(int32_t index, const char* type)
1,500✔
1188
{
1189
  if (index < 0 || index >= model::tallies.size()) {
1,500✔
1190
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1191
    return OPENMC_E_OUT_OF_BOUNDS;
×
1192
  }
1193
  if (strcmp(type, "volume") == 0) {
1,500✔
1194
    model::tallies[index]->type_ = TallyType::VOLUME;
1,125✔
1195
  } else if (strcmp(type, "mesh-surface") == 0) {
375✔
1196
    model::tallies[index]->type_ = TallyType::MESH_SURFACE;
375✔
1197
  } else if (strcmp(type, "surface") == 0) {
×
1198
    model::tallies[index]->type_ = TallyType::SURFACE;
×
1199
  } else if (strcmp(type, "pulse-height") == 0) {
×
UNCOV
1200
    model::tallies[index]->type_ = TallyType::PULSE_HEIGHT;
×
1201
  } else {
1202
    set_errmsg(fmt::format("Unknown tally type: {}", type));
×
UNCOV
1203
    return OPENMC_E_INVALID_ARGUMENT;
×
1204
  }
1205

1206
  return 0;
1,500✔
1207
}
1208

1209
extern "C" int openmc_tally_get_active(int32_t index, bool* active)
24✔
1210
{
1211
  if (index < 0 || index >= model::tallies.size()) {
24✔
1212
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1213
    return OPENMC_E_OUT_OF_BOUNDS;
×
1214
  }
1215
  *active = model::tallies[index]->active_;
24✔
1216

1217
  return 0;
24✔
1218
}
1219

1220
extern "C" int openmc_tally_set_active(int32_t index, bool active)
1,512✔
1221
{
1222
  if (index < 0 || index >= model::tallies.size()) {
1,512✔
1223
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1224
    return OPENMC_E_OUT_OF_BOUNDS;
×
1225
  }
1226
  model::tallies[index]->active_ = active;
1,512✔
1227

1228
  return 0;
1,512✔
1229
}
1230

1231
extern "C" int openmc_tally_get_writable(int32_t index, bool* writable)
24✔
1232
{
1233
  if (index < 0 || index >= model::tallies.size()) {
24✔
1234
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1235
    return OPENMC_E_OUT_OF_BOUNDS;
×
1236
  }
1237
  *writable = model::tallies[index]->writable();
24✔
1238

1239
  return 0;
24✔
1240
}
1241

1242
extern "C" int openmc_tally_set_writable(int32_t index, bool writable)
476✔
1243
{
1244
  if (index < 0 || index >= model::tallies.size()) {
476✔
1245
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1246
    return OPENMC_E_OUT_OF_BOUNDS;
×
1247
  }
1248
  model::tallies[index]->set_writable(writable);
476✔
1249

1250
  return 0;
476✔
1251
}
1252

1253
extern "C" int openmc_tally_get_multiply_density(int32_t index, bool* value)
24✔
1254
{
1255
  if (index < 0 || index >= model::tallies.size()) {
24✔
1256
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1257
    return OPENMC_E_OUT_OF_BOUNDS;
×
1258
  }
1259
  *value = model::tallies[index]->multiply_density();
24✔
1260

1261
  return 0;
24✔
1262
}
1263

1264
extern "C" int openmc_tally_set_multiply_density(int32_t index, bool value)
320✔
1265
{
1266
  if (index < 0 || index >= model::tallies.size()) {
320✔
1267
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1268
    return OPENMC_E_OUT_OF_BOUNDS;
×
1269
  }
1270
  model::tallies[index]->set_multiply_density(value);
320✔
1271

1272
  return 0;
320✔
1273
}
1274

1275
extern "C" int openmc_tally_get_scores(int32_t index, int** scores, int* n)
204✔
1276
{
1277
  if (index < 0 || index >= model::tallies.size()) {
204✔
1278
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1279
    return OPENMC_E_OUT_OF_BOUNDS;
×
1280
  }
1281

1282
  *scores = model::tallies[index]->scores_.data();
204✔
1283
  *n = model::tallies[index]->scores_.size();
204✔
1284
  return 0;
204✔
1285
}
1286

1287
extern "C" int openmc_tally_set_scores(
1,988✔
1288
  int32_t index, int n, const char** scores)
1289
{
1290
  if (index < 0 || index >= model::tallies.size()) {
1,988✔
1291
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1292
    return OPENMC_E_OUT_OF_BOUNDS;
×
1293
  }
1294

1295
  vector<std::string> scores_str(scores, scores + n);
1,988✔
1296
  try {
1297
    model::tallies[index]->set_scores(scores_str);
1,988✔
1298
  } catch (const std::invalid_argument& ex) {
×
1299
    set_errmsg(ex.what());
×
1300
    return OPENMC_E_INVALID_ARGUMENT;
×
UNCOV
1301
  }
×
1302

1303
  return 0;
1,988✔
1304
}
1,988✔
1305

1306
extern "C" int openmc_tally_get_nuclides(int32_t index, int** nuclides, int* n)
252✔
1307
{
1308
  // Make sure the index fits in the array bounds.
1309
  if (index < 0 || index >= model::tallies.size()) {
252✔
1310
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1311
    return OPENMC_E_OUT_OF_BOUNDS;
×
1312
  }
1313

1314
  *n = model::tallies[index]->nuclides_.size();
252✔
1315
  *nuclides = model::tallies[index]->nuclides_.data();
252✔
1316

1317
  return 0;
252✔
1318
}
1319

1320
extern "C" int openmc_tally_set_nuclides(
2,310✔
1321
  int32_t index, int n, const char** nuclides)
1322
{
1323
  // Make sure the index fits in the array bounds.
1324
  if (index < 0 || index >= model::tallies.size()) {
2,310✔
1325
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1326
    return OPENMC_E_OUT_OF_BOUNDS;
×
1327
  }
1328

1329
  vector<std::string> words(nuclides, nuclides + n);
2,310✔
1330
  vector<int> nucs;
2,310✔
1331
  for (auto word : words) {
9,060✔
1332
    if (word == "total") {
6,762✔
1333
      nucs.push_back(-1);
1,500✔
1334
    } else {
1335
      auto search = data::nuclide_map.find(word);
5,262✔
1336
      if (search == data::nuclide_map.end()) {
5,262✔
1337
        int err = openmc_load_nuclide(word.c_str(), nullptr, 0);
1,232✔
1338
        if (err < 0) {
1,232✔
1339
          set_errmsg(openmc_err_msg);
12✔
1340
          return OPENMC_E_DATA;
12✔
1341
        }
1342
      }
1343
      nucs.push_back(data::nuclide_map.at(word));
5,250✔
1344
    }
1345
  }
6,762✔
1346

1347
  model::tallies[index]->nuclides_ = nucs;
2,298✔
1348

1349
  return 0;
2,298✔
1350
}
2,310✔
1351

1352
extern "C" int openmc_tally_get_filters(
996✔
1353
  int32_t index, const int32_t** indices, size_t* n)
1354
{
1355
  if (index < 0 || index >= model::tallies.size()) {
996✔
1356
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1357
    return OPENMC_E_OUT_OF_BOUNDS;
×
1358
  }
1359

1360
  *indices = model::tallies[index]->filters().data();
996✔
1361
  *n = model::tallies[index]->filters().size();
996✔
1362
  return 0;
996✔
1363
}
1364

1365
extern "C" int openmc_tally_set_filters(
2,036✔
1366
  int32_t index, size_t n, const int32_t* indices)
1367
{
1368
  // Make sure the index fits in the array bounds.
1369
  if (index < 0 || index >= model::tallies.size()) {
2,036✔
1370
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1371
    return OPENMC_E_OUT_OF_BOUNDS;
×
1372
  }
1373

1374
  // Set the filters.
1375
  try {
1376
    // Convert indices to filter pointers
1377
    vector<Filter*> filters;
2,036✔
1378
    for (gsl::index i = 0; i < n; ++i) {
4,853✔
1379
      int32_t i_filt = indices[i];
2,817✔
1380
      filters.push_back(model::tally_filters.at(i_filt).get());
2,817✔
1381
    }
1382
    model::tallies[index]->set_filters(filters);
2,036✔
1383
  } catch (const std::out_of_range& ex) {
2,036✔
1384
    set_errmsg("Index in tally filter array out of bounds.");
×
1385
    return OPENMC_E_OUT_OF_BOUNDS;
×
UNCOV
1386
  }
×
1387

1388
  return 0;
2,036✔
1389
}
1390

1391
//! Reset tally results and number of realizations
1392
extern "C" int openmc_tally_reset(int32_t index)
6,100✔
1393
{
1394
  // Make sure the index fits in the array bounds.
1395
  if (index < 0 || index >= model::tallies.size()) {
6,100✔
1396
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1397
    return OPENMC_E_OUT_OF_BOUNDS;
×
1398
  }
1399

1400
  model::tallies[index]->reset();
6,100✔
1401
  return 0;
6,100✔
1402
}
1403

1404
extern "C" int openmc_tally_get_n_realizations(int32_t index, int32_t* n)
6,717✔
1405
{
1406
  // Make sure the index fits in the array bounds.
1407
  if (index < 0 || index >= model::tallies.size()) {
6,717✔
1408
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1409
    return OPENMC_E_OUT_OF_BOUNDS;
×
1410
  }
1411

1412
  *n = model::tallies[index]->n_realizations_;
6,717✔
1413
  return 0;
6,717✔
1414
}
1415

1416
//! \brief Returns a pointer to a tally results array along with its shape. This
1417
//! allows a user to obtain in-memory tally results from Python directly.
1418
extern "C" int openmc_tally_results(
35,169✔
1419
  int32_t index, double** results, size_t* shape)
1420
{
1421
  // Make sure the index fits in the array bounds.
1422
  if (index < 0 || index >= model::tallies.size()) {
35,169✔
1423
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1424
    return OPENMC_E_OUT_OF_BOUNDS;
×
1425
  }
1426

1427
  const auto& t {model::tallies[index]};
35,169✔
1428
  if (t->results_.size() == 0) {
35,169✔
1429
    set_errmsg("Tally results have not been allocated yet.");
×
UNCOV
1430
    return OPENMC_E_ALLOCATE;
×
1431
  }
1432

1433
  // Set pointer to results and copy shape
1434
  *results = t->results_.data();
35,169✔
1435
  auto s = t->results_.shape();
35,169✔
1436
  shape[0] = s[0];
35,169✔
1437
  shape[1] = s[1];
35,169✔
1438
  shape[2] = s[2];
35,169✔
1439
  return 0;
35,169✔
1440
}
1441

1442
extern "C" int openmc_global_tallies(double** ptr)
12✔
1443
{
1444
  *ptr = simulation::global_tallies.data();
12✔
1445
  return 0;
12✔
1446
}
1447

1448
extern "C" size_t tallies_size()
2,024✔
1449
{
1450
  return model::tallies.size();
2,024✔
1451
}
1452

1453
// given a tally ID, remove it from the tallies vector
1454
extern "C" int openmc_remove_tally(int32_t index)
12✔
1455
{
1456
  // check that id is in the map
1457
  if (index < 0 || index >= model::tallies.size()) {
12✔
1458
    set_errmsg("Index in tallies array is out of bounds.");
×
UNCOV
1459
    return OPENMC_E_OUT_OF_BOUNDS;
×
1460
  }
1461

1462
  // delete the tally via iterator pointing to correct position
1463
  // this calls the Tally destructor, removing the tally from the map as well
1464
  model::tallies.erase(model::tallies.begin() + index);
12✔
1465

1466
  return 0;
12✔
1467
}
1468

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

© 2025 Coveralls, Inc