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

openmc-dev / openmc / 15917095934

27 Jun 2025 02:57AM UTC coverage: 85.261% (+0.009%) from 85.252%
15917095934

Pull #3424

github

web-flow
Merge 2538b8bfc into 25d64c9b2
Pull Request #3424: Fixed a bug in distribcell offsets logic

27 of 28 new or added lines in 1 file covered. (96.43%)

16 existing lines in 3 files now uncovered.

52622 of 61719 relevant lines covered (85.26%)

37107297.8 hits per line

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

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

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

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

11
#include "openmc/cell.h"
12
#include "openmc/constants.h"
13
#include "openmc/container_util.h"
14
#include "openmc/dagmc.h"
15
#include "openmc/error.h"
16
#include "openmc/file_utils.h"
17
#include "openmc/geometry.h"
18
#include "openmc/lattice.h"
19
#include "openmc/material.h"
20
#include "openmc/settings.h"
21
#include "openmc/surface.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, std::unordered_map<int32_t, int32_t>>
30
  universe_cell_counts;
31
std::unordered_map<int32_t, int32_t> universe_level_counts;
32
} // namespace model
33

34
// adds the cell counts of universe b to universe a
35
void update_universe_cell_count(int32_t a, int32_t b)
887,558✔
36
{
37
  auto& universe_a_counts = model::universe_cell_counts[a];
887,558✔
38
  const auto& universe_b_counts = model::universe_cell_counts[b];
887,558✔
39
  for (const auto& it : universe_b_counts) {
2,495,177✔
40
    universe_a_counts[it.first] += it.second;
1,607,619✔
41
  }
42
}
887,558✔
43

44
void read_geometry_xml()
1,348✔
45
{
46
  // Display output message
47
  write_message("Reading geometry XML file...", 5);
1,348✔
48

49
  // Check if geometry.xml exists
50
  std::string filename = settings::path_input + "geometry.xml";
1,348✔
51
  if (!file_exists(filename)) {
1,348✔
52
    fatal_error("Geometry XML file '" + filename + "' does not exist!");
×
53
  }
54

55
  // Parse settings.xml file
56
  pugi::xml_document doc;
1,348✔
57
  auto result = doc.load_file(filename.c_str());
1,348✔
58
  if (!result) {
1,348✔
59
    fatal_error("Error processing geometry.xml file.");
×
60
  }
61

62
  // Get root element
63
  pugi::xml_node root = doc.document_element();
1,348✔
64

65
  read_geometry_xml(root);
1,348✔
66
}
1,348✔
67

68
void read_geometry_xml(pugi::xml_node root)
6,725✔
69
{
70
  // Read surfaces, cells, lattice
71
  read_surfaces(root);
6,725✔
72
  read_cells(root);
6,725✔
73
  read_lattices(root);
6,723✔
74

75
  // Check to make sure a boundary condition was applied to at least one
76
  // surface
77
  bool boundary_exists = false;
6,723✔
78
  for (const auto& surf : model::surfaces) {
17,008✔
79
    if (surf->bc_) {
16,975✔
80
      boundary_exists = true;
6,690✔
81
      break;
6,690✔
82
    }
83
  }
84

85
  if (settings::run_mode != RunMode::PLOTTING &&
6,723✔
86
      settings::run_mode != RunMode::VOLUME && !boundary_exists) {
6,447✔
87
    fatal_error("No boundary conditions were applied to any surfaces!");
×
88
  }
89

90
  // Allocate universes, universe cell arrays, and assign base universe
91
  model::root_universe = find_root_universe();
6,723✔
92

93
  // if the root universe is DAGMC geometry, make sure the model is well-formed
94
  check_dagmc_root_univ();
6,723✔
95
}
6,723✔
96

97
//==============================================================================
98

