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

openmc-dev / openmc / 21043561037

15 Jan 2026 07:23PM UTC coverage: 81.388% (-0.7%) from 82.044%
21043561037

Pull #3734

github

web-flow
Merge 5e79b7b6f into 179048b80
Pull Request #3734: Specify temperature from a field (structured mesh only)

16703 of 22995 branches covered (72.64%)

Branch coverage included in aggregate %.

156 of 179 new or added lines in 12 files covered. (87.15%)

843 existing lines in 36 files now uncovered.

54487 of 64475 relevant lines covered (84.51%)

27592585.27 hits per line

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

87.72
/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/simulation.h"
22
#include "openmc/tallies/filter.h"
23
#include "openmc/tallies/filter_cell_instance.h"
24
#include "openmc/tallies/filter_distribcell.h"
25

26
namespace openmc {
27

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

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

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

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

50
  // Get root element
51
  pugi::xml_node root = doc.document_element();
607✔
52

53
  read_geometry_xml(root);
607✔
54
}
607✔
55

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

63
  read_surfaces(root, periodic_pairs, albedo_map, periodic_sense_map);
3,537✔
64
  read_cells(root);
3,537✔
65
  prepare_boundary_conditions(periodic_pairs, albedo_map, periodic_sense_map);
3,537✔
66
  read_lattices(root);
3,537✔
67

68
  // Check to make sure a boundary condition was applied to at least one
69
  // surface
70
  bool boundary_exists = false;
3,537✔
71
  for (const auto& surf : model::surfaces) {
8,825✔
72
    if (surf->bc_) {
8,810✔
73
      boundary_exists = true;
3,522✔
74
      break;
3,522✔
75
    }
76
  }
77

78
  if (settings::run_mode != RunMode::PLOTTING &&
3,537✔
79
      settings::run_mode != RunMode::VOLUME && !boundary_exists) {
3,484!
80
    fatal_error("No boundary conditions were applied to any surfaces!");
×
81
  }
82

83
  // Allocate universes, universe cell arrays, and assign base universe
84
  model::root_universe = find_root_universe();
3,537✔
85

86
  // if the root universe is DAGMC geometry, make sure the model is well-formed
87
  check_dagmc_root_univ();
3,537✔
88
}
3,537✔
89

90
//==============================================================================
91

92
void adjust_indices()
3,537✔
93
{
94
  // Adjust material/fill idices.
95
  for (auto& c : model::cells) {
19,924✔
96
    if (c->fill_ != C_NONE) {
16,387✔
97
      int32_t id = c->fill_;
3,167✔
98
      auto search_univ = model::universe_map.find(id);
3,167✔
99
      auto search_lat = model::lattice_map.find(id);
3,167✔
100
      if (search_univ != model::universe_map.end()) {
3,167✔
101
        c->type_ = Fill::UNIVERSE;
2,314✔
102
        c->fill_ = search_univ->second;
2,314✔
103
      } else if (search_lat != model::lattice_map.end()) {
853!
104
        c->type_ = Fill::LATTICE;
853✔
105
        c->fill_ = search_lat->second;
853✔
106
      } else {
107
        fatal_error(fmt::format("Specified fill {} on cell {} is neither a "
×
108
                                "universe nor a lattice.",
109
          id, c->id_));
×
110
      }
111
    } else {
112
      c->type_ = Fill::MATERIAL;
13,220✔
113
      for (auto& mat_id : c->material_) {
27,030✔
114
        if (mat_id != MATERIAL_VOID) {
13,810✔
115
          auto search = model::material_map.find(mat_id);
8,612✔
116
          if (search == model::material_map.end()) {
8,612!
117
            fatal_error(
×
118
              fmt::format("Could not find material {} specified on cell {}",
×
119
                mat_id, c->id_));
×
120
          }
121
          // Change from ID to index
122
          mat_id = search->second;
8,612✔
123
        }
124
      }
125
    }
126
  }
127

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

139
  // Change all lattice universe values from IDs to indices.
140
  for (auto& l : model::lattices) {
4,375✔
141
    l->adjust_indices();
838✔
142
  }
143
}
3,537✔
144

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

