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

openmc-dev / openmc / 31265430262

08 Aug 2026 03:49PM UTC coverage: 81.463% (+0.03%) from 81.429%
31265430262

Pull #3971

github

web-flow
Merge ca0a9521d into 83a5a51ae
Pull Request #3971: Delta tracking

18857 of 27294 branches covered (69.09%)

Branch coverage included in aggregate %.

601 of 645 new or added lines in 20 files covered. (93.18%)

543 existing lines in 18 files now uncovered.

60850 of 70550 relevant lines covered (86.25%)

50508854.62 hits per line

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

83.3
/src/geometry.cpp
1
#include "openmc/geometry.h"
2

3
#include <fmt/core.h>
4
#include <fmt/ostream.h>
5

6
#include "openmc/array.h"
7
#include "openmc/cell.h"
8
#include "openmc/constants.h"
9
#include "openmc/error.h"
10
#include "openmc/lattice.h"
11
#include "openmc/settings.h"
12
#include "openmc/simulation.h"
13
#include "openmc/string_utils.h"
14
#include "openmc/surface.h"
15

16
namespace openmc {
17

18
//==============================================================================
19
// Global variables
20
//==============================================================================
21

22
namespace model {
23

24
int root_universe {-1};
25
int n_coord_levels;
26

27
vector<int> boundary_surfaces;
28

29
vector<int64_t> overlap_check_count;
30

31
vector<OverlapKey> overlap_keys;
32
std::unordered_map<OverlapKey, int, OverlapKeyHash> overlap_key_index;
33

34
} // namespace model
35

36
//==============================================================================
37
// Non-member functions
38
//==============================================================================
39

40
int check_cell_overlap(GeometryState& p, bool error)
1,442,100 ✔
41
{
42
  int n_coord = p.n_coord();
1,442,100 ✔
43

44
  // If no overlap found, return a nonphysical index
45
  int overlap_index = -1;
1,442,100 ✔
46

47
  // Loop through each coordinate level
48
  for (int j = 0; j < n_coord; j++) {
2,884,200 ✔
49
    Universe& univ = *model::universes[p.coord(j).universe()];
1,442,100 ✔
50

51
    // Loop through each cell on this level
52
    for (auto index_cell : univ.cells_) {
4,968,568 ✔
53
      Cell& c = *model::cells[index_cell];
3,920,488 ✔
54
      if (c.contains(p.coord(j).r(), p.coord(j).u(), p.surface())) {
3,920,488 ✔
55
#pragma omp atomic
759,036 ✔
56
        ++model::overlap_check_count[index_cell];
1,391,566 ✔
57
        if (index_cell != p.coord(j).cell()) {
1,391,566 ✔
58
          if (error) {
394,020 !
59
            fatal_error(
×
60
              fmt::format("Overlapping cells detected: {}, {} on universe {}",
×
61
                c.id_, model::cells[p.coord(j).cell()]->id_, univ.id_));
×
62
          }
63

64
          // With no fatal error (plotter is calling), now adds overlaps and
65
          // ensures order does not matter when making overlap key
66
          int cell_a = model::cells[index_cell]->id_;
394,020 !
67
          int cell_b = model::cells[p.coord(j).cell()]->id_;
394,020 !
68
          int a = std::min(cell_a, cell_b);
394,020 !
69
          int b = std::max(cell_a, cell_b);
394,020 !
70
          OverlapKey key {univ.id_, a, b};
394,020 ✔
71
#pragma omp critical(overlap_key_update)
429,840 ✔
72
          {
394,020 ✔
73
            auto it = model::overlap_key_index.find(key);
394,020 ✔
74
            if (it != model::overlap_key_index.end()) {
394,020 ✔
75
              overlap_index = it->second; // already exists, reuse index
393,965 ✔
76
            } else {
77
              int idx = int(model::overlap_keys.size());
55 ✔
78
              model::overlap_keys.push_back(key);
55 ✔
79
              model::overlap_key_index[key] = idx;
55 ✔
80
              overlap_index = idx;
55 ✔
81
            }
82
          }
83
          break;
394,020 ✔
84
        }
85
      }
86
    }
87
  }
88
  return overlap_index;
1,442,100 ✔
89
}
90

91
//==============================================================================
92

93
int cell_instance_at_level(const GeometryState& p, int level)
2,147,483,647 ✔
94
{
95
  // throw error if the requested level is too deep for the geometry
96
  if (level > model::n_coord_levels) {
2,147,483,647 !
97
    fatal_error(fmt::format("Cell instance at level {} requested, but only {} "
×
98
                            "levels exist in the geometry.",
99
      level, p.n_coord()));
100
  }
101

102
  // determine the cell instance
103
  Cell& c {*model::cells[p.coord(level).cell()]};
2,147,483,647 !
104

105
  // quick exit if this cell doesn't have distribcell instances
106
  if (c.distribcell_index_ == C_NONE)
2,147,483,647 !
107
    return C_NONE;
108

109
  // compute the cell's instance
110
  int instance = 0;
111
  for (int i = 0; i < level; i++) {
2,147,483,647 ✔
112
    const auto& c_i {*model::cells[p.coord(i).cell()]};
2,147,483,647 ✔
113
    if (c_i.type_ == Fill::UNIVERSE) {
2,147,483,647 ✔
114
      instance += c_i.offset_[c.distribcell_index_];
1,892,151,861 ✔
115
    } else if (c_i.type_ == Fill::LATTICE) {
1,440,057,649 !
116
      instance += c_i.offset_[c.distribcell_index_];
1,440,057,649 ✔
117
      auto& lat {*model::lattices[p.coord(i + 1).lattice()]};
1,440,057,649 ✔
118
      const auto& i_xyz {p.coord(i + 1).lattice_index()};
1,440,057,649 ✔
119
      if (lat.are_valid_indices(i_xyz)) {
1,440,057,649 ✔
120
        instance += lat.offset(c.distribcell_index_, i_xyz);
1,435,173,682 ✔
121
      }
122
    }
123
  }
124
  return instance;
125
}
126

127
//==============================================================================
128

129
bool find_cell_inner(
2,147,483,647 ✔
130
  GeometryState& p, const NeighborList* neighbor_list, bool verbose)
131
{
132
  // Find which cell of this universe the particle is in.  Use the neighbor list
133
  // to shorten the search if one was provided.
134
  bool found = false;
2,147,483,647 ✔
135
  int32_t i_cell = C_NONE;
2,147,483,647 ✔
136
  if (neighbor_list) {
2,147,483,647 ✔
137
    for (auto it = neighbor_list->cbegin(); it != neighbor_list->cend(); ++it) {
2,147,483,647 ✔
138
      i_cell = *it;
2,147,483,647 !
139

140
      // Make sure the search cell is in the same universe.
141
      int i_universe = p.lowest_coord().universe();
2,147,483,647 !
142
      if (model::cells[i_cell]->universe_ != i_universe)
2,147,483,647 !
143
        continue;
×
144

145
      // Check if this cell contains the particle.
146
      Position r {p.r_local()};
2,147,483,647 ✔
147
      Direction u {p.u_local()};
2,147,483,647 ✔
148
      auto surf = p.surface();
2,147,483,647 ✔
149
      if (model::cells[i_cell]->contains(r, u, surf)) {
2,147,483,647 ✔
150
        p.lowest_coord().cell() = i_cell;
2,147,483,647 ✔
151
        found = true;
2,147,483,647 ✔
152
        break;
2,147,483,647 ✔
153
      }
154
    }
155

156
    // If we're attempting a neighbor list search and fail, we
157
    // now know we should return false. This will trigger an
158
    // exhaustive search from neighbor_list_find_cell and make
159
    // the result from that be appended to the neighbor list.
160
    if (!found) {
2,147,483,647 ✔
161
      return found;
162
    }
163
  }
164

165
  // Check successively lower coordinate levels until finding material fill
166
  for (;; ++p.n_coord()) {
2,147,483,647 ✔
167
    // If we did not attempt to use neighbor lists, i_cell is still C_NONE.  In
168
    // that case, we should now do an exhaustive search to find the right value
169
    // of i_cell.
170
    //
171
    // Alternatively, neighbor list searches could have succeeded, but we found
172
    // that the fill of the neighbor cell was another universe. As such, in the
173
    // code below this conditional, we set i_cell back to C_NONE to indicate
174
    // that.
175
    if (i_cell == C_NONE) {
2,147,483,647 ✔
176
      int i_universe = p.lowest_coord().universe();
2,147,483,647 ✔
177
      const auto& univ {model::universes[i_universe]};
2,147,483,647 ✔
178
      found = univ->find_cell(p);
2,147,483,647 ✔
179
    }
180

181
    if (!found) {
2,147,483,647 ✔
182
      return found;
183
    }
184
    i_cell = p.lowest_coord().cell();
2,147,483,647 !
185

186
    // Announce the cell that the particle is entering.
187
    if (found && verbose) {
2,147,483,647 !
188
      auto msg = fmt::format("    Entering cell {}", model::cells[i_cell]->id_);
×
189
      write_message(msg, 1);
×
190
    }
×
191

192
    Cell& c {*model::cells[i_cell]};
2,147,483,647 ✔
193
    if (c.type_ == Fill::MATERIAL) {
2,147,483,647 ✔
194
      // Found a material cell which means this is the lowest coord level.
195

196
      p.cell_instance() = 0;
2,147,483,647 ✔
197
      // Find the distribcell instance number.
198
      if (c.distribcell_index_ >= 0) {
2,147,483,647 ✔
199
        p.cell_instance() = cell_instance_at_level(p, p.n_coord() - 1);
2,147,483,647 ✔
200
      }
201

202
      // Set the material, temperature and density multiplier.
203
      p.material_last() = p.material();
2,147,483,647 ✔
204
      p.material() = c.material(p.cell_instance());
2,147,483,647 ✔
205
      p.sqrtkT_last() = p.sqrtkT();
2,147,483,647 ✔
206
      p.sqrtkT() = c.sqrtkT(p.cell_instance());
2,147,483,647 ✔
207
      p.density_mult_last() = p.density_mult();
2,147,483,647 ✔
208
      p.density_mult() = c.density_mult(p.cell_instance());
2,147,483,647 ✔
209

210
      return true;
2,147,483,647 ✔
211

212
    } else if (c.type_ == Fill::UNIVERSE) {
1,283,906,795 ✔
213
      //========================================================================
214
      //! Found a lower universe, update this coord level then search the next.
215

216
      // Set the lower coordinate level universe.
217
      auto& coord {p.coord(p.n_coord())};
1,003,639,390 ✔
218
      coord.universe() = c.fill_;
1,003,639,390 ✔
219

220
      // Set the position and direction.
221
      coord.r() = p.r_local();
1,003,639,390 ✔
222
      coord.u() = p.u_local();
1,003,639,390 ✔
223

224
      // Apply translation.
225
      coord.r() -= c.translation_;
1,003,639,390 ✔
226

227
      // Apply rotation.
228
      if (!c.rotation_.empty()) {
1,003,639,390 ✔
229
        coord.rotate(c.rotation_);
66,157,597 ✔
230
      }
231

232
    } else if (c.type_ == Fill::LATTICE) {
280,267,405 !
233
      //========================================================================
234
      //! Found a lower lattice, update this coord level then search the next.
235

236
      Lattice& lat {*model::lattices[c.fill_]};
280,267,405 ✔
237

238
      // Set the position and direction.
239
      auto& coord {p.coord(p.n_coord())};
280,267,405 ✔
240
      coord.r() = p.r_local();
280,267,405 ✔
241
      coord.u() = p.u_local();
280,267,405 ✔
242

243
      // Apply translation.
244
      coord.r() -= c.translation_;
280,267,405 ✔
245

246
      // Apply rotation.
247
      if (!c.rotation_.empty()) {
280,267,405 ✔
248
        coord.rotate(c.rotation_);
358,336 ✔
249
      }
250

251
      // Determine lattice indices.
252
      auto& i_xyz {coord.lattice_index()};
280,267,405 ✔
253
      lat.get_indices(coord.r(), coord.u(), i_xyz);
280,267,405 ✔
254

255
      // Get local position in appropriate lattice cell
256
      coord.r() = lat.get_local_position(coord.r(), i_xyz);
280,267,405 ✔
257

258
      // Set lattice indices.
259
      coord.lattice() = c.fill_;
280,267,405 ✔
260

261
      // Set the lower coordinate level universe.
262
      if (lat.are_valid_indices(i_xyz)) {
280,267,405 ✔
263
        coord.universe() = lat[i_xyz];
275,383,438 ✔
264
      } else {
265
        if (lat.outer_ != NO_OUTER_UNIVERSE) {
4,883,967 !
266
          coord.universe() = lat.outer_;
4,883,967 ✔
267
        } else {
268
          p.mark_as_lost(fmt::format(
×
269
            "Particle {} left lattice {}, but it has no outer definition.",
270
            p.id(), lat.id_));
×
271
        }
272
      }
273
    }
274
    i_cell = C_NONE; // trip non-neighbor cell search at next iteration
1,283,906,795 ✔
275
    found = false;
1,283,906,795 ✔
276
  }
277

278
  return found;
279
}
280

281
//==============================================================================
282

283
bool neighbor_list_find_cell(GeometryState& p, bool verbose)
2,147,483,647 ✔
284
{
285

286
  // Reset all the deeper coordinate levels.
287
  for (int i = p.n_coord(); i < model::n_coord_levels; i++) {
2,147,483,647 ✔
288
    p.coord(i).reset();
1,931,741,108 ✔
289
  }
290

291
  // Get the cell this particle was in previously.
292
  auto coord_lvl = p.n_coord() - 1;
2,147,483,647 ✔
293
  auto i_cell = p.coord(coord_lvl).cell();
2,147,483,647 ✔
294
  Cell& c {*model::cells[i_cell]};
2,147,483,647 ✔
295

296
  // Search for the particle in that cell's neighbor list.  Return if we
297
  // found the particle.
298
  bool found = find_cell_inner(p, &c.neighbors_, verbose);
2,147,483,647 ✔
299
  if (found)
2,147,483,647 ✔
300
    return found;
301

302
  // The particle could not be found in the neighbor list.  Try searching all
303
  // cells in this universe, and update the neighbor list if we find a new
304
  // neighboring cell.
305
  found = find_cell_inner(p, nullptr, verbose);
1,602,568 ✔
306
  if (found)
1,602,568 ✔
307
    c.neighbors_.push_back(p.coord(coord_lvl).cell());
30,809 ✔
308
  return found;
309
}
310

311
void reconcile_cell_after_collision(GeometryState& p)
6,957,478 ✔
312
{
313
  // Find the first coordinate level whose current cell is inconsistent with
314
  // the particle's post-collision direction.
315
  int invalid_level = C_NONE;
6,957,478 ✔
316
  for (int level = 0; level < p.n_coord(); ++level) {
20,872,390 ✔
317
    const auto& coord {p.coord(level)};
13,914,923 !
318
    if (coord.cell() == C_NONE || !model::cells[coord.cell()]->contains(
13,914,923 !
319
                                    coord.r(), coord.u(), SURFACE_NONE)) {
13,914,923 ✔
320
      invalid_level = level;
321
      break;
322
    }
323
  }
324

325
  if (invalid_level == C_NONE)
6,957,478 ✔
326
    return;
327

328
  // Search from the first inconsistent level so that parent cells and
329
  // transformed lower universes are both handled correctly.
330
  if (p.coord(invalid_level).cell() != C_NONE) {
11 !
331
    p.n_coord() = invalid_level + 1;
11 ✔
332
    if (neighbor_list_find_cell(p))
11 !
333
      return;
334
  }
335

336
  // The current cell may not have a complete neighbor list yet. Fall back to
337
  // the normal exhaustive search used after a failed surface crossing.
UNCOV
338
  p.n_coord() = 1;
×
UNCOV
339
  if (!exhaustive_find_cell(p)) {
×
UNCOV
340
    p.mark_as_lost("Could not find particle after a collision near a surface.");
×
341
  }
342
}
343

344
bool exhaustive_find_cell(GeometryState& p, bool verbose)
1,416,613,169 ✔
345
{
346
  int i_universe = p.lowest_coord().universe();
1,416,613,169 ✔
347
  if (i_universe == C_NONE) {
1,416,613,169 ✔
348
    p.coord(0).universe() = model::root_universe;
380,759,328 ✔
349
    p.n_coord() = 1;
380,759,328 ✔
350
    i_universe = model::root_universe;
380,759,328 ✔
351
  }
352
  // Reset all the deeper coordinate levels.
353
  for (int i = p.n_coord(); i < model::n_coord_levels; i++) {
1,620,021,070 ✔
354
    p.coord(i).reset();
203,407,901 ✔
355
  }
356
  return find_cell_inner(p, nullptr, verbose);
1,416,613,169 ✔
357
}
358

359
//==============================================================================
360

361
void cross_lattice(GeometryState& p, const BoundaryInfo& boundary, bool verbose)
813,765,120 ✔
362
{
363
  auto& coord {p.lowest_coord()};
813,765,120 !
364
  auto& lat {*model::lattices[coord.lattice()]};
813,765,120 !
365

366
  if (verbose) {
813,765,120 !
UNCOV
367
    write_message(
×
UNCOV
368
      fmt::format("    Crossing lattice {}. Current position ({},{},{}). r={}",
×
UNCOV
369
        lat.id_, coord.lattice_index()[0], coord.lattice_index()[1],
×
UNCOV
370
        coord.lattice_index()[2], p.r()),
×
371
      1);
372
  }
373

374
  // Set the lattice indices.
375
  coord.lattice_index()[0] += boundary.lattice_translation()[0];
813,765,120 ✔
376
  coord.lattice_index()[1] += boundary.lattice_translation()[1];
813,765,120 ✔
377
  coord.lattice_index()[2] += boundary.lattice_translation()[2];
813,765,120 ✔
378

379
  // Set the new coordinate position.
380
  const auto& upper_coord {p.coord(p.n_coord() - 2)};
813,765,120 ✔
381
  const auto& cell {model::cells[upper_coord.cell()]};
813,765,120 ✔
382
  Position r = upper_coord.r();
813,765,120 ✔
383
  r -= cell->translation_;
813,765,120 ✔
384
  if (!cell->rotation_.empty()) {
813,765,120 ✔
385
    r = r.rotate(cell->rotation_);
471,647 ✔
386
  }
387
  p.r_local() = lat.get_local_position(r, coord.lattice_index());
813,765,120 ✔
388

389
  if (!lat.are_valid_indices(coord.lattice_index())) {
813,765,120 ✔
390
    // The particle is outside the lattice.  Search for it from the base coords.
391
    p.n_coord() = 1;
3,714,425 ✔
392
    bool found = exhaustive_find_cell(p);
3,714,425 ✔
393

394
    if (!found) {
3,714,425 !
UNCOV
395
      p.mark_as_lost(fmt::format("Particle {} could not be located after "
×
396
                                 "crossing a boundary of lattice {}",
UNCOV
397
        p.id(), lat.id_));
×
398
    }
399

400
  } else {
401
    // Find cell in next lattice element.
402
    p.lowest_coord().universe() = lat[coord.lattice_index()];
810,050,695 ✔
403
    bool found = exhaustive_find_cell(p);
810,050,695 ✔
404

405
    if (!found) {
810,050,695 !
406
      // A particle crossing the corner of a lattice tile may not be found.  In
407
      // this case, search for it from the base coords.
UNCOV
408
      p.n_coord() = 1;
×
UNCOV
409
      bool found = exhaustive_find_cell(p);
×
UNCOV
410
      if (!found) {
×
UNCOV
411
        p.mark_as_lost(fmt::format("Particle {} could not be located after "
×
412
                                   "crossing a boundary of lattice {}",
UNCOV
413
          p.id(), lat.id_));
×
414
      }
415
    }
416
  }
417
}
813,765,120 ✔
418

419
//==============================================================================
420

421
BoundaryInfo distance_to_boundary(GeometryState& p)
2,147,483,647 ✔
422
{
423
  BoundaryInfo info;
2,147,483,647 ✔
424
  double d_lat = INFINITY;
2,147,483,647 ✔
425
  double d_surf = INFINITY;
2,147,483,647 ✔
426
  int32_t level_surf_cross;
2,147,483,647 ✔
427
  array<int, 3> level_lat_trans {};
2,147,483,647 ✔
428

429
  // Loop over each coordinate level.
430
  for (int i = 0; i < p.n_coord(); i++) {
2,147,483,647 ✔
431
    const auto& coord {p.coord(i)};
2,147,483,647 ✔
432
    const Position& r {coord.r()};
2,147,483,647 ✔
433
    const Direction& u {coord.u()};
2,147,483,647 ✔
434
    Cell& c {*model::cells[coord.cell()]};
2,147,483,647 ✔
435

436
    // Find the oncoming surface in this cell and the distance to it.
437
    auto surface_distance = c.distance(r, u, p.surface(), &p);
2,147,483,647 ✔
438
    d_surf = surface_distance.first;
2,147,483,647 ✔
439
    level_surf_cross = surface_distance.second;
2,147,483,647 ✔
440

441
    // Find the distance to the next lattice tile crossing.
442
    if (coord.lattice() != C_NONE) {
2,147,483,647 ✔
443
      auto& lat {*model::lattices[coord.lattice()]};
1,406,189,295 !
444
      // TODO: refactor so both lattice use the same position argument (which
445
      // also means the lat.type attribute can be removed)
446
      std::pair<double, array<int, 3>> lattice_distance;
1,406,189,295 ✔
447
      switch (lat.type_) {
1,406,189,295 !
448
      case LatticeType::rect:
1,320,685,998 ✔
449
        lattice_distance = lat.distance(r, u, coord.lattice_index());
1,320,685,998 ✔
450
        break;
1,320,685,998 ✔
451
      case LatticeType::hex:
85,503,297 ✔
452
        auto& cell_above {model::cells[p.coord(i - 1).cell()]};
85,503,297 ✔
453
        Position r_hex {p.coord(i - 1).r()};
85,503,297 ✔
454
        r_hex -= cell_above->translation_;
85,503,297 ✔
455
        if (coord.rotated()) {
85,503,297 ✔
456
          r_hex = r_hex.rotate(cell_above->rotation_);
701,954 ✔
457
        }
458
        r_hex.z = coord.r().z;
85,503,297 ✔
459
        lattice_distance = lat.distance(r_hex, u, coord.lattice_index());
85,503,297 ✔
460
        break;
85,503,297 ✔
461
      }
462
      d_lat = lattice_distance.first;
1,406,189,295 ✔
463
      level_lat_trans = lattice_distance.second;
1,406,189,295 ✔
464

465
      if (d_lat < 0) {
1,406,189,295 !
UNCOV
466
        p.mark_as_lost(fmt::format("Particle {} had a negative distance "
×
467
                                   "to a lattice boundary.",
UNCOV
468
          p.id()));
×
469
      }
470
    }
471

472
    // If the boundary on this coordinate level is coincident with a boundary on
473
    // a higher level then we need to make sure that the higher level boundary
474
    // is selected.  This logic must consider floating point precision.
475
    double& d = info.distance();
2,147,483,647 ✔
476
    if (d_surf < d_lat - FP_COINCIDENT) {
2,147,483,647 ✔
477
      if (d == INFINITY || (d - d_surf) / d >= FP_REL_PRECISION) {
2,147,483,647 ✔
478
        // Update closest distance
479
        d = d_surf;
2,147,483,647 ✔
480

481
        // If the cell is not simple, it is possible that both the negative and
482
        // positive half-space were given in the region specification. Thus, we
483
        // have to explicitly check which half-space the particle would be
484
        // traveling into if the surface is crossed
485
        if (c.is_simple() || d == INFTY) {
2,147,483,647 ✔
486
          info.surface() = level_surf_cross;
2,147,483,647 ✔
487
        } else {
488
          Position r_hit = r + d_surf * u;
186,569,834 ✔
489
          Surface& surf {*model::surfaces[std::abs(level_surf_cross) - 1]};
186,569,834 ✔
490
          Direction norm = surf.normal(r_hit);
186,569,834 ✔
491
          if (u.dot(norm) > 0) {
186,569,834 ✔
492
            info.surface() = std::abs(level_surf_cross);
176,702,200 ✔
493
          } else {
494
            info.surface() = -std::abs(level_surf_cross);
9,867,634 ✔
495
          }
496
        }
497

498
        info.lattice_translation()[0] = 0;
2,147,483,647 ✔
499
        info.lattice_translation()[1] = 0;
2,147,483,647 ✔
500
        info.lattice_translation()[2] = 0;
2,147,483,647 ✔
501
        info.coord_level() = i + 1;
2,147,483,647 ✔
502
      }
503
    } else {
504
      if (d == INFINITY || (d - d_lat) / d >= FP_REL_PRECISION) {
1,171,653,315 !
505
        d = d_lat;
962,488,240 ✔
506
        info.surface() = SURFACE_NONE;
962,488,240 ✔
507
        info.lattice_translation() = level_lat_trans;
962,488,240 ✔
508
        info.coord_level() = i + 1;
962,488,240 ✔
509
      }
510
    }
511
  }
512
  return info;
2,147,483,647 ✔
513
}
514

515
//==============================================================================
516

517
BoundaryInfo distance_to_external_boundary(GeometryState& p)
113,028,311 ✔
518
{
519
  BoundaryInfo info;
113,028,311 ✔
520

521
  info.distance() = INFTY;
113,028,311 ✔
522
  info.surface() = 0;
113,028,311 ✔
523
  info.coord_level() = 1;
113,028,311 ✔
524
  for (auto s_idx : model::boundary_surfaces) {
565,141,555 ✔
525
    const auto& s = model::surfaces[s_idx];
452,113,244 ✔
526
    double surf_dist = s->distance(p.r(), p.u(), false);
452,113,244 ✔
527
    if (surf_dist < info.distance()) {
452,113,244 ✔
528
      info.distance() = surf_dist;
168,532,496 ✔
529
      info.surface() = s_idx + 1;
168,532,496 ✔
530
      if (s->sense(p.r(), p.u())) {
168,532,496 ✔
531
        info.surface() *= -1;
83,164,906 ✔
532
      }
533
    }
534
  }
535

536
  return info;
113,028,311 ✔
537
}
538

539
//==============================================================================
540
// C API
541
//==============================================================================
542

543
extern "C" int openmc_find_cell(
600,308 ✔
544
  const double* xyz, int32_t* index, int32_t* instance)
545
{
546
  GeometryState geom_state;
600,308 ✔
547

548
  geom_state.r() = Position {xyz};
600,308 ✔
549
  geom_state.u() = {0.0, 0.0, 1.0};
600,308 ✔
550

551
  if (!exhaustive_find_cell(geom_state)) {
600,308 ✔
552
    set_errmsg(
11 ✔
553
      fmt::format("Could not find cell at position {}.", geom_state.r()));
11 ✔
554
    return OPENMC_E_GEOMETRY;
11 ✔
555
  }
556

557
  *index = geom_state.lowest_coord().cell();
600,297 ✔
558
  *instance = geom_state.cell_instance();
600,297 ✔
559
  return 0;
600,297 ✔
560
}
600,308 ✔
561

562
extern "C" int openmc_global_bounding_box(double* llc, double* urc)
11 ✔
563
{
564
  auto bbox = model::universes.at(model::root_universe)->bounding_box();
11 ✔
565

566
  // set lower left corner values
567
  llc[0] = bbox.min.x;
11 ✔
568
  llc[1] = bbox.min.y;
11 ✔
569
  llc[2] = bbox.min.z;
11 ✔
570

571
  // set upper right corner values
572
  urc[0] = bbox.max.x;
11 ✔
573
  urc[1] = bbox.max.y;
11 ✔
574
  urc[2] = bbox.max.z;
11 ✔
575

576
  return 0;
11 ✔
577
}
578

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