99
void adjust_indices()
6,725✔
100
{
101
  // Adjust material/fill idices.
102
  for (auto& c : model::cells) {
38,184✔
103
    if (c->fill_ != C_NONE) {
31,459✔
104
      int32_t id = c->fill_;
6,792✔
105
      auto search_univ = model::universe_map.find(id);
6,792✔
106
      auto search_lat = model::lattice_map.find(id);
6,792✔
107
      if (search_univ != model::universe_map.end()) {
6,792✔
108
        c->type_ = Fill::UNIVERSE;
4,998✔
109
        c->fill_ = search_univ->second;
4,998✔
110
      } else if (search_lat != model::lattice_map.end()) {
1,794✔
111
        c->type_ = Fill::LATTICE;
1,794✔
112
        c->fill_ = search_lat->second;
1,794✔
113
      } else {
114
        fatal_error(fmt::format("Specified fill {} on cell {} is neither a "
×
115
                                "universe nor a lattice.",
116
          id, c->id_));
×
117
      }
118
    } else {
119
      c->type_ = Fill::MATERIAL;
24,667✔
120
      for (auto& mat_id : c->material_) {
50,895✔
121
        if (mat_id != MATERIAL_VOID) {
26,228✔
122
          auto search = model::material_map.find(mat_id);
18,005✔
123
          if (search == model::material_map.end()) {
18,005✔
124
            fatal_error(
×
125
              fmt::format("Could not find material {} specified on cell {}",
×
126
                mat_id, c->id_));
×
127
          }
128
          // Change from ID to index
129
          mat_id = search->second;
18,005✔
130
        }
131
      }
132
    }
133
  }
134

135
  // Change cell.universe values from IDs to indices.
136
  for (auto& c : model::cells) {
38,184✔
137
    auto search = model::universe_map.find(c->universe_);
31,459✔
138
    if (search != model::universe_map.end()) {
31,459✔
139
      c->universe_ = search->second;
31,459✔
140
    } else {
141
      fatal_error(fmt::format("Could not find universe {} specified on cell {}",
×
142
        c->universe_, c->id_));
×
143
    }
144
  }
145

146
  // Change all lattice universe values from IDs to indices.
147
  for (auto& l : model::lattices) {
8,486✔
148
    l->adjust_indices();
1,761✔
149
  }
150
}
6,725✔
151

152
//==============================================================================
153
//! Partition some universes with many z-planes for faster find_cell searches.
154

155
void partition_universes()
6,725✔
156
{
157
  // Iterate over universes with more than 10 cells.  (Fewer than 10 is likely
158
  // not worth partitioning.)
159
  for (const auto& univ : model::universes) {
24,691✔
160
    if (univ->cells_.size() > 10) {
17,966✔
161
      // Collect the set of surfaces in this universe.
162
      std::unordered_set<int32_t> surf_inds;
182✔
163
      for (auto i_cell : univ->cells_) {
3,064✔
164
        for (auto token : model::cells[i_cell]->surfaces()) {
10,920✔
165
          surf_inds.insert(std::abs(token) - 1);
8,038✔
166
        }
2,882✔
167
      }
168

169
      // Partition the universe if there are more than 5 z-planes.  (Fewer than
170
      // 5 is likely not worth it.)
171
      int n_zplanes = 0;
182✔
172
      for (auto i_surf : surf_inds) {
2,342✔
173
        if (dynamic_cast<const SurfaceZPlane*>(model::surfaces[i_surf].get())) {
2,256✔
174
          ++n_zplanes;
640✔
175
          if (n_zplanes > 5) {
640✔
176
            univ->partitioner_ = make_unique<UniversePartitioner>(*univ);
96✔
177
            break;
96✔
178
          }
179
        }
180
      }
181
    }
182✔
182
  }
183
}
6,725✔
184

185
//==============================================================================
186