148
void partition_universes()
3,537✔
149
{
150
  // Iterate over universes with more than 10 cells.  (Fewer than 10 is likely
151
  // not worth partitioning.)
152
  for (const auto& univ : model::universes) {
13,583✔
153
    if (univ->cells_.size() > 10) {
10,046✔
154
      // Collect the set of surfaces in this universe.
155
      std::unordered_set<int32_t> surf_inds;
78✔
156
      for (auto i_cell : univ->cells_) {
1,321✔
157
        for (auto token : model::cells[i_cell]->surfaces()) {
4,706✔
158
          surf_inds.insert(std::abs(token) - 1);
3,463✔
159
        }
1,243✔
160
      }
161

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

178
//==============================================================================
179

180
void assign_temperatures()
3,537✔
181
{
182
  for (auto& c : model::cells) {
19,924✔
183
    // Ignore non-material cells and cells with defined temperature.
184
    if (c->material_.size() == 0)
16,387✔
185
      continue;
3,167✔
186
    if (c->sqrtkT_.size() > 0)
13,220✔
187
      continue;
138✔
188

189
    c->sqrtkT_.reserve(c->material_.size());
13,082✔
190
    for (auto i_mat : c->material_) {
26,754✔
191
      if (i_mat == MATERIAL_VOID) {
13,672✔
192
        // Set void region to 0K.
193
        c->sqrtkT_.push_back(0);
5,198✔
194
      } else {
195
        const auto& mat {model::materials[i_mat]};
8,474✔
196
        c->sqrtkT_.push_back(std::sqrt(K_BOLTZMANN * mat->temperature()));
8,474✔
197
      }
198
    }
199
  }
200
}
3,537✔
201

202
//==============================================================================
203

204
void finalize_cell_densities()
3,537✔
205
{
206
  for (auto& c : model::cells) {
19,924✔
207
    // Convert to density multipliers.
208
    if (!c->density_mult_.empty()) {
16,387✔
209
      for (int32_t instance = 0; instance < c->density_mult_.size();
574✔
210
           ++instance) {
211
        c->density_mult_[instance] /=
539✔
212
          model::materials[c->material(instance)]->density_gpcc();
539✔
213
      }
214
    } else {
215
      c->density_mult_ = {1.0};
16,352✔
216
    }
217
  }
218
}
3,537✔
219

220
//==============================================================================
221

222
void get_temperatures(
3,484✔
223
  vector<vector<double>>& nuc_temps, vector<vector<double>>& thermal_temps)
224
{
225
  for (const auto& cell : model::cells) {
19,727✔
226
    // Skip non-material cells.
227
    if (cell->fill_ != C_NONE)
16,243✔
228
      continue;
3,162✔
229

230
    for (int j = 0; j < cell->material_.size(); ++j) {
26,752✔
231
      // Skip void materials
232
      int i_material = cell->material_[j];
13,671✔
233
      if (i_material == MATERIAL_VOID)
13,671✔
234
        continue;
5,188✔
235

236
      // Get temperature(s) of cell (rounding to nearest integer)
237
      vector<double> cell_temps;
8,483✔
238
      if (cell->sqrtkT_.size() == 1) {
8,483✔
239
        double sqrtkT = cell->sqrtkT_[0];
7,803✔
240
        cell_temps.push_back(sqrtkT * sqrtkT / K_BOLTZMANN);
7,803✔
241
      } else if (cell->sqrtkT_.size() == cell->material_.size()) {
680✔
242
        double sqrtkT = cell->sqrtkT_[j];
673✔
243
        cell_temps.push_back(sqrtkT * sqrtkT / K_BOLTZMANN);
673✔
244
      } else {
245
        for (double sqrtkT : cell->sqrtkT_)
35✔
246
          cell_temps.push_back(sqrtkT * sqrtkT / K_BOLTZMANN);
28✔
247
      }
248

249
      const auto& mat {model::materials[i_material]};
8,483✔
250
      for (const auto& i_nuc : mat->nuclide_) {
38,533✔
251
        for (double temperature : cell_temps) {
60,121✔
252
          // Add temperature if it hasn't already been added
253
          if (!contains(nuc_temps[i_nuc], temperature))
30,071✔
254
            nuc_temps[i_nuc].push_back(temperature);
13,244✔
255
        }
256
      }
257

258
      for (const auto& table : mat->thermal_tables_) {
9,930✔
259
        // Get index in data::thermal_scatt array
260
        int i_sab = table.index_table;
1,447✔
261

262
        for (double temperature : cell_temps) {
2,894✔
263
          // Add temperature if it hasn't already been added
264
          if (!contains(thermal_temps[i_sab], temperature))
1,447✔
265
            thermal_temps[i_sab].push_back(temperature);
537✔
266
        }
267
      }
268
    }
8,483✔
269
  }
270

271
  // Add temperatures from a temperature field.
272
  // We assume that we do not know how geometric cells are impacted by the
273
  // temperature field in advance. If we had access to this information, we
274
  // could limit the declarations of temperature from the temperature field to
275
  // impacted nuclides only.
276
  if (settings::temperature_field_on) {
3,484✔
277
    for (auto t : simulation::temperature_field.values()) {
63✔
278
      // Nuclide temperatures
279
      for (size_t i = 0; i < nuc_temps.size(); i++) {
392✔
280
        if (!contains(nuc_temps[i], t)) {
336!
281
          nuc_temps[i].push_back(t);
336✔
282
        }
283
      }
284
      // Thermal scattering temperatures
285
      for (size_t i = 0; i < thermal_temps.size(); i++) {
112✔
286
        if (!contains(thermal_temps[i], t)) {
56!
287
          thermal_temps[i].push_back(t);
56✔
288
        }
289
      }
290
    }
291
  }
292
}
3,484✔
293

294
//==============================================================================
295

296
void finalize_geometry()
3,537✔
297
{
298
  // Perform some final operations to set up the geometry
299
  adjust_indices();
3,537✔
300
  count_universe_instances();
3,537✔
301
  partition_universes();
3,537✔
302

303
  // Assign temperatures to cells that don't have temperatures already assigned
304
  assign_temperatures();
3,537✔
305

306
  // Determine number of nested coordinate levels in the geometry
307
  model::n_coord_levels = maximum_levels(model::root_universe);
3,537✔
308
}
3,537✔
309

310
//==============================================================================
311

312
int32_t find_root_universe()
3,537✔
313
{
314
  // Find all the universes listed as a cell fill.
315
  std::unordered_set<int32_t> fill_univ_ids;
3,537✔
316
  for (const auto& c : model::cells) {
19,924✔
317
    fill_univ_ids.insert(c->fill_);
16,387✔
318
  }
319

320
  // Find all the universes contained in a lattice.
321
  for (const auto& lat : model::lattices) {
4,375✔
322
    for (auto it = lat->begin(); it != lat->end(); ++it) {
419,838✔
323
      fill_univ_ids.insert(*it);
419,000✔
324
    }
325
    if (lat->outer_ != NO_OUTER_UNIVERSE) {
838✔
326
      fill_univ_ids.insert(lat->outer_);
177✔
327
    }
328
  }
329

330
  // Figure out which universe is not in the set.  This is the root universe.
331
  bool root_found {false};
3,537✔
332
  int32_t root_univ;
333
  for (int32_t i = 0; i < model::universes.size(); i++) {
13,583✔
334
    auto search = fill_univ_ids.find(model::universes[i]->id_);
10,046✔
335
    if (search == fill_univ_ids.end()) {
10,046✔
336
      if (root_found) {
3,537!
337
        fatal_error("Two or more universes are not used as fill universes, so "
×
338
                    "it is not possible to distinguish which one is the root "
339
                    "universe.");
340
      } else {
341
        root_found = true;
3,537✔
342
        root_univ = i;
3,537✔
343
      }
344
    }
345
  }
346
  if (!root_found)
3,537!
347
    fatal_error("Could not find a root universe.  Make sure "
×
348
                "there are no circular dependencies in the geometry.");
349

350
  return root_univ;
3,537✔
351
}
3,537✔
352

353
//==============================================================================
354

355
void prepare_distribcell(const std::vector<int32_t>* user_distribcells)
3,538✔
356
{
357
  write_message("Preparing distributed cell instances...", 5);
3,538✔
358

359
  std::unordered_set<int32_t> distribcells;
3,538✔
360

361
  // start with any cells manually specified via the C++ API
362
  if (user_distribcells) {
3,538✔
363
    distribcells.insert(user_distribcells->begin(), user_distribcells->end());
7✔
364
  }
365

366
  // Find all cells listed in a DistribcellFilter or CellInstanceFilter
367
  for (auto& filt : model::tally_filters) {
7,369✔
368
    auto* distrib_filt = dynamic_cast<DistribcellFilter*>(filt.get());
3,831!
369
    auto* cell_inst_filt = dynamic_cast<CellInstanceFilter*>(filt.get());
3,831!
370
    if (distrib_filt) {
3,831✔
371
      distribcells.insert(distrib_filt->cell());
83✔
372
    }
373
    if (cell_inst_filt) {
3,831✔
374
      const auto& filter_cells = cell_inst_filt->cells();
14✔
375
      distribcells.insert(filter_cells.begin(), filter_cells.end());
14✔
376
    }
377
  }
378

379
  // By default, add material cells to the list of distributed cells
380
  if (settings::material_cell_offsets) {
3,538!
381
    for (int64_t i = 0; i < model::cells.size(); ++i) {
19,954✔
382
      if (model::cells[i]->type_ == Fill::MATERIAL)
16,416✔
383
        distribcells.insert(i);
13,235✔
384
    }
385
  }
386

387
  // Make sure that the number of materials/temperatures matches the number of
388
  // cell instances.
389
  for (int i = 0; i < model::cells.size(); i++) {
19,954✔
390
    Cell& c {*model::cells[i]};
16,416✔
391

392
    if (c.material_.size() > 1) {
16,416✔
393
      if (c.material_.size() != c.n_instances()) {
90!
394
        fatal_error(fmt::format(
×
395
          "Cell {} was specified with {} materials but has {} distributed "
396
          "instances. The number of materials must equal one or the number "
397
          "of instances.",
398
          c.id_, c.material_.size(), c.n_instances()));
×
399
      }
400
    }
401

402
    if (c.sqrtkT_.size() > 1) {
16,416✔
403
      if (c.sqrtkT_.size() != c.n_instances()) {
97!
404
        fatal_error(fmt::format(
×
405
          "Cell {} was specified with {} temperatures but has {} distributed "
406
          "instances. The number of temperatures must equal one or the number "
407
          "of instances.",
408
          c.id_, c.sqrtkT_.size(), c.n_instances()));
×
409
      }
410
    }
411

412
    if (c.density_mult_.size() > 1) {
16,416✔
413
      if (c.density_mult_.size() != c.n_instances()) {
28!
414
        fatal_error(fmt::format("Cell {} was specified with {} density "
×
415
                                "multipliers but has {} distributed "
416
                                "instances. The number of density multipliers "
417
                                "must equal one or the number "
418
                                "of instances.",
419
          c.id_, c.density_mult_.size(), c.n_instances()));
×
420
      }
421
    }
422
  }
423

424
  // Search through universes for material cells and assign each one a
425
  // distribcell array index according to the containing universe.
426
  vector<int32_t> target_univ_ids;
3,538✔
427
  for (const auto& u : model::universes) {
13,599✔
428
    for (auto idx : u->cells_) {
26,477✔
429
      if (distribcells.find(idx) != distribcells.end()) {
16,416✔
430
        if (!contains(target_univ_ids, u->id_)) {
13,270✔
431
          target_univ_ids.push_back(u->id_);
8,864✔
432
        }
433
        model::cells[idx]->distribcell_index_ =
13,270✔
434
          std::find(target_univ_ids.begin(), target_univ_ids.end(), u->id_) -
13,270✔
435
          target_univ_ids.begin();
26,540✔
436
      }
437
    }
438
  }
439

440
  // Allocate the cell and lattice offset tables.
441
  int n_maps = target_univ_ids.size();
3,538✔
442
  for (auto& c : model::cells) {
19,954✔
443
    if (c->type_ != Fill::MATERIAL) {
16,416✔
444
      c->offset_.resize(n_maps, C_NONE);
3,181✔
445
    }
446
  }
447
  for (auto& lat : model::lattices) {
4,383✔
448
    lat->allocate_offset_table(n_maps);
845✔
449
  }
450

451
// Fill the cell and lattice offset tables.
452
#pragma omp parallel for
1,466✔
453
  for (int map = 0; map < target_univ_ids.size(); map++) {
4,910✔
454
    auto target_univ_id = target_univ_ids[map];
2,838✔
455
    std::unordered_map<int32_t, int32_t> univ_count_memo;
2,838✔
456
    for (const auto& univ : model::universes) {
13,375✔
457
      int32_t offset = 0;
10,537✔
458
      for (int32_t cell_indx : univ->cells_) {
47,539✔
459
        Cell& c = *model::cells[cell_indx];
37,002✔
460

461
        if (c.type_ == Fill::UNIVERSE) {
37,002✔
462
          c.offset_[map] = offset;
20,649✔
463
          int32_t search_univ = c.fill_;
20,649✔
464
          offset += count_universe_instances(
20,649✔
465
            search_univ, target_univ_id, univ_count_memo);
466

467
        } else if (c.type_ == Fill::LATTICE) {
16,353✔
468
          c.offset_[map] = offset;
1,719✔
469
          Lattice& lat = *model::lattices[c.fill_];
1,719✔
470
          offset += lat.fill_offset_table(target_univ_id, map, univ_count_memo);
1,719✔
471
        }
472
      }
473
    }
474
  }
2,838✔
475
}
3,538✔
476

477
//==============================================================================
478

479
void count_universe_instances()
3,537✔
480
{
481
  for (auto& univ : model::universes) {
13,583✔
482
    std::unordered_map<int32_t, int32_t> univ_count_memo;
10,046✔
483
    univ->n_instances_ = count_universe_instances(
10,046✔
484
      model::root_universe, univ->id_, univ_count_memo);
10,046✔
485
  }
10,046✔
486
}
3,537✔
487

488
//==============================================================================
489

490
int count_universe_instances(int32_t search_univ, int32_t target_univ_id,
12,660,548✔
491
  std::unordered_map<int32_t, int32_t>& univ_count_memo)
492
{
493
  // If this is the target, it can't contain itself.
494
  if (model::universes[search_univ]->id_ == target_univ_id) {
12,660,548✔
495
    return 1;
1,183,575✔
496
  }
497

498
  // If we have already counted the number of instances, reuse that value.
499
  auto search = univ_count_memo.find(search_univ);
11,476,973✔
500
  if (search != univ_count_memo.end()) {
11,476,973✔
501
    return search->second;
3,448,913✔
502
  }
503

504
  int count {0};
8,028,060✔
505
  for (int32_t cell_indx : model::universes[search_univ]->cells_) {
16,136,318✔
506
    Cell& c = *model::cells[cell_indx];
8,108,258✔
507

508
    if (c.type_ == Fill::UNIVERSE) {
8,108,258✔
509
      int32_t next_univ = c.fill_;
73,950✔
510
      count +=
73,950✔
511
        count_universe_instances(next_univ, target_univ_id, univ_count_memo);
73,950✔
512

513
    } else if (c.type_ == Fill::LATTICE) {
8,034,308✔
514
      Lattice& lat = *model::lattices[c.fill_];
10,872✔
515
      for (auto it = lat.begin(); it != lat.end(); ++it) {
7,078,369✔
516
        int32_t next_univ = *it;
7,067,497✔
517
        count +=
7,067,497✔
518
          count_universe_instances(next_univ, target_univ_id, univ_count_memo);
7,067,497✔
519
      }
520
    }
521
  }
522

523
  // Remember the number of instances in this universe.
524
  univ_count_memo[search_univ] = count;
8,028,060✔
525

526
  return count;
8,028,060✔
527
}
528

529
//==============================================================================
530

531
std::string distribcell_path_inner(int32_t target_cell, int32_t map,
1,166,170✔
532
  int32_t target_offset, const Universe& search_univ, int32_t offset)
533
{
534
  std::stringstream path;
1,166,170✔
535

536
  path << "u" << search_univ.id_ << "->";
1,166,170✔
537

538
  // Check to see if this universe directly contains the target cell.  If so,
539
  // write to the path and return.
540
  for (int32_t cell_indx : search_univ.cells_) {
5,410,425✔
541
    if ((cell_indx == target_cell) && (offset == target_offset)) {
4,665,640!
542
      Cell& c = *model::cells[cell_indx];
421,385✔
543
      path << "c" << c.id_;
421,385✔
544
      return path.str();
842,770✔
545
    }
546
  }
547

548
  // The target must be further down the geometry tree and contained in a fill
549
  // cell or lattice cell in this universe.  Find which cell contains the
550
  // target.
551
  vector<std::int32_t>::const_reverse_iterator cell_it {
552
    search_univ.cells_.crbegin()};
744,785✔
553
  for (; cell_it != search_univ.cells_.crend(); ++cell_it) {
4,244,180!
554
    Cell& c = *model::cells[*cell_it];
4,244,180✔
555

556
    // Material cells don't contain other cells so ignore them.
557
    if (c.type_ != Fill::MATERIAL) {
4,244,180✔
558
      int32_t temp_offset = offset + c.offset_[map];
1,062,980✔
559
      if (c.type_ == Fill::LATTICE) {
1,062,980!
560
        Lattice& lat = *model::lattices[c.fill_];
1,062,980✔
561
        int32_t indx = lat.universes_.size() * map + lat.begin().indx_;
1,062,980✔
562
        temp_offset += lat.offsets_[indx];
1,062,980✔
563
      }
564

565
      // The desired cell is the first cell that gives an offset smaller or
566
      // equal to the target offset.
567
      if (temp_offset <= target_offset)
1,062,980✔
568
        break;
744,785✔
569
    }
570
  }
571

572
  // if we get through the loop without finding an appropriate entry, throw
573
  // an error
574
  if (cell_it == search_univ.cells_.crend()) {
744,785!
575
    fatal_error(
×
576
      fmt::format("Failed to generate a text label for distribcell with ID {}."
×
577
                  "The current label is: '{}'",
578
        model::cells[target_cell]->id_, path.str()));
×
579
  }
580

581
  // Add the cell to the path string.
582
  Cell& c = *model::cells[*cell_it];
744,785✔
583
  path << "c" << c.id_ << "->";
744,785✔
584

585
  if (c.type_ == Fill::UNIVERSE) {
744,785!
586
    // Recurse into the fill cell.
587
    offset += c.offset_[map];
×
588
    path << distribcell_path_inner(
×
589
      target_cell, map, target_offset, *model::universes[c.fill_], offset);
×
590
    return path.str();
×
591
  } else {
592
    // Recurse into the lattice cell.
593
    Lattice& lat = *model::lattices[c.fill_];
744,785✔
594
    path << "l" << lat.id_;
744,785✔
595
    for (ReverseLatticeIter it = lat.rbegin(); it != lat.rend(); ++it) {
131,385,800!
596
      int32_t indx = lat.universes_.size() * map + it.indx_;
131,385,800✔
597
      int32_t temp_offset = offset + lat.offsets_[indx] + c.offset_[map];
131,385,800✔
598
      if (temp_offset <= target_offset) {
131,385,800✔
599
        offset = temp_offset;
744,785✔
600
        path << "(" << lat.index_to_string(it.indx_) << ")->";
744,785✔
601
        path << distribcell_path_inner(
1,489,570✔
602
          target_cell, map, target_offset, *model::universes[*it], offset);
1,489,570✔
603
        return path.str();
1,489,570✔
604
      }
605
    }
606
    throw std::runtime_error {"Error determining distribcell path."};
×
607
  }
608
}
1,166,170✔
609

610
std::string distribcell_path(
421,385✔
611
  int32_t target_cell, int32_t map, int32_t target_offset)
612
{
613
  auto& root_univ = *model::universes[model::root_universe];
421,385✔
614
  return distribcell_path_inner(target_cell, map, target_offset, root_univ, 0);
421,385✔
615
}
616

617
//==============================================================================
618

619
int maximum_levels(int32_t univ)
424,926✔
620
{
621

622
  const auto level_count = model::universe_level_counts.find(univ);
424,926✔
623
  if (level_count != model::universe_level_counts.end()) {
424,926✔
624
    return level_count->second;
414,951✔
625
  }
626

627
  int levels_below {0};
9,975✔
628

629
  for (int32_t cell_indx : model::universes[univ]->cells_) {
26,291✔
630
    Cell& c = *model::cells[cell_indx];
16,316✔
631
    if (c.type_ == Fill::UNIVERSE) {
16,316✔
632
      int32_t next_univ = c.fill_;
2,314✔
633
      levels_below = std::max(levels_below, maximum_levels(next_univ));
2,314✔
634
    } else if (c.type_ == Fill::LATTICE) {
14,002✔
635
      Lattice& lat = *model::lattices[c.fill_];
853✔
636
      for (auto it = lat.begin(); it != lat.end(); ++it) {
419,928✔
637
        int32_t next_univ = *it;
419,075✔
638
        levels_below = std::max(levels_below, maximum_levels(next_univ));
419,075✔
639
      }
640
    }
641
  }
642

643
  ++levels_below;
9,975✔
644
  model::universe_level_counts[univ] = levels_below;
9,975✔
645
  return levels_below;
9,975✔
646
}
647

UNCOV
648
bool is_root_universe(int32_t univ_id)
×
649
{
UNCOV
650
  return model::universe_map[univ_id] == model::root_universe;
×
651
}
652

653
//==============================================================================
654

655
void free_memory_geometry()
3,594✔
656
{
657
  model::cells.clear();
3,594✔
658
  model::cell_map.clear();
3,594✔
659

660
  model::universes.clear();
3,594✔
661
  model::universe_map.clear();
3,594✔
662

663
  model::lattices.clear();
3,594✔
664
  model::lattice_map.clear();
3,594✔
665

666
  model::overlap_check_count.clear();
3,594✔
667
}
3,594✔
668

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

© 2026 Coveralls, Inc