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

openmc-dev / openmc / 32734756650

24 Aug 2026 01:47PM UTC coverage: 81.299% (-0.1%) from 81.425%
32734756650

Pull #3675

github

web-flow
Merge d355de710 into 7ecd3a961
Pull Request #3675: Extend level scattering to support incident photons

18557 of 27030 branches covered (68.65%)

Branch coverage included in aggregate %.

55 of 77 new or added lines in 4 files covered. (71.43%)

738 existing lines in 26 files now uncovered.

60342 of 70018 relevant lines covered (86.18%)

49966364.8 hits per line

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

82.39
/src/output.cpp
1
#include "openmc/output.h"
2

3
#include <algorithm> // for transform, max
4
#include <cstdio>    // for stdout
5
#include <cstring>   // for strlen
6
#include <ctime>     // for time, localtime
7
#include <fstream>
8
#include <iomanip> // for setw, setprecision, put_time
9
#include <ios>     // for fixed, scientific, left
10
#include <iostream>
11
#include <sstream>
12
#include <unordered_map>
13
#include <utility> // for pair
14

15
#include <fmt/core.h>
16
#include <fmt/ostream.h>
17
#ifdef _OPENMP
18
#include <omp.h>
19
#endif
20
#include "openmc/tensor.h"
21

22
#include "openmc/capi.h"
23
#include "openmc/cell.h"
24
#include "openmc/constants.h"
25
#include "openmc/eigenvalue.h"
26
#include "openmc/error.h"
27
#include "openmc/geometry.h"
28
#include "openmc/lattice.h"
29
#include "openmc/math_functions.h"
30
#include "openmc/message_passing.h"
31
#include "openmc/mgxs_interface.h"
32
#include "openmc/nuclide.h"
33
#include "openmc/plot.h"
34
#include "openmc/random_ray/flat_source_domain.h"
35
#include "openmc/reaction.h"
36
#include "openmc/settings.h"
37
#include "openmc/simulation.h"
38
#include "openmc/surface.h"
39
#include "openmc/tallies/derivative.h"
40
#include "openmc/tallies/filter.h"
41
#include "openmc/tallies/tally.h"
42
#include "openmc/tallies/tally_scoring.h"
43
#include "openmc/timer.h"
44