187
void assign_temperatures()
6,725✔
188
{
189
  for (auto& c : model::cells) {
38,184✔
190
    // Ignore non-material cells and cells with defined temperature.
191
    if (c->material_.size() == 0)
31,459✔
192
      continue;
6,792✔
193
    if (c->sqrtkT_.size() > 0)
24,667✔
194
      continue;
441✔
195

196
    c->sqrtkT_.reserve(c->material_.size());
24,226✔
197
    for (auto i_mat : c->material_) {
50,004✔
198
      if (i_mat == MATERIAL_VOID) {
25,778✔
199
        // Set void region to 0K.
200
        c->sqrtkT_.push_back(0);
8,223✔
201
      } else {
202
        const auto& mat {model::materials[i_mat]};
17,555✔
203
        c->sqrtkT_.push_back(std::sqrt(K_BOLTZMANN * mat->temperature()));
17,555✔
204
      }
205
    }
206
  }
207
}
6,725✔
208

209
//==============================================================================
210

211
void get_temperatures(
6,449✔
212
  vector<vector<double>>& nuc_temps, vector<vector<double>>& thermal_temps)
213
{
214
  for (const auto& cell : model::cells) {
37,335✔
215
    // Skip non-material cells.
216
    if (cell->fill_ != C_NONE)
30,886✔
217
      continue;
6,779✔
218

219
    for (int j = 0; j < cell->material_.size(); ++j) {
49,775✔
220
      // Skip void materials
221
      int i_material = cell->material_[j];
25,668✔
222
      if (i_material == MATERIAL_VOID)
25,668✔
223
        continue;
8,107✔
224

225
      // Get temperature(s) of cell (rounding to nearest integer)
226
      vector<double> cell_temps;
17,561✔
227
      if (cell->sqrtkT_.size() == 1) {
17,561✔
228
        double sqrtkT = cell->sqrtkT_[0];
15,785✔
229
        cell_temps.push_back(sqrtkT * sqrtkT / K_BOLTZMANN);
15,785✔
230
      } else if (cell->sqrtkT_.size() == cell->material_.size()) {
1,776✔
231
        double sqrtkT = cell->sqrtkT_[j];
1,760✔
232
        cell_temps.push_back(sqrtkT * sqrtkT / K_BOLTZMANN);
1,760✔
233
      } else {
234
        for (double sqrtkT : cell->sqrtkT_)
80✔
235
          cell_temps.push_back(sqrtkT * sqrtkT / K_BOLTZMANN);
64✔
236
      }
237

238
      const auto& mat {model::materials[i_material]};
17,561✔
239
      for (const auto& i_nuc : mat->nuclide_) {
83,875✔
240
        for (double temperature : cell_temps) {
132,676✔
241
          // Add temperature if it hasn't already been added
242
          if (!contains(nuc_temps[i_nuc], temperature))
66,362✔
243
            nuc_temps[i_nuc].push_back(temperature);
26,773✔
244
        }
245
      }
246

247
      for (const auto& table : mat->thermal_tables_) {
20,682✔
248
        // Get index in data::thermal_scatt array
249
        int i_sab = table.index_table;
3,121✔
250

251
        for (double temperature : cell_temps) {
6,242✔
252
          // Add temperature if it hasn't already been added
253
          if (!contains(thermal_temps[i_sab], temperature))
3,121✔
254
            thermal_temps[i_sab].push_back(temperature);
1,078✔
255
        }
256
      }
257
    }
17,561✔
258
  }
259
}
6,449✔
260

261
//==============================================================================
262

263
void finalize_geometry()
6,725✔
264
{
265
  // Perform some final operations to set up the geometry
266
  adjust_indices();
6,725✔
267
  count_cell_instances(model::root_universe);
6,725✔
268
  partition_universes();
6,725✔
269

270
  // Assign temperatures to cells that don't have temperatures already assigned
271
  assign_temperatures();
6,725✔
272

273
  // Determine number of nested coordinate levels in the geometry
274
  model::n_coord_levels = maximum_levels(model::root_universe);
6,725✔
275
}
6,725✔
276

277
//==============================================================================
278

