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

openmc-dev / openmc / 29869880772

21 Jul 2026 09:25PM UTC coverage: 81.397% (+0.09%) from 81.305%
29869880772

Pull #3971

github

web-flow
Merge 7d9285442 into 852f92780
Pull Request #3971: Delta tracking

18715 of 27082 branches covered (69.1%)

Branch coverage included in aggregate %.

613 of 661 new or added lines in 20 files covered. (92.74%)

546 existing lines in 13 files now uncovered.

60444 of 70168 relevant lines covered (86.14%)

48954501.23 hits per line

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

85.61
/src/geometry_aux.cpp
1
#include "openmc/geometry_aux.h"
2

3
#include <algorithm> // for std::max
4
#include <sstream>
5
#include <unordered_set>
6

7
#include <fmt/core.h>
8
#include <pugixml.hpp>
9

10
#include "openmc/cell.h"
11
#include "openmc/constants.h"
12
#include "openmc/container_util.h"
13
#include "openmc/dagmc.h"
14
#include "openmc/error.h"
15
#include "openmc/file_utils.h"
16
#include "openmc/geometry.h"
17
#include "openmc/lattice.h"
18
#include "openmc/material.h"
19
#include "openmc/settings.h"
20
#include "openmc/surface.h"
21
#include "openmc/tallies/filter.h"
22
#include "openmc/tallies/filter_cell_instance.h"
23
#include "openmc/tallies/filter_distribcell.h"
24