45
namespace openmc {
46

47
extern "C" int openmc_get_feature_enabled(const char* feature, bool* enabled)
593✔
48
{
49
  if (!feature || !enabled) {
593!
UNCOV
50
    set_errmsg("Feature name and output pointer must not be null.");
×
UNCOV
51
    return OPENMC_E_INVALID_ARGUMENT;
×
52
  }
53

54
  if (strcmp(feature, "dagmc") == 0) {
593✔
55
#ifdef OPENMC_DAGMC_ENABLED
56
    *enabled = true;
31✔
57
#else
58
    *enabled = false;
306✔
59
#endif
60
  } else if (strcmp(feature, "libmesh") == 0) {
256✔
61
#ifdef OPENMC_LIBMESH_ENABLED
62
    *enabled = true;
36✔
63
#else
64
    *enabled = false;
154✔
65
#endif
66
  } else if (strcmp(feature, "strict_fp") == 0) {
66✔
67
#ifdef OPENMC_ENABLE_STRICT_FP
68
    *enabled = true;
22✔
69
#else
70
    *enabled = false;
71
#endif
72
  } else if (strcmp(feature, "uwuw") == 0) {
44✔
73
#ifdef OPENMC_UWUW_ENABLED
74
    *enabled = true;
3✔
75
#else
76
    *enabled = false;
30✔
77
#endif
78
  } else {
79
    set_errmsg(fmt::format("Unknown build feature '{}'.", feature));
11✔
80
    return OPENMC_E_INVALID_ARGUMENT;
11✔
81
  }
82

83
  return 0;
84
}
85

86
//==============================================================================
87

88
void title()
7,893✔
89
{
90
  fmt::print("                                %%%%%%%%%%%%%%%\n"
7,893✔
91
             "                           %%%%%%%%%%%%%%%%%%%%%%%%\n"
92
             "                        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"
93
             "                      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"
94
             "                    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"
95
             "                   %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"
96
             "                                    %%%%%%%%%%%%%%%%%%%%%%%%\n"
97
             "                                     %%%%%%%%%%%%%%%%%%%%%%%%\n"
98
             "                 ###############      %%%%%%%%%%%%%%%%%%%%%%%%\n"
99
             "                ##################     %%%%%%%%%%%%%%%%%%%%%%%\n"
100
             "                ###################     %%%%%%%%%%%%%%%%%%%%%%%\n"
101
             "                ####################     %%%%%%%%%%%%%%%%%%%%%%\n"
102
             "                #####################     %%%%%%%%%%%%%%%%%%%%%\n"
103
             "                ######################     %%%%%%%%%%%%%%%%%%%%\n"
104
             "                #######################     %%%%%%%%%%%%%%%%%%\n"
105
             "                 #######################     %%%%%%%%%%%%%%%%%\n"
106
             "                 ######################     %%%%%%%%%%%%%%%%%\n"
107
             "                  ####################     %%%%%%%%%%%%%%%%%\n"
108
             "                    #################     %%%%%%%%%%%%%%%%%\n"
109
             "                     ###############     %%%%%%%%%%%%%%%%\n"
110
             "                       ############     %%%%%%%%%%%%%%%\n"
111
             "                          ########     %%%%%%%%%%%%%%\n"
112
             "                                      %%%%%%%%%%%\n\n");
113

114
  // Write version information
115
  fmt::print(
15,786✔
116
    "                 | The OpenMC Monte Carlo Code\n"
117
    "       Copyright | 2011-2026 MIT, UChicago Argonne LLC, and contributors\n"
118
    "         License | https://docs.openmc.org/en/latest/license.html\n"
119
    "         Version | {}.{}.{}{}{}\n",
120
    VERSION_MAJOR, VERSION_MINOR, VERSION_RELEASE, VERSION_DEV ? "-dev" : "",
7,893✔
121
    VERSION_COMMIT_COUNT);
122
  fmt::print("     Commit Hash | {}\n", VERSION_COMMIT_HASH);
7,893✔
123

124
  // Write the date and time
125
  fmt::print("       Date/Time | {}\n", time_stamp());
9,314✔
126

127
#ifdef OPENMC_MPI
128
  // Write number of processors
129
  fmt::print("   MPI Processes | {}\n", mpi::n_procs);
2,907✔
130
#endif
131

132
#ifdef _OPENMP
133
  // Write number of OpenMP threads
134
  fmt::print("  OpenMP Threads | {}\n", omp_get_max_threads());
4,315✔
135
#endif
136
  fmt::print("\n");
7,893✔
137
  std::fflush(stdout);
7,893✔
138
}
7,893✔
139

140
//==============================================================================
141

142
std::string header(const char* msg)
41,707✔
143
{
144
  // Determine how many times to repeat the '=' character.
145
  int n_prefix = (63 - strlen(msg)) / 2;
41,707✔
146
  int n_suffix = n_prefix;
41,707✔
147
  if ((strlen(msg) % 2) == 0)
41,707✔
148
    ++n_suffix;
8,317✔
149

150
  // Convert to uppercase.
151
  std::string upper(msg);
41,707✔
152
  std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper);
41,707✔
153

154
  // Add ===>  <=== markers.
155
  std::stringstream out;
41,707✔
156
  out << ' ';
41,707✔
157
  for (int i = 0; i < n_prefix; i++)
1,055,665✔
158
    out << '=';
1,013,958✔
159
  out << ">     " << upper << "     <";
41,707✔
160
  for (int i = 0; i < n_suffix; i++)
1,063,982✔
161
    out << '=';
1,022,275✔
162

163
  return out.str();
41,707✔
164
}
41,707✔
165

166
std::string header(const std::string& msg)
20,413✔
167
{
168
  return header(msg.c_str());
20,413✔
169
}
170

171
void header(const char* msg, int level)
21,294✔
172
{
173
  auto out = header(msg);
21,294✔
174

175
  // Print header based on verbosity level.
176
  if (settings::verbosity >= level) {
21,294✔
177
    fmt::print("\n{}\n\n", out);
20,859✔
178
    std::fflush(stdout);
20,859✔
179
  }
180
}
21,294✔
181

182
//==============================================================================
183

184
std::string time_stamp()
23,736✔
185
{
186
  std::stringstream ts;
23,736✔
187
  std::time_t t = std::time(nullptr); // get time now
23,736✔
188
  ts << std::put_time(std::localtime(&t), "%Y-%m-%d %H:%M:%S");
23,736✔
189
  return ts.str();
47,472✔
190
}
23,736✔
191

192
//==============================================================================
193