279
int32_t find_root_universe()
6,725✔
280
{
281
  // Find all the universes listed as a cell fill.
282
  std::unordered_set<int32_t> fill_univ_ids;
6,725✔
283
  for (const auto& c : model::cells) {
38,184✔
284
    fill_univ_ids.insert(c->fill_);
31,459✔
285
  }
286

287
  // Find all the universes contained in a lattice.
288
  for (const auto& lat : model::lattices) {
8,486✔
289
    for (auto it = lat->begin(); it != lat->end(); ++it) {
884,156✔
290
      fill_univ_ids.insert(*it);
882,395✔
291
    }
292
    if (lat->outer_ != NO_OUTER_UNIVERSE) {
1,761✔
293
      fill_univ_ids.insert(lat->outer_);
385✔
294
    }
295
  }
296

297
  // Figure out which universe is not in the set.  This is the root universe.
298
  bool root_found {false};
6,725✔
299
  int32_t root_univ;
300
  for (int32_t i = 0; i < model::universes.size(); i++) {
24,691✔
301
    auto search = fill_univ_ids.find(model::universes[i]->id_);
17,966✔
302
    if (search == fill_univ_ids.end()) {
17,966✔
303
      if (root_found) {
6,725✔
304
        fatal_error("Two or more universes are not used as fill universes, so "
×
305
                    "it is not possible to distinguish which one is the root "
306
                    "universe.");
307
      } else {
308
        root_found = true;
6,725✔
309
        root_univ = i;
6,725✔
310
      }
311
    }
312
  }
313
  if (!root_found)
6,725✔
314
    fatal_error("Could not find a root universe.  Make sure "
×
315
                "there are no circular dependencies in the geometry.");
316

317
  return root_univ;
6,725✔
318
}
6,725✔
319

320
//==============================================================================
321