25
namespace openmc {
26

27
namespace model {
28
std::unordered_map<int32_t, int32_t> universe_level_counts;
29
} // namespace model
30

31
void read_geometry_xml()
1,399✔
32
{
33
  // Display output message
34
  write_message("Reading geometry XML file...", 5);
1,399✔
35

36
  // Check if geometry.xml exists
37
  std::string filename = settings::path_input + "geometry.xml";
1,399✔
38
  if (!file_exists(filename)) {
1,399!
39
    fatal_error("Geometry XML file '" + filename + "' does not exist!");
×
40
  }
41

42
  // Parse settings.xml file
43
  pugi::xml_document doc;
1,399✔
44
  auto result = doc.load_file(filename.c_str());
1,399✔
45
  if (!result) {
1,399!
46
    fatal_error("Error processing geometry.xml file.");
×
47
  }
48

49
  // Get root element
50
  pugi::xml_node root = doc.document_element();
1,399✔
51

52
  read_geometry_xml(root);
1,399✔
53
}
1,399✔
54

55
void read_geometry_xml(pugi::xml_node root)
9,113✔
56
{
57
  // Read surfaces, cells, lattice
58
  std::set<std::pair<int, int>> periodic_pairs;
9,113✔
59
  std::unordered_map<int, double> albedo_map;
9,113✔
60
  std::unordered_map<int, int> periodic_sense_map;
9,113✔
61

62
  read_surfaces(root, periodic_pairs, albedo_map, periodic_sense_map);
9,113✔
63
  read_cells(root);
9,113✔
64
  prepare_boundary_conditions(periodic_pairs, albedo_map, periodic_sense_map);
9,111✔
65
  read_lattices(root);
9,111✔
66

67
  // Check to make sure a boundary condition was applied to at least one
68
  // surface
69
  bool boundary_exists = false;
9,111✔
70
  for (const auto& surf : model::surfaces) {
22,096✔
71
    if (surf->bc_) {
22,063✔
72
      boundary_exists = true;
73
      break;
74
    }
75
  }
76

77
  if (settings::run_mode != RunMode::PLOTTING &&
9,111✔
78
      settings::run_mode != RunMode::VOLUME && !boundary_exists) {
8,072!
79
    fatal_error("No boundary conditions were applied to any surfaces!");
×
80
  }
81

82
  // Allocate universes, universe cell arrays, and assign base universe
83
  model::root_universe = find_root_universe();
9,111✔
84

85
  // if the root universe is DAGMC geometry, make sure the model is well-formed
86
  check_dagmc_root_univ();
9,111✔
87
}
18,222✔
88

89
//==============================================================================
90

91
void adjust_indices()
9,113✔
92
{
93
  // Adjust material/fill idices.
94
  for (auto& c : model::cells) {
45,820✔
95
    if (c->fill_ != C_NONE) {
36,707✔
96
      int32_t id = c->fill_;
7,409✔
97
      auto search_univ = model::universe_map.find(id);
7,409✔
98
      auto search_lat = model::lattice_map.find(id);
7,409✔
99
      if (search_univ != model::universe_map.end()) {
7,409✔
100
        c->type_ = Fill::UNIVERSE;
5,277✔
101
        c->fill_ = search_univ->second;
5,277✔
102
      } else if (search_lat != model::lattice_map.end()) {
2,132!
103
        c->type_ = Fill::LATTICE;
2,132✔
104
        c->fill_ = search_lat->second;
2,132✔
105
      } else {
106
        fatal_error(fmt::format("Specified fill {} on cell {} is neither a "
×
107
                                "universe nor a lattice.",
108
          id, c->id_));
×
109
      }
110
    } else {
111
      c->type_ = Fill::MATERIAL;
29,298✔
112
      for (auto& mat_id : c->material_) {
59,925✔
113
        if (mat_id != MATERIAL_VOID) {
30,627✔
114
          auto search = model::material_map.find(mat_id);
21,490!
115
          if (search == model::material_map.end()) {
21,490!
116
            fatal_error(
×
117
              fmt::format("Could not find material {} specified on cell {}",
×
118
                mat_id, c->id_));
×
119
          }
120
          // Change from ID to index
121
          mat_id = search->second;
21,490✔
122
        }
123
      }
124
    }
125
  }
126

127
  // Change cell.universe values from IDs to indices.
128
  for (auto& c : model::cells) {
45,820✔
129
    auto search = model::universe_map.find(c->universe_);
36,707!
130
    if (search != model::universe_map.end()) {
36,707!
131
      c->universe_ = search->second;
36,707✔
132
    } else {
133
      fatal_error(fmt::format("Could not find universe {} specified on cell {}",
×
134
        c->universe_, c->id_));
×
135
    }
136
  }
137

138
  // Change all lattice universe values from IDs to indices.
139
  for (auto& l : model::lattices) {
11,212✔
140
    l->adjust_indices();
2,099✔
141
  }
142
}
9,113✔
143

144
//==============================================================================
145
//! Partition some universes with many z-planes for faster find_cell searches.
146

147
void partition_universes()
9,113✔
148
{
149
  // Iterate over universes with more than 10 cells.  (Fewer than 10 is likely
150
  // not worth partitioning.)
151
  for (const auto& univ : model::universes) {
30,227✔
152
    if (univ->cells_.size() > 10) {
21,114✔
153
      // Collect the set of surfaces in this universe.
154
      std::unordered_set<int32_t> surf_inds;
168✔
155
      for (auto i_cell : univ->cells_) {
2,841✔
156
        for (auto token : model::cells[i_cell]->surfaces()) {
10,122✔
157
          surf_inds.insert(std::abs(token) - 1);
7,449✔
158
        }
2,673✔
159
      }
160

161
      // Partition the universe if there are more than 5 z-planes.  (Fewer than
162
      // 5 is likely not worth it.)
163
      int n_zplanes = 0;
168✔
164
      for (auto i_surf : surf_inds) {
2,172✔
165
        if (dynamic_cast<const SurfaceZPlane*>(model::surfaces[i_surf].get())) {
2,094!
166
          ++n_zplanes;
600✔
167
          if (n_zplanes > 5) {
600✔
168
            univ->partitioner_ = make_unique<UniversePartitioner>(*univ);
90✔
169
            break;
90✔
170
          }
171
        }
172
      }
173
    }
168✔
174
  }
175
}
9,113✔
176

177
//==============================================================================
178

179
void assign_temperatures()
9,113✔
180
{
181
  for (auto& c : model::cells) {
45,820✔
182
    // Ignore non-material cells and cells with defined temperature.
183
    if (c->material_.size() == 0)
36,707✔
184
      continue;
7,409✔
185
    if (c->sqrtkT_.size() > 0)
29,298✔
186
      continue;
551✔
187

188
    c->sqrtkT_.reserve(c->material_.size());
28,747✔
189
    for (auto i_mat : c->material_) {
58,814✔
190
      if (i_mat == MATERIAL_VOID) {
30,067✔
191
        // Set void region to 0K.
192
        c->sqrtkT_.push_back(0);
9,137✔
193
      } else {
194
        const auto& mat {model::materials[i_mat]};
20,930✔
195
        c->sqrtkT_.push_back(std::sqrt(K_BOLTZMANN * mat->temperature()));
20,930✔
196
      }
197
    }
198
  }
199
}
9,113✔
200

201
//==============================================================================
202

203
void finalize_cell_densities()
9,113✔
204
{
205
  for (auto& c : model::cells) {
45,820✔
206
    // Convert to density multipliers.
207
    if (!c->density_mult_.empty()) {
36,707✔
208
      for (int32_t instance = 0; instance < c->density_mult_.size();
1,380✔
209
           ++instance) {
210
        c->density_mult_[instance] /=
2,550!
211
          model::materials[c->material(instance)]->density_gpcc();
3,825!
212
      }
213
    } else {
214
      c->density_mult_ = {1.0};
36,602✔
215
    }
216
  }
217
}
9,113✔
218

219
//==============================================================================
220

221
void get_temperatures(
8,972✔
222
  vector<vector<double>>& nuc_temps, vector<vector<double>>& thermal_temps)
223
{
224
  for (const auto& cell : model::cells) {
45,311✔
225
    // Skip non-material cells.
226
    if (cell->fill_ != C_NONE)
36,339✔
227
      continue;
7,398✔
228

229
    for (int j = 0; j < cell->material_.size(); ++j) {
59,211✔
230
      // Skip void materials
231
      int i_material = cell->material_[j];
30,270✔
232
      if (i_material == MATERIAL_VOID)
30,270✔
233
        continue;
9,071✔
234

235
      // Get temperature(s) of cell (rounding to nearest integer)
236
      vector<double> cell_temps;
21,199✔
237
      if (cell->sqrtkT_.size() == 1) {
21,199✔
238
        double sqrtkT = cell->sqrtkT_[0];
19,634✔
239
        cell_temps.push_back(sqrtkT * sqrtkT / K_BOLTZMANN);
19,634✔
240
      } else if (cell->sqrtkT_.size() == cell->material_.size()) {
1,565✔
241
        double sqrtkT = cell->sqrtkT_[j];
1,505✔
242
        cell_temps.push_back(sqrtkT * sqrtkT / K_BOLTZMANN);
1,505✔
243
      } else {
244
        for (double sqrtkT : cell->sqrtkT_)
1,200✔
245
          cell_temps.push_back(sqrtkT * sqrtkT / K_BOLTZMANN);
1,140✔
246
      }
247

248
      const auto& mat {model::materials[i_material]};
21,199✔
249
      for (const auto& i_nuc : mat->nuclide_) {
94,387✔
250
        for (double temperature : cell_temps) {
147,456✔
251
          // Add temperature if it hasn't already been added
252
          if (!contains(nuc_temps[i_nuc], temperature))
74,268✔
253
            nuc_temps[i_nuc].push_back(temperature);
35,244✔
254
        }
255
      }
256

257
      for (const auto& table : mat->thermal_tables_) {
24,789✔
258
        // Get index in data::thermal_scatt array
259
        int i_sab = table.index_table;
3,590✔
260

261
        for (double temperature : cell_temps) {
7,180✔
262
          // Add temperature if it hasn't already been added
263
          if (!contains(thermal_temps[i_sab], temperature))
3,590✔
264
            thermal_temps[i_sab].push_back(temperature);
1,589✔
265
        }
266
      }
267
    }
21,199✔
268
  }
269
}
8,972✔
270

271
//==============================================================================
272

273
void detect_boundary_surfaces()
9,113✔
274
{
275
  for (int i = 0; i < model::surfaces.size(); i++) {
56,793✔
276
    // if the surface has a non-transmission boundary condition,
277
    // add it to the list of surfaces to track during delta tracking
278
    const auto& s = model::surfaces[i];
47,680✔
279
    if (s->bc_) {
47,680✔
280
      // A bug in MOAB causes ray_fire to fail on surface primatives in
281
      // DAGSurface. This prevents us from tracking the distance to the closest
282
      // boundary for DAGMC geometry, and so we need to avoid applying BCs with
283
      // DAGMC. This doesn't happen in surface tracking as ray_fire is called on
284
      // a volume primative through DAGCell.
285
      if (settings::delta_tracking && s->geom_type() == GeometryType::DAG) {
29,006!
NEW
286
        fatal_error("At present, the application of boundary conditions to "
×
287
                    "DAGMC surfaces is not supported when running with delta "
288
                    "tracking. If you wish to use DAGMC models with delta "
289
                    "tracking, please remove all boundary conditions from the "
290
                    "DAGMC universe (including the graveyard) and instead apply "
291
                    "them with CSG cells filled with the DAGMC universe.");
292
      }
293
      model::boundary_surfaces.push_back(i);
29,006✔
294
    }
295
  }
296
}
9,113✔
297

298
//==============================================================================
299

300
void finalize_geometry()
9,113✔
301
{
302
  // Perform some final operations to set up the geometry
303
  adjust_indices();
9,113✔
304
  count_universe_instances();
9,113✔
305
  partition_universes();
9,113✔
306

307
  // Assign temperatures to cells that don't have temperatures already assigned
308
  assign_temperatures();
9,113✔
309

310
  // Find all boundary surfaces. Used in delta tracking to trace through the
311
  // geometry.
312
  detect_boundary_surfaces();
9,113✔
313

314
  // Determine number of nested coordinate levels in the geometry
315
  model::n_coord_levels = maximum_levels(model::root_universe);
9,113✔
316
}
9,113✔
317

318
//==============================================================================
319

320
int32_t find_root_universe()
9,113✔
321
{
322
  // Find all the universes listed as a cell fill.
323
  std::unordered_set<int32_t> fill_univ_ids;
9,113✔
324
  for (const auto& c : model::cells) {
45,820✔
325
    fill_univ_ids.insert(c->fill_);
36,707✔
326
  }
327

328
  // Find all the universes contained in a lattice.
329
  for (const auto& lat : model::lattices) {
11,212✔
330
    for (auto it = lat->begin(); it != lat->end(); ++it) {
969,362✔
331
      fill_univ_ids.insert(*it);
967,263✔
332
    }
333
    if (lat->outer_ != NO_OUTER_UNIVERSE) {
2,099✔
334
      fill_univ_ids.insert(lat->outer_);
430✔
335
    }
336
  }
337

338
  // Figure out which universe is not in the set.  This is the root universe.
339
  bool root_found {false};
340
  int32_t root_univ;
341
  for (int32_t i = 0; i < model::universes.size(); i++) {
30,227✔
342
    auto search = fill_univ_ids.find(model::universes[i]->id_);
21,114✔
343
    if (search == fill_univ_ids.end()) {
21,114✔
344
      if (root_found) {
9,113!
UNCOV
345
        fatal_error("Two or more universes are not used as fill universes, so "
×
346
                    "it is not possible to distinguish which one is the root "
347
                    "universe.");
348
      } else {
349
        root_found = true;
350
        root_univ = i;
351
      }
352
    }
353
  }
354
  if (!root_found)
9,113!
UNCOV
355
    fatal_error("Could not find a root universe.  Make sure "
×
356
                "there are no circular dependencies in the geometry.");
357

358
  return root_univ;
9,113✔
359
}
9,113✔
360

361
//==============================================================================
362

363
void prepare_distribcell(const std::vector<int32_t>* user_distribcells)
9,086✔
364
{
365
  write_message("Preparing distributed cell instances...", 5);
9,086✔
366

367
  std::unordered_set<int32_t> distribcells;
9,086✔
368

369
  // start with any cells manually specified via the C++ API
370
  if (user_distribcells) {
9,086✔
371
    distribcells.insert(user_distribcells->begin(), user_distribcells->end());
15✔
372
  }
373

374
  // Find all cells listed in a DistribcellFilter or CellInstanceFilter
375
  for (auto& filt : model::tally_filters) {
19,842✔
376
    auto* distrib_filt = dynamic_cast<DistribcellFilter*>(filt.get());
10,756!
377
    auto* cell_inst_filt = dynamic_cast<CellInstanceFilter*>(filt.get());
10,756!
378
    if (distrib_filt) {
10,756✔
379
      distribcells.insert(distrib_filt->cell());
179✔
380
    }
381
    if (cell_inst_filt) {
10,756✔
382
      const auto& filter_cells = cell_inst_filt->cells();
32✔
383
      distribcells.insert(filter_cells.begin(), filter_cells.end());
32✔
384
    }
385
  }
386

387
  // By default, add material cells to the list of distributed cells
388
  if (settings::material_cell_offsets) {
9,086!
389
    for (int64_t i = 0; i < model::cells.size(); ++i) {
45,796✔
390
      if (model::cells[i]->type_ == Fill::MATERIAL)
36,710✔
391
        distribcells.insert(i);
29,271✔
392
    }
393
  }
394

395
  // Make sure that the number of materials/temperatures matches the number of
396
  // cell instances.
397
  for (int i = 0; i < model::cells.size(); i++) {
45,796✔
398
    Cell& c {*model::cells[i]};
36,710✔
399

400
    if (c.material_.size() > 1) {
36,710✔
401
      if (c.material_.size() != c.n_instances()) {
203!
UNCOV
402
        fatal_error(fmt::format(
×
403
          "Cell {} was specified with {} materials but has {} distributed "
404
          "instances. The number of materials must equal one or the number "
405
          "of instances.",
UNCOV
406
          c.id_, c.material_.size(), c.n_instances()));
×
407
      }
408
    }
409

410
    if (c.sqrtkT_.size() > 1) {
36,710✔
411
      if (c.sqrtkT_.size() != c.n_instances()) {
260!
UNCOV
412
        fatal_error(fmt::format(
×
413
          "Cell {} was specified with {} temperatures but has {} distributed "
414
          "instances. The number of temperatures must equal one or the number "
415
          "of instances.",
UNCOV
416
          c.id_, c.sqrtkT_.size(), c.n_instances()));
×
417
      }
418
    }
419

420
    if (c.density_mult_.size() > 1) {
36,710✔
421
      if (c.density_mult_.size() != c.n_instances()) {
90!
UNCOV
422
        fatal_error(fmt::format("Cell {} was specified with {} density "
×
423
                                "multipliers but has {} distributed "
424
                                "instances. The number of density multipliers "
425
                                "must equal one or the number "
426
                                "of instances.",
UNCOV
427
          c.id_, c.density_mult_.size(), c.n_instances()));
×
428
      }
429
    }
430
  }
431

432
  // Search through universes for material cells and assign each one a
433
  // distribcell array index according to the containing universe.
434
  vector<int32_t> target_univ_ids;
9,086✔
435
  for (const auto& u : model::universes) {
30,203✔
436
    for (auto idx : u->cells_) {
57,827✔
437
      if (distribcells.find(idx) != distribcells.end()) {
36,710✔
438
        if (!contains(target_univ_ids, u->id_)) {
29,346✔
439
          target_univ_ids.push_back(u->id_);
18,198✔
440
        }
441
        model::cells[idx]->distribcell_index_ =
29,346✔
442
          std::find(target_univ_ids.begin(), target_univ_ids.end(), u->id_) -
29,346✔
443
          target_univ_ids.begin();
29,346✔
444
      }
445
    }
446
  }
447

448
  // Allocate the cell and lattice offset tables.
449
  int n_maps = target_univ_ids.size();
9,086✔
450
  for (auto& c : model::cells) {
45,796✔
451
    if (c->type_ != Fill::MATERIAL) {
36,710✔
452
      c->offset_.resize(n_maps, C_NONE);
7,439✔
453
    }
454
  }
455
  for (auto& lat : model::lattices) {
11,200✔
456
    lat->allocate_offset_table(n_maps);
2,114✔
457
  }
458

459
// Fill the cell and lattice offset tables.
460
#pragma omp parallel for
5,191✔
461
  for (int map = 0; map < target_univ_ids.size(); map++) {
9,073✔
462
    auto target_univ_id = target_univ_ids[map];
5,178✔
463
    std::unordered_map<int32_t, int32_t> univ_count_memo;
5,178✔
464
    for (const auto& univ : model::universes) {
22,576✔
465
      int32_t offset = 0;
17,398✔
466
      for (int32_t cell_indx : univ->cells_) {
75,839✔
467
        Cell& c = *model::cells[cell_indx];
58,441✔
468

469
        if (c.type_ == Fill::UNIVERSE) {
58,441✔
470
          c.offset_[map] = offset;
31,283✔
471
          int32_t search_univ = c.fill_;
31,283✔
472
          offset += count_universe_instances(
31,283✔
473
            search_univ, target_univ_id, univ_count_memo);
474

475
        } else if (c.type_ == Fill::LATTICE) {
27,158✔
476
          c.offset_[map] = offset;
2,811✔
477
          Lattice& lat = *model::lattices[c.fill_];
2,811✔
478
          offset += lat.fill_offset_table(target_univ_id, map, univ_count_memo);
2,811✔
479
        }
480
      }
481
    }
482
  }
5,178✔
483
}
18,172✔
484

485
//==============================================================================
486

487
void count_universe_instances()
9,113✔
488
{
489
  for (auto& univ : model::universes) {
30,227✔
490
    std::unordered_map<int32_t, int32_t> univ_count_memo;
21,114✔
491
    univ->n_instances_ = count_universe_instances(
21,114✔
492
      model::root_universe, univ->id_, univ_count_memo);
21,114✔
493
  }
21,114✔
494
}
9,113✔
495

496
//==============================================================================
497

498
int count_universe_instances(int32_t search_univ, int32_t target_univ_id,
22,667,352✔
499
  std::unordered_map<int32_t, int32_t>& univ_count_memo)
500
{
501
  // If this is the target, it can't contain itself.
502
  if (model::universes[search_univ]->id_ == target_univ_id) {
22,667,352✔
503
    return 1;
504
  }
505

506
  // If we have already counted the number of instances, reuse that value.
507
  auto search = univ_count_memo.find(search_univ);
19,935,709✔
508
  if (search != univ_count_memo.end()) {
19,935,709✔
509
    return search->second;
7,870,683✔
510
  }
511

512
  int count {0};
12,065,026✔
513
  for (int32_t cell_indx : model::universes[search_univ]->cells_) {
24,303,753✔
514
    Cell& c = *model::cells[cell_indx];
12,238,727✔
515

516
    if (c.type_ == Fill::UNIVERSE) {
12,238,727✔
517
      int32_t next_univ = c.fill_;
159,531✔
518
      count +=
159,531✔
519
        count_universe_instances(next_univ, target_univ_id, univ_count_memo);
159,531✔
520

521
    } else if (c.type_ == Fill::LATTICE) {
12,079,196✔
522
      Lattice& lat = *model::lattices[c.fill_];
21,438✔
523
      for (auto it = lat.begin(); it != lat.end(); ++it) {
13,015,789✔
524
        int32_t next_univ = *it;
12,994,351✔
525
        count +=
12,994,351✔
526
          count_universe_instances(next_univ, target_univ_id, univ_count_memo);
12,994,351✔
527
      }
528
    }
529
  }
530

531
  // Remember the number of instances in this universe.
532
  univ_count_memo[search_univ] = count;
12,065,026✔
533

534
  return count;
12,065,026✔
535
}
536

537
//==============================================================================
538

539
std::string distribcell_path_inner(int32_t target_cell, int32_t map,
2,565,574✔
540
  int32_t target_offset, const Universe& search_univ, int32_t offset)
541
{
542
  std::stringstream path;
2,565,574✔
543

544
  path << "u" << search_univ.id_ << "->";
2,565,574✔
545

546
  // Check to see if this universe directly contains the target cell.  If so,
547
  // write to the path and return.
548
  for (int32_t cell_indx : search_univ.cells_) {
11,902,935✔
549
    if ((cell_indx == target_cell) && (offset == target_offset)) {
10,264,408✔
550
      Cell& c = *model::cells[cell_indx];
927,047✔
551
      path << "c" << c.id_;
927,047✔
552
      return path.str();
927,047✔
553
    }
554
  }
555

556
  // The target must be further down the geometry tree and contained in a fill
557
  // cell or lattice cell in this universe.  Find which cell contains the
558
  // target.
559
  vector<std::int32_t>::const_reverse_iterator cell_it {
560
    search_univ.cells_.crbegin()};
561
  for (; cell_it != search_univ.cells_.crend(); ++cell_it) {
9,337,196!
562
    Cell& c = *model::cells[*cell_it];
9,337,196✔
563

564
    // Material cells don't contain other cells so ignore them.
565
    if (c.type_ != Fill::MATERIAL) {
9,337,196✔
566
      int32_t temp_offset = offset + c.offset_[map];
2,338,556!
567
      if (c.type_ == Fill::LATTICE) {
2,338,556!
568
        Lattice& lat = *model::lattices[c.fill_];
2,338,556✔
569
        int32_t indx = lat.universes_.size() * map + lat.begin().indx_;
2,338,556✔
570
        temp_offset += lat.offsets_[indx];
2,338,556✔
571
      }
572

573
      // The desired cell is the first cell that gives an offset smaller or
574
      // equal to the target offset.
575
      if (temp_offset <= target_offset)
2,338,556✔
576
        break;
577
    }
578
  }
579

580
  // if we get through the loop without finding an appropriate entry, throw
581
  // an error
582
  if (cell_it == search_univ.cells_.crend()) {
1,638,527!
UNCOV
583
    fatal_error(
×
584
      fmt::format("Failed to generate a text label for distribcell with ID {}."
×
585
                  "The current label is: '{}'",
UNCOV
586
        model::cells[target_cell]->id_, path.str()));
×
587
  }
588

589
  // Add the cell to the path string.
590
  Cell& c = *model::cells[*cell_it];
1,638,527✔
591
  path << "c" << c.id_ << "->";
1,638,527✔
592

593
  if (c.type_ == Fill::UNIVERSE) {
1,638,527!
594
    // Recurse into the fill cell.
UNCOV
595
    offset += c.offset_[map];
×
596
    path << distribcell_path_inner(
×
597
      target_cell, map, target_offset, *model::universes[c.fill_], offset);
×
598
    return path.str();
×
599
  } else {
600
    // Recurse into the lattice cell.
601
    Lattice& lat = *model::lattices[c.fill_];
1,638,527✔
602
    path << "l" << lat.id_;
1,638,527✔
603
    for (ReverseLatticeIter it = lat.rbegin(); it != lat.rend(); ++it) {
289,048,760!
604
      int32_t indx = lat.universes_.size() * map + it.indx_;
289,048,760✔
605
      int32_t temp_offset = offset + lat.offsets_[indx] + c.offset_[map];
289,048,760✔
606
      if (temp_offset <= target_offset) {
289,048,760✔
607
        offset = temp_offset;
1,638,527✔
608
        path << "(" << lat.index_to_string(it.indx_) << ")->";
3,277,054✔
609
        path << distribcell_path_inner(
1,638,527✔
610
          target_cell, map, target_offset, *model::universes[*it], offset);
3,277,054✔
611
        return path.str();
1,638,527✔
612
      }
613
    }
UNCOV
614
    throw std::runtime_error {"Error determining distribcell path."};
×
615
  }
616
}
2,565,574✔
617

618
std::string distribcell_path(
927,047✔
619
  int32_t target_cell, int32_t map, int32_t target_offset)
620
{
621
  auto& root_univ = *model::universes[model::root_universe];
927,047✔
622
  return distribcell_path_inner(target_cell, map, target_offset, root_univ, 0);
927,047✔
623
}
624

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

627
int maximum_levels(int32_t univ)
981,818✔
628
{
629

630
  const auto level_count = model::universe_level_counts.find(univ);
981,818✔
631
  if (level_count != model::universe_level_counts.end()) {
981,818✔
632
    return level_count->second;
960,859✔
633
  }
634

635
  int levels_below {0};
20,959✔
636

637
  for (int32_t cell_indx : model::universes[univ]->cells_) {
57,503✔
638
    Cell& c = *model::cells[cell_indx];
36,544✔
639
    if (c.type_ == Fill::UNIVERSE) {
36,544✔
640
      int32_t next_univ = c.fill_;
5,277✔
641
      levels_below = std::max(levels_below, maximum_levels(next_univ));
7,294✔
642
    } else if (c.type_ == Fill::LATTICE) {
31,267✔
643
      Lattice& lat = *model::lattices[c.fill_];
2,132✔
644
      for (auto it = lat.begin(); it != lat.end(); ++it) {
969,560✔
645
        int32_t next_univ = *it;
967,428✔
646
        levels_below = std::max(levels_below, maximum_levels(next_univ));
969,467✔
647
      }
648
    }
649
  }
650

651
  ++levels_below;
20,959✔
652
  model::universe_level_counts[univ] = levels_below;
20,959✔
653
  return levels_below;
20,959✔
654
}
655

656
bool is_root_universe(int32_t univ_id)
18,923✔
657
{
658
  return model::universe_map[univ_id] == model::root_universe;
18,923✔
659
}
660

661
//==============================================================================
662

663
void free_memory_geometry()
9,239✔
664
{
665
  model::cells.clear();
9,239✔
666
  model::cell_map.clear();
9,239✔
667

668
  model::universes.clear();
9,239✔
669
  model::universe_map.clear();
9,239✔
670

671
  model::lattices.clear();
9,239✔
672
  model::lattice_map.clear();
9,239✔
673

674
  model::overlap_check_count.clear();
9,239✔
675
}
9,239✔
676

677
} // 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