194
void print_particle(Particle& p)
44✔
195
{
196
  // Display particle type and ID.
197
  switch (p.type().pdg_number()) {
44!
198
  case PDG_NEUTRON:
44✔
199
    fmt::print("Neutron ");
44✔
200
    break;
44✔
201
  case PDG_PHOTON:
×
UNCOV
202
    fmt::print("Photon ");
×
UNCOV
203
    break;
×
UNCOV
204
  case PDG_ELECTRON:
×
UNCOV
205
    fmt::print("Electron ");
×
UNCOV
206
    break;
×
UNCOV
207
  case PDG_POSITRON:
×
UNCOV
208
    fmt::print("Positron ");
×
UNCOV
209
    break;
×
UNCOV
210
  default:
×
211
    fmt::print("Particle {} ", p.type().str());
×
212
  }
213
  fmt::print("{}\n", p.id());
44✔
214

215
  // Display particle geometry hierarchy.
216
  for (auto i = 0; i < p.n_coord(); i++) {
88✔
217
    fmt::print("  Level {}\n", i);
44✔
218

219
    if (p.coord(i).cell() != C_NONE) {
44!
220
      const Cell& c {*model::cells[p.coord(i).cell()]};
44✔
221
      fmt::print("    Cell             = {}\n", c.id_);
44✔
222
    }
223

224
    if (p.coord(i).universe() != C_NONE) {
44!
225
      const Universe& u {*model::universes[p.coord(i).universe()]};
44✔
226
      fmt::print("    Universe         = {}\n", u.id_);
44✔
227
    }
228

229
    if (p.coord(i).lattice() != C_NONE) {
44!
UNCOV
230
      const Lattice& lat {*model::lattices[p.coord(i).lattice()]};
×
UNCOV
231
      fmt::print("    Lattice          = {}\n", lat.id_);
×
UNCOV
232
      fmt::print("    Lattice position = ({},{},{})\n",
×
UNCOV
233
        p.coord(i).lattice_index()[0], p.coord(i).lattice_index()[1],
×
UNCOV
234
        p.coord(i).lattice_index()[2]);
×
235
    }
236

237
    fmt::print("    r = {}\n", p.coord(i).r());
44✔
238
    fmt::print("    u = {}\n", p.coord(i).u());
44✔
239
  }
240

241
  // Display miscellaneous info.
242
  if (p.surface() != SURFACE_NONE) {
44!
243
    // Surfaces identifiers are >= 1, but indices are >= 0 so we need -1
UNCOV
244
    const Surface& surf {*model::surfaces[p.surface_index()]};
×
UNCOV
245
    fmt::print("  Surface = {}\n", (p.surface() > 0) ? surf.id_ : -surf.id_);
×
246
  }
247
  fmt::print("  Weight = {}\n", p.wgt());
44✔
248
  if (settings::run_CE) {
44!
249
    fmt::print("  Energy = {}\n", p.E());
44✔
250
  } else {
251
    fmt::print("  Energy Group = {}\n", p.g());
×
252
  }
253
  fmt::print("  Delayed Group = {}\n\n", p.delayed_group());
44✔
254
}
44✔
255

256
//==============================================================================
257

258
void print_plot()
88✔
259
{
260
  header("PLOTTING SUMMARY", 5);
88✔
261
  if (settings::verbosity < 5)
88!
262
    return;
263

264
  for (const auto& pl : model::plots) {
330✔
265
    fmt::print("Plot ID: {}\n", pl->id());
242✔
266
    fmt::print("Plot file: {}\n", pl->path_plot());
242✔
267
    fmt::print("Universe depth: {}\n", pl->level());
242✔
268
    pl->print_info(); // prints type-specific plot info
242✔
269
    fmt::print("\n");
242✔
270
  }
271
}
272

273
//==============================================================================
274

UNCOV
275
void print_overlap_check()
×
276
{
277
#ifdef OPENMC_MPI
278
  vector<int64_t> temp(model::overlap_check_count);
279
  mpi::reduce(temp.data(), model::overlap_check_count.data(),
×
280
    model::overlap_check_count.size(), MPI_SUM, 0, mpi::intracomm);
281
#endif
282

UNCOV
283
  if (mpi::master) {
×
UNCOV
284
    header("cell overlap check summary", 1);
×
UNCOV
285
    fmt::print(" Cell ID      No. Overlap Checks\n");
×
286

UNCOV
287
    vector<int32_t> sparse_cell_ids;
×
UNCOV
288
    for (int i = 0; i < model::cells.size(); i++) {
×
UNCOV
289
      fmt::print(
×
UNCOV
290
        " {:8} {:17}\n", model::cells[i]->id_, model::overlap_check_count[i]);
×
UNCOV
291
      if (model::overlap_check_count[i] < 10) {
×
UNCOV
292
        sparse_cell_ids.push_back(model::cells[i]->id_);
×
293
      }
294
    }
295

UNCOV
296
    fmt::print("\n There were {} cells with less than 10 overlap checks\n",
×
UNCOV
297
      sparse_cell_ids.size());
×
UNCOV
298
    for (auto id : sparse_cell_ids) {
×
UNCOV
299
      fmt::print(" {}", id);
×
300
    }
UNCOV
301
    fmt::print("\n");
×
UNCOV
302
  }
×
UNCOV
303
}
×
304

305
//==============================================================================
306