322
void prepare_distribcell(const std::vector<int32_t>* user_distribcells)
6,721✔
323
{
324
  write_message("Preparing distributed cell instances...", 5);
6,721✔
325

326
  std::unordered_set<int32_t> distribcells;
6,721✔
327

328
  // start with any cells manually specified via the C++ API
329
  if (user_distribcells) {
6,721✔
330
    distribcells.insert(user_distribcells->begin(), user_distribcells->end());
16✔
331
  }
332

333
  // Find all cells listed in a DistribcellFilter or CellInstanceFilter
334
  for (auto& filt : model::tally_filters) {
14,448✔
335
    auto* distrib_filt = dynamic_cast<DistribcellFilter*>(filt.get());
7,727✔
336
    auto* cell_inst_filt = dynamic_cast<CellInstanceFilter*>(filt.get());
7,727✔
337
    if (distrib_filt) {
7,727✔
338
      distribcells.insert(distrib_filt->cell());
177✔
339
    }
340
    if (cell_inst_filt) {
7,727✔
341
      const auto& filter_cells = cell_inst_filt->cells();
34✔
342
      distribcells.insert(filter_cells.begin(), filter_cells.end());
34✔
343
    }
344
  }
345

346
  // By default, add material cells to the list of distributed cells
347
  if (settings::material_cell_offsets) {
6,721✔
348
    for (int64_t i = 0; i < model::cells.size(); ++i) {
38,232✔
349
      if (model::cells[i]->type_ == Fill::MATERIAL)
31,511✔
350
        distribcells.insert(i);
24,687✔
351
    }
352
  }
353

354
  // Make sure that the number of materials/temperatures matches the number of
355
  // cell instances.
356
  for (int i = 0; i < model::cells.size(); i++) {
38,232✔
357
    Cell& c {*model::cells[i]};
31,511✔
358

359
    if (c.material_.size() > 1) {
31,511✔
360
      if (c.material_.size() != c.n_instances_) {
227✔
361
        fatal_error(fmt::format(
×
362
          "Cell {} was specified with {} materials but has {} distributed "
363
          "instances. The number of materials must equal one or the number "
364
          "of instances.",
365
          c.id_, c.material_.size(), c.n_instances_));
×
366
      }
367
    }
368

369
    if (c.sqrtkT_.size() > 1) {
31,511✔
370
      if (c.sqrtkT_.size() != c.n_instances_) {
240✔
371
        fatal_error(fmt::format(
×
372
          "Cell {} was specified with {} temperatures but has {} distributed "
373
          "instances. The number of temperatures must equal one or the number "
374
          "of instances.",
375
          c.id_, c.sqrtkT_.size(), c.n_instances_));
×
376
      }
377
    }
378
  }
379

380
  // Search through universes for material cells and assign each one a
381
  // distribcell array index according to the containing universe.
382
  vector<int32_t> target_univ_ids;
6,721✔
383
  for (const auto& u : model::universes) {
24,715✔
384
    for (auto idx : u->cells_) {
49,505✔
385
      if (distribcells.find(idx) != distribcells.end()) {
31,511✔
386
        if (std::find(target_univ_ids.begin(), target_univ_ids.end(), u->id_) ==
24,767✔
387
            target_univ_ids.end()) {
49,534✔
388
          target_univ_ids.push_back(u->id_);
15,434✔
389
        }
390
        model::cells[idx]->distribcell_index_ =
24,767✔
391
          std::find(target_univ_ids.begin(), target_univ_ids.end(), u->id_) -
24,767✔
392
          target_univ_ids.begin();
49,534✔
393
      }
394
    }
395
  }
396

397
  // Allocate the cell and lattice offset tables.
398
  int n_maps = target_univ_ids.size();
6,721✔
399
  for (auto& c : model::cells) {
38,232✔
400
    if (c->type_ != Fill::MATERIAL) {
31,511✔
401
      c->offset_.resize(n_maps, C_NONE);
6,824✔
402
    }
403
  }
404
  for (auto& lat : model::lattices) {
8,498✔
405
    lat->allocate_offset_table(n_maps);
1,777✔
406
  }
407

408
// Fill the cell and lattice offset tables.
409
#pragma omp parallel for
3,742✔
410
  for (int map = 0; map < target_univ_ids.size(); map++) {
7,157✔
411
    auto target_univ_id = target_univ_ids[map];
4,178✔
412
    std::unordered_map<int32_t, int32_t> univ_count_memo;
4,178✔
413
    for (const auto& univ : model::universes) {
21,290✔
414
      int32_t offset = 0;
17,112✔
415
      for (int32_t cell_indx : univ->cells_) {
79,758✔
416
        Cell& c = *model::cells[cell_indx];
62,646✔
417

418
        if (c.type_ == Fill::UNIVERSE) {
62,646✔
419
          c.offset_[map] = offset;
35,866✔
420
          int32_t search_univ = c.fill_;
35,866✔
421
          offset += count_universe_instances(
35,866✔
422
            search_univ, target_univ_id, univ_count_memo);
423

424
        } else if (c.type_ == Fill::LATTICE) {
26,780✔
425
          c.offset_[map] = offset;
2,845✔
426
          Lattice& lat = *model::lattices[c.fill_];
2,845✔
427
          offset +=
2,845✔
428
            lat.fill_offset_table(0, target_univ_id, map, univ_count_memo);
2,845✔
429
        }
430
      }
431
    }
432
  }
4,178✔
433

434
  // check distinct distribcell paths from contiguous cell instances
435
  for (const auto& u : model::universes) {
24,715✔
436
    for (auto idx : u->cells_) {
49,505✔
437
      if (distribcells.find(idx) != distribcells.end()) {
31,511✔
438
        int32_t map =
439
          std::find(target_univ_ids.begin(), target_univ_ids.end(), u->id_) -
24,767✔
440
          target_univ_ids.begin();
24,767✔
441
        Cell& c = *model::cells[idx];
24,767✔
442
        std::set<std::string> paths;
24,767✔
443
        for (int32_t i = 0; i < c.n_instances_; i++) {
34,963,372✔
444
          auto path = distribcell_path(idx, map, i);
34,938,605✔
445
          if (paths.find(path) != paths.end()) {
34,938,605✔
NEW
446
            fatal_error(fmt::format(
×
447
              "Two or more cell instances have the same path {}", path));
448
          } else {
449
            paths.insert(path);
34,938,605✔
450
          }
451
        }
34,938,605✔
452
      }
24,767✔
453
    }
454
  }
455
}
6,721✔
456

457
//==============================================================================
458

459
void count_cell_instances(int32_t univ_indx)
894,283✔
460
{
461
  const auto univ_counts = model::universe_cell_counts.find(univ_indx);
894,283✔
462
  if (univ_counts != model::universe_cell_counts.end()) {
894,283✔
463
    for (const auto& it : univ_counts->second) {
2,457,413✔
464
      model::cells[it.first]->n_instances_ += it.second;
1,580,928✔
465
    }
466
  } else {
467
    for (int32_t cell_indx : model::universes[univ_indx]->cells_) {
49,081✔
468
      Cell& c = *model::cells[cell_indx];
31,283✔
469
      ++c.n_instances_;
31,283✔
470
      model::universe_cell_counts[univ_indx][cell_indx] += 1;
31,283✔
471

472
      if (c.type_ == Fill::UNIVERSE) {
31,283✔
473
        // This cell contains another universe.  Recurse into that universe.
474
        count_cell_instances(c.fill_);
4,998✔
475
        update_universe_cell_count(univ_indx, c.fill_);
4,998✔
476
      } else if (c.type_ == Fill::LATTICE) {
26,285✔
477
        // This cell contains a lattice.  Recurse into the lattice universes.
478
        Lattice& lat = *model::lattices[c.fill_];
1,794✔
479
        for (auto it = lat.begin(); it != lat.end(); ++it) {
884,354✔
480
          count_cell_instances(*it);
882,560✔
481
          update_universe_cell_count(univ_indx, *it);
882,560✔
482
        }
483
      }
484
    }
485
  }
486
}
894,283✔
487

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

490
int count_universe_instances(int32_t search_univ, int32_t target_univ_id,
11,767,233✔
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) {
11,767,233✔
495
    return 1;
1,600,034✔
496
  }
497

498
  // If we have already counted the number of instances, reuse that value.
499
  auto search = univ_count_memo.find(search_univ);
10,167,199✔
500
  if (search != univ_count_memo.end()) {
10,167,199✔
501
    return search->second;
4,148,276✔
502
  }
503

504
  int count {0};
6,018,923✔
505
  for (int32_t cell_indx : model::universes[search_univ]->cells_) {
12,123,274✔
506
    Cell& c = *model::cells[cell_indx];
6,104,351✔
507

508
    if (c.type_ == Fill::UNIVERSE) {
6,104,351✔
509
      int32_t next_univ = c.fill_;
77,286✔
510
      count +=
77,286✔
511
        count_universe_instances(next_univ, target_univ_id, univ_count_memo);
77,286✔
512

513
    } else if (c.type_ == Fill::LATTICE) {
6,027,065✔
514
      Lattice& lat = *model::lattices[c.fill_];
3,458✔
515
      for (auto it = lat.begin(); it != lat.end(); ++it) {
2,452,834✔
516
        int32_t next_univ = *it;
2,449,376✔
517
        count +=
2,449,376✔
518
          count_universe_instances(next_univ, target_univ_id, univ_count_memo);
2,449,376✔
519
      }
520
    }
521
  }
522

523
  // Remember the number of instances in this universe.
524
  univ_count_memo[search_univ] = count;
6,018,923✔
525

526
  return count;
6,018,923✔
527
}
528

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

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

536
  path << "u" << search_univ.id_ << "->";
107,175,124✔
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_) {
591,073,918✔
541
    if ((cell_indx == target_cell) && (offset == target_offset)) {
519,764,402✔
542
      Cell& c = *model::cells[cell_indx];
35,865,608✔
543
      path << "c" << c.id_;
35,865,608✔
544
      return path.str();
71,731,216✔
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()};
71,309,516✔
553
  for (; cell_it != search_univ.cells_.crend(); ++cell_it) {
431,240,488✔
554
    Cell& c = *model::cells[*cell_it];
431,240,488✔
555

556
    // Material cells don't contain other cells so ignore them.
557
    if (c.type_ != Fill::MATERIAL) {
431,240,488✔
558
      int32_t temp_offset = offset + c.offset_[map];
88,838,843✔
559
      if (c.type_ == Fill::LATTICE) {
88,838,843✔
560
        Lattice& lat = *model::lattices[c.fill_];
87,852,016✔
561
        int32_t indx = lat.universes_.size() * map + lat.begin().indx_;
87,852,016✔
562
        temp_offset += lat.offsets_[indx];
87,852,016✔
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)
88,838,843✔
568
        break;
71,309,516✔
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()) {
71,309,516✔
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];
71,309,516✔
583
  path << "c" << c.id_ << "->";
71,309,516✔
584

585
  if (c.type_ == Fill::UNIVERSE) {
71,309,516✔
586
    // Recurse into the fill cell.
587
    offset += c.offset_[map];
894,283✔
588
    path << distribcell_path_inner(
1,788,566✔
589
      target_cell, map, target_offset, *model::universes[c.fill_], offset);
1,788,566✔
590
    return path.str();
894,283✔
591
  } else {
592
    // Recurse into the lattice cell.
593
    Lattice& lat = *model::lattices[c.fill_];
70,415,233✔
594
    path << "l" << lat.id_;
70,415,233✔
595
    for (ReverseLatticeIter it = lat.rbegin(); it != lat.rend(); ++it) {
2,147,483,647✔
596
      int32_t indx = lat.universes_.size() * map + it.indx_;
2,147,483,647✔
597
      int32_t temp_offset = offset + lat.offsets_[indx] + c.offset_[map];
2,147,483,647✔
598
      if (temp_offset <= target_offset) {
2,147,483,647✔
599
        offset = temp_offset;
70,415,233✔
600
        path << "(" << lat.index_to_string(it.indx_) << ")->";
70,415,233✔
601
        path << distribcell_path_inner(
140,830,466✔
602
          target_cell, map, target_offset, *model::universes[*it], offset);
140,830,466✔
603
        return path.str();
140,830,466✔
604
      }
605
    }
606
    throw std::runtime_error {"Error determining distribcell path."};
×
607
  }
608
}
107,175,124✔
609

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

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

619
int maximum_levels(int32_t univ)
894,283✔
620
{
621

622
  const auto level_count = model::universe_level_counts.find(univ);
894,283✔
623
  if (level_count != model::universe_level_counts.end()) {
894,283✔
624
    return level_count->second;
876,485✔
625
  }
626

627
  int levels_below {0};
17,798✔
628

629
  for (int32_t cell_indx : model::universes[univ]->cells_) {
49,081✔
630
    Cell& c = *model::cells[cell_indx];
31,283✔
631
    if (c.type_ == Fill::UNIVERSE) {
31,283✔
632
      int32_t next_univ = c.fill_;
4,998✔
633
      levels_below = std::max(levels_below, maximum_levels(next_univ));
4,998✔
634
    } else if (c.type_ == Fill::LATTICE) {
26,285✔
635
      Lattice& lat = *model::lattices[c.fill_];
1,794✔
636
      for (auto it = lat.begin(); it != lat.end(); ++it) {
884,354✔
637
        int32_t next_univ = *it;
882,560✔
638
        levels_below = std::max(levels_below, maximum_levels(next_univ));
882,560✔
639
      }
640
    }
641
  }
642

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

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

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

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

660
  model::universes.clear();
6,830✔
661
  model::universe_map.clear();
6,830✔
662

663
  model::lattices.clear();
6,830✔
664
  model::lattice_map.clear();
6,830✔
665

666
  model::overlap_check_count.clear();
6,830✔
667
}
6,830✔
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