UNCOV
307
void print_usage()
×
308
{
UNCOV
309
  if (mpi::master) {
×
UNCOV
310
    fmt::print(
×
311
      "Usage: openmc [options] [path]\n\n"
312
      "Options:\n"
313
      "  -c, --volume           Run in stochastic volume calculation mode\n"
314
      "  -g, --geometry-debug   Run with geometry debugging on\n"
315
      "  -n, --particles        Number of particles per generation\n"
316
      "  -p, --plot             Run in plotting mode\n"
317
      "  -r, --restart          Restart a previous run from a state point\n"
318
      "                         or a particle restart file\n"
319
      "  -s, --threads          Number of OpenMP threads\n"
320
      "  -t, --track            Write tracks for all particles (up to "
321
      "max_tracks)\n"
322
      "  -e, --event            Run using event-based parallelism\n"
323
      "  -q, --verbosity        Output verbosity\n"
324
      "  -v, --version          Show version information\n"
325
      "  -h, --help             Show this message\n");
326
  }
UNCOV
327
}
×
328

329
//==============================================================================
330

331
void print_version()
11✔
332
{
333
  if (mpi::master) {
11!
334
    fmt::print("OpenMC version {}.{}.{}{}{}\n", VERSION_MAJOR, VERSION_MINOR,
22✔
335
      VERSION_RELEASE, VERSION_DEV ? "-dev" : "", VERSION_COMMIT_COUNT);
11✔
336
    fmt::print("Commit hash: {}\n", VERSION_COMMIT_HASH);
11✔
337
    fmt::print("Copyright (c) 2011-2026 MIT, UChicago Argonne LLC, and "
11✔
338
               "contributors\nMIT/X license at "
339
               "<https://docs.openmc.org/en/latest/license.html>\n");
340
  }
341
}
11✔
342

343
//==============================================================================
344

345
void print_build_info()
11✔
346
{
347
  const std::string n("no");
11✔
348
  const std::string y("yes");
11✔
349

350
  std::string mpi(n);
11✔
351
  std::string phdf5(n);
11✔
352
  std::string dagmc(n);
11✔
353
  std::string libmesh(n);
11✔
354
  std::string png(n);
11✔
355
  std::string profiling(n);
11✔
356
  std::string coverage(n);
11✔
357
  std::string uwuw(n);
11✔
358
  std::string strict_fp(n);
11✔
359

360
#ifdef PHDF5
361
  phdf5 = y;
4✔
362
#endif
363
#ifdef OPENMC_MPI
364
  mpi = y;
4✔
365
#endif
366
#ifdef OPENMC_DAGMC_ENABLED
367
  dagmc = y;
1✔
368
#endif
369
#ifdef OPENMC_LIBMESH_ENABLED
370
  libmesh = y;
2✔
371
#endif
372
#ifdef USE_LIBPNG
373
  png = y;
11✔
374
#endif
375
#ifdef PROFILINGBUILD
376
  profiling = y;
377
#endif
378
#ifdef COVERAGEBUILD
379
  coverage = y;
11✔
380
#endif
381
#ifdef OPENMC_UWUW_ENABLED
382
  uwuw = y;
1✔
383
#endif
384
#ifdef OPENMC_ENABLE_STRICT_FP
385
  strict_fp = y;
11✔
386
#endif
387

388
  // Wraps macro variables in quotes
389
#define STRINGIFY(x) STRINGIFY2(x)
390
#define STRINGIFY2(x) #x
391

392
  if (mpi::master) {
11!
393
    fmt::print("Build type:            {}\n", STRINGIFY(BUILD_TYPE));
11✔
394
    fmt::print("Compiler ID:           {} {}\n", STRINGIFY(COMPILER_ID),
11✔
395
      STRINGIFY(COMPILER_VERSION));
396
    fmt::print("MPI enabled:           {}\n", mpi);
11✔
397
    fmt::print("Parallel HDF5 enabled: {}\n", phdf5);
11✔
398
    fmt::print("PNG support:           {}\n", png);
11✔
399
    fmt::print("DAGMC support:         {}\n", dagmc);
11✔
400
    fmt::print("libMesh support:       {}\n", libmesh);
11✔
401
    fmt::print("Coverage testing:      {}\n", coverage);
11✔
402
    fmt::print("Profiling flags:       {}\n", profiling);
11✔
403
    fmt::print("UWUW support:          {}\n", uwuw);
11✔
404
    fmt::print("Strict FP:             {}\n", strict_fp);
13✔
405
  }
406
}
11✔
407

408
//==============================================================================
409

410
void print_columns()
3,429✔
411
{
412
  if (settings::entropy_on) {
3,429✔
413
    fmt::print("  Bat./Gen.      k       Entropy         Average k \n"
495✔
414
               "  =========   ========   ========   ====================\n");
415
  } else {
416
    fmt::print("  Bat./Gen.      k            Average k\n"
2,934✔
417
               "  =========   ========   ====================\n");
418
  }
419
}
3,429✔
420

421
//==============================================================================
422

423
void print_generation()
76,308✔
424
{
425
  // Determine overall generation index and number of active generations
426
  int idx = overall_generation() - 1;
76,308✔
427
  int n = simulation::current_batch > settings::n_inactive
152,616✔
428
            ? settings::gen_per_batch * simulation::n_realizations +
76,308✔
429
                simulation::current_gen
430
            : 0;
431

432
  // write out batch/generation and generation k-effective
433
  auto batch_and_gen = std::to_string(simulation::current_batch) + "/" +
152,616✔
434
                       std::to_string(simulation::current_gen);
152,616✔
435
  fmt::print("  {:>9}   {:8.5f}", batch_and_gen, simulation::k_generation[idx]);
76,308✔
436

437
  // write out entropy info
438
  if (settings::entropy_on) {
76,308✔
439
    fmt::print("   {:8.5f}", simulation::entropy[idx]);
13,255✔
440
  }
441

442
  if (n > 1) {
76,308✔
443
    fmt::print("   {:8.5f} +/-{:8.5f}", simulation::keff, simulation::keff_std);
53,831✔
444
  }
445
  fmt::print("\n");
76,308✔
446
  std::fflush(stdout);
76,308✔
447
}
76,308✔
448

449
//==============================================================================
450

451
void show_time(const char* label, double secs, int indent_level)
75,558✔
452
{
453
  int width = 33 - indent_level * 2;
75,558✔
454
  fmt::print("{0:{1}} {2:<{3}} = {4:>10.4e} seconds\n", "", 2 * indent_level,
75,558✔
455
    label, width, secs);
456
}
75,558✔
457

458
void show_rate(const char* label, double particles_per_sec)
8,174✔
459
{
460
  fmt::print(" {:<33} = {:.6} particles/second\n", label, particles_per_sec);
8,174✔
461
}
8,174✔
462

463
void print_runtime()
5,927✔
464
{
465
  using namespace simulation;
5,927✔
466

467
  // display header block
468
  header("Timing Statistics", 6);
5,927✔
469
  if (settings::verbosity < 6)
5,927!
470
    return;
471

472
  // display time elapsed for various sections
473
  show_time("Total time for initialization", time_initialize.elapsed());
5,927✔
474
  show_time("Reading cross sections", time_read_xs.elapsed(), 1);
5,927✔
475
  show_time("Total time in simulation",
5,927✔
476
    time_inactive.elapsed() + time_active.elapsed());
5,927✔
477
  show_time("Time in transport only", time_transport.elapsed(), 1);
5,927✔
478
  if (settings::event_based) {
5,927✔
479
    show_time("Particle initialization", time_event_init.elapsed(), 2);
164✔
480
    show_time("XS lookups", time_event_calculate_xs.elapsed(), 2);
164✔
481
    show_time("Advancing", time_event_advance_particle.elapsed(), 2);
164✔
482
    show_time("Surface crossings", time_event_surface_crossing.elapsed(), 2);
164✔
483
    show_time("Collisions", time_event_collision.elapsed(), 2);
164✔
484
    show_time("Particle death", time_event_death.elapsed(), 2);
164✔
485
  }
486
  if (settings::run_mode == RunMode::EIGENVALUE) {
5,927✔
487
    show_time("Time in inactive batches", time_inactive.elapsed(), 1);
3,154✔
488
  }
489
  show_time("Time in active batches", time_active.elapsed(), 1);
5,927✔
490
  if (settings::run_mode == RunMode::EIGENVALUE) {
5,927✔
491
    show_time("Time synchronizing fission bank", time_bank.elapsed(), 1);
3,154✔
492
    show_time("Sampling source sites", time_bank_sample.elapsed(), 2);
3,154✔
493
    show_time("SEND/RECV source sites", time_bank_sendrecv.elapsed(), 2);
3,154✔
494
  }
495
  show_time("Time accumulating tallies", time_tallies.elapsed(), 1);
5,927✔
496
  show_time("Time writing statepoints", time_statepoint.elapsed(), 1);
5,927✔
497
  show_time("Total time for finalization", time_finalize.elapsed());
5,927✔
498
  show_time("Total time elapsed", time_total.elapsed());
5,927✔
499

500
  // Calculate particle rate in active/inactive batches
501
  int n_active = simulation::current_batch - settings::n_inactive;
5,927✔
502
  double speed_inactive = 0.0;
5,927✔
503
  double speed_active;
5,927✔
504
  if (settings::restart_run) {
5,927✔
505
    if (simulation::restart_batch < settings::n_inactive) {
33!
UNCOV
506
      speed_inactive = (settings::n_particles *
×
UNCOV
507
                         (settings::n_inactive - simulation::restart_batch) *
×
UNCOV
508
                         settings::gen_per_batch) /
×
UNCOV
509
                       time_inactive.elapsed();
×
UNCOV
510
      speed_active =
×
UNCOV
511
        (settings::n_particles * n_active * settings::gen_per_batch) /
×
UNCOV
512
        time_active.elapsed();
×
513
    } else {
514
      speed_active = (settings::n_particles *
33✔
515
                       (settings::n_batches - simulation::restart_batch) *
33✔
516
                       settings::gen_per_batch) /
33✔
517
                     time_active.elapsed();
33✔
518
    }
519
  } else {
520
    if (settings::n_inactive > 0) {
5,894✔
521
      speed_inactive = (settings::n_particles * settings::n_inactive *
2,247✔
522
                         settings::gen_per_batch) /
2,247✔
523
                       time_inactive.elapsed();
2,247✔
524
    }
525
    speed_active =
5,894✔
526
      (settings::n_particles * n_active * settings::gen_per_batch) /
5,894✔
527
      time_active.elapsed();
5,894✔
528
  }
529

530
  // display calculation rate
531
  if (!(settings::restart_run &&
5,927✔
532
        (simulation::restart_batch >= settings::n_inactive)) &&
33!
533
      settings::n_inactive > 0) {
5,894✔
534
    show_rate("Calculation Rate (inactive)", speed_inactive);
2,247✔
535
  }
536
  show_rate("Calculation Rate (active)", speed_active);
5,927✔
537

538
  // Display track rate when weight windows are enabled
539
  if (settings::weight_windows_on) {
5,927✔
540
    double speed_tracks =
335✔
541
      simulation::simulation_tracks_completed / time_active.elapsed();
335✔
542
    fmt::print(
335✔
543
      " {:<33} = {:.6} tracks/second\n", "Track Rate (active)", speed_tracks);
544
  }
545
}
546

547
//==============================================================================
548

549
std::pair<double, double> mean_stdev(const double* x, int n)
14,456,282✔
550
{
551
  double mean = x[static_cast<int>(TallyResult::SUM)] / n;
14,456,282✔
552
  double stdev =
14,456,282✔
553
    n > 1 ? std::sqrt(std::max(0.0,
28,902,664✔
554
              (x[static_cast<int>(TallyResult::SUM_SQ)] / n - mean * mean) /
28,902,664✔
555
                (n - 1)))
14,446,382✔
556
          : 0.0;
14,456,282✔
557
  return {mean, stdev};
14,456,282✔
558
}
559

560
//==============================================================================
561

562
void print_results()
5,927✔
563
{
564
  // display header block for results
565
  header("Results", 4);
5,927✔
566
  if (settings::verbosity < 4)
5,927!
UNCOV
567
    return;
×
568

569
  // Calculate t-value for confidence intervals
570
  int n = simulation::n_realizations;
5,927✔
571
  double alpha, t_n1, t_n3;
5,927✔
572
  if (settings::confidence_intervals) {
5,927✔
573
    alpha = 1.0 - CONFIDENCE_LEVEL;
11✔
574
    t_n1 = t_percentile(1.0 - alpha / 2.0, n - 1);
11✔
575
    t_n3 = t_percentile(1.0 - alpha / 2.0, n - 3);
11✔
576
  } else {
577
    t_n1 = 1.0;
578
    t_n3 = 1.0;
579
  }
580

581
  // write global tallies
582
  const auto& gt = simulation::global_tallies;
5,927✔
583
  double mean, stdev;
5,927✔
584
  if (n > 1) {
5,927✔
585
    if (settings::run_mode == RunMode::EIGENVALUE) {
5,444✔
586
      std::tie(mean, stdev) = mean_stdev(&gt(GlobalTally::K_COLLISION, 0), n);
3,044✔
587
      fmt::print(" k-effective (Collision)     = {:.5f} +/- {:.5f}\n", mean,
6,088✔
588
        t_n1 * stdev);
3,044✔
589
      std::tie(mean, stdev) = mean_stdev(&gt(GlobalTally::K_TRACKLENGTH, 0), n);
3,044✔
590
      fmt::print(" k-effective (Track-length)  = {:.5f} +/- {:.5f}\n", mean,
6,088✔
591
        t_n1 * stdev);
3,044✔
592
      std::tie(mean, stdev) = mean_stdev(&gt(GlobalTally::K_ABSORPTION, 0), n);
3,044✔
593
      fmt::print(" k-effective (Absorption)    = {:.5f} +/- {:.5f}\n", mean,
6,088✔
594
        t_n1 * stdev);
3,044✔
595
      if (n > 3) {
3,044✔
596
        double k_combined[2];
2,978✔
597
        openmc_get_keff(k_combined);
2,978✔
598
        fmt::print(" Combined k-effective        = {:.5f} +/- {:.5f}\n",
2,978✔
599
          k_combined[0], k_combined[1]);
600
      }
601
    }
602
    std::tie(mean, stdev) = mean_stdev(&gt(GlobalTally::LEAKAGE, 0), n);
5,444✔
603
    fmt::print(
9,886✔
604
      " Leakage Fraction            = {:.5f} +/- {:.5f}\n", mean, t_n1 * stdev);
5,444✔
605
  } else {
606
    if (mpi::master)
483!
607
      warning("Could not compute uncertainties -- only one "
966✔
608
              "active batch simulated!");
609

610
    if (settings::run_mode == RunMode::EIGENVALUE) {
483✔
611
      fmt::print(" k-effective (Collision)    = {:.5f}\n",
220✔
612
        gt(GlobalTally::K_COLLISION, TallyResult::SUM) / n);
110✔
613
      fmt::print(" k-effective (Track-length) = {:.5f}\n",
220✔
614
        gt(GlobalTally::K_TRACKLENGTH, TallyResult::SUM) / n);
110✔
615
      fmt::print(" k-effective (Absorption)   = {:.5f}\n",
200✔
616
        gt(GlobalTally::K_ABSORPTION, TallyResult::SUM) / n);
110✔
617
    }
618
    fmt::print(" Leakage Fraction           = {:.5f}\n",
877✔
619
      gt(GlobalTally::LEAKAGE, TallyResult::SUM) / n);
483✔
620
  }
621
  fmt::print("\n");
5,927✔
622
  std::fflush(stdout);
5,927✔
623
}
624

625
//==============================================================================
626

627
const std::unordered_map<int, const char*> score_names = {
628
  {SCORE_FLUX, "Flux"},
629
  {SCORE_TOTAL, "Total Reaction Rate"},
630
  {SCORE_SCATTER, "Scattering Rate"},
631
  {SCORE_NU_SCATTER, "Scattering Production Rate"},
632
  {SCORE_ABSORPTION, "Absorption Rate"},
633
  {SCORE_FISSION, "Fission Rate"},
634
  {SCORE_NU_FISSION, "Nu-Fission Rate"},
635
  {SCORE_KAPPA_FISSION, "Kappa-Fission Rate"},
636
  {SCORE_EVENTS, "Events"},
637
  {SCORE_DECAY_RATE, "Decay Rate"},
638
  {SCORE_DELAYED_NU_FISSION, "Delayed-Nu-Fission Rate"},
639
  {SCORE_PROMPT_NU_FISSION, "Prompt-Nu-Fission Rate"},
640
  {SCORE_INVERSE_VELOCITY, "Flux-Weighted Inverse Velocity"},
641
  {SCORE_FISS_Q_PROMPT, "Prompt fission power"},
642
  {SCORE_FISS_Q_RECOV, "Recoverable fission power"},
643
  {SCORE_CURRENT, "Current"},
644
  {SCORE_PULSE_HEIGHT, "pulse-height"},
645
  {SCORE_IFP_TIME_NUM, "IFP lifetime numerator"},
646
  {SCORE_IFP_BETA_NUM, "IFP delayed fraction numerator"},
647
  {SCORE_IFP_DENOM, "IFP common denominator"},
648
};
649

650
//! Create an ASCII output file showing all tally results.
651

652
void write_tallies()
6,634✔
653
{
654
  if (model::tallies.empty())
6,634✔
655
    return;
2,090✔
656

657
  // Tag tallies.out written during the forward solve of an adjoint run
658
  const char* forward =
9,088✔
659
    (FlatSourceDomain::solve_ == RandomRaySolve::FORWARD_FOR_ADJOINT)
4,544✔
660
      ? "forward."
4,544✔
661
      : "";
662

663
  // Set filename for tallies_out
664
  std::string filename =
4,544✔
665
    fmt::format("{}tallies.{}out", settings::path_output, forward);
4,544✔
666

667
  // Open the tallies.out file.
668
  std::ofstream tallies_out;
4,544✔
669
  tallies_out.open(filename, std::ios::out | std::ios::trunc);
4,544✔
670

671
  // Loop over each tally.
672
  for (auto i_tally = 0; i_tally < model::tallies.size(); ++i_tally) {
24,957✔
673
    const auto& tally {*model::tallies[i_tally]};
20,413✔
674

675
    // Write header block.
676
    std::string tally_header("TALLY " + std::to_string(tally.id_));
20,413✔
677
    if (!tally.name_.empty())
20,413✔
678
      tally_header += ": " + tally.name_;
4,900✔
679
    fmt::print(tallies_out, "{}\n\n", header(tally_header));
20,413✔
680

681
    if (!tally.writable_) {
20,413✔
682
      fmt::print(tallies_out, " Internal\n\n");
600✔
683
      continue;
600✔
684
    }
685

686
    // Calculate t-value for confidence intervals
687
    double t_value = 1;
19,813✔
688
    if (settings::confidence_intervals) {
19,813✔
689
      auto alpha = 1 - CONFIDENCE_LEVEL;
11✔
690
      t_value = t_percentile(1 - alpha * 0.5, tally.n_realizations_ - 1);
11✔
691
    }
692

693
    // Write derivative information.
694
    if (tally.deriv_ != C_NONE) {
19,813✔
695
      const auto& deriv {model::tally_derivs[tally.deriv_]};
220!
696
      switch (deriv.variable) {
220!
697
      case DerivativeVariable::DENSITY:
88✔
698
        fmt::print(tallies_out, " Density derivative Material {}\n",
176✔
699
          deriv.diff_material);
88✔
700
        break;
88✔
701
      case DerivativeVariable::NUCLIDE_DENSITY:
88✔
702
        fmt::print(tallies_out,
176✔
703
          " Nuclide density derivative Material {} Nuclide {}\n",
704
          deriv.diff_material, data::nuclides[deriv.diff_nuclide]->name_);
88✔
705
        break;
88✔
706
      case DerivativeVariable::TEMPERATURE:
44✔
707
        fmt::print(tallies_out, " Temperature derivative Material {}\n",
88✔
708
          deriv.diff_material);
44✔
709
        break;
44✔
UNCOV
710
      default:
×
UNCOV
711
        fatal_error(fmt::format("Differential tally dependent variable for "
×
712
                                "tally {} not defined in output.cpp",
UNCOV
713
          tally.id_));
×
714
      }
715
    }
716

717
    // Initialize Filter Matches Object
718
    vector<FilterMatch> filter_matches;
39,626✔
719
    // Allocate space for tally filter matches
720
    filter_matches.resize(model::tally_filters.size());
19,813✔
721

722
    // Loop over all filter bin combinations.
723
    auto filter_iter = FilterBinIter(tally, false, &filter_matches);
19,813✔
724
    auto end = FilterBinIter(tally, true, &filter_matches);
19,813✔
725
    for (; filter_iter != end; ++filter_iter) {
10,161,267✔
726
      auto filter_index = filter_iter.index_;
727

728
      // Print info about this combination of filter bins.  The stride check
729
      // prevents redundant output.
730
      int indent = 0;
731
      for (auto i = 0; i < tally.filters().size(); ++i) {
32,541,902✔
732
        if (filter_index % tally.strides(i) == 0) {
22,400,448✔
733
          auto i_filt = tally.filters(i);
15,671,484✔
734
          const auto& filt {*model::tally_filters[i_filt]};
15,671,484✔
735
          auto& match {filter_matches[i_filt]};
15,671,484✔
736
          fmt::print(tallies_out, "{0:{1}}{2}\n", "", indent + 1,
15,671,484✔
737
            filt.text_label(match.i_bin_));
31,342,968✔
738
        }
739
        indent += 2;
22,400,448✔
740
      }
741

742
      // Loop over all nuclide and score combinations.
743
      int score_index = 0;
10,141,454✔
744
      for (auto i_nuclide : tally.nuclides_) {
21,710,015✔
745
        // Write label for this nuclide bin.
746
        if (i_nuclide == -1) {
11,568,561✔
747
          fmt::print(tallies_out, "{0:{1}}Total Material\n", "", indent + 1);
8,732,794✔
748
        } else {
749
          if (settings::run_CE) {
2,835,767✔
750
            fmt::print(tallies_out, "{0:{1}}{2}\n", "", indent + 1,
2,835,393✔
751
              data::nuclides[i_nuclide]->name_);
2,835,393✔
752
          } else {
753
            fmt::print(tallies_out, "{0:{1}}{2}\n", "", indent + 1,
374✔
754
              data::mg.nuclides_[i_nuclide].name);
374✔
755
          }
756
        }
757

758
        // Write the score, mean, and uncertainty.
759
        indent += 2;
11,568,561✔
760
        for (auto score : tally.scores_) {
26,010,267✔
761
          std::string score_name =
14,441,706✔
762
            score > 0 ? reaction_name(score) : score_names.at(score);
14,441,706✔
763
          double mean, stdev;
14,441,706✔
764
          std::tie(mean, stdev) =
14,441,706✔
765
            mean_stdev(&tally.results_(filter_index, score_index, 0),
28,883,412✔
766
              tally.n_realizations_);
14,441,706✔
767
          fmt::print(tallies_out, "{0:{1}}{2:<36} {3:.6} +/- {4:.6}\n", "",
28,883,412✔
768
            indent + 1, score_name, mean, t_value * stdev);
14,441,706✔
769
          score_index += 1;
14,441,706✔
770
        }
14,441,706✔
771
        indent -= 2;
11,568,561✔
772
      }
773
    }
774
  }
20,413✔
775
}
4,544✔
776

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

© 2026 Coveralls, Inc