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

openmc-dev / openmc / 33522947239

01 Sep 2026 02:59PM UTC coverage: 81.422% (+0.09%) from 81.333%
33522947239

Pull #3944

github

web-flow
Merge 6616ec53c into c6f91874b
Pull Request #3944: DNP drift (regular mesh only)

19135 of 27694 branches covered (69.09%)

Branch coverage included in aggregate %.

716 of 821 new or added lines in 22 files covered. (87.21%)

698 existing lines in 16 files now uncovered.

61133 of 70889 relevant lines covered (86.24%)

50653245.55 hits per line

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

83.15
/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<int64_t> overlap_check_count;
28

29
vector<OverlapKey> overlap_keys;
30
std::unordered_map<OverlapKey, int, OverlapKeyHash> overlap_key_index;
31

32
} // namespace model
33

34
//==============================================================================
35
// Non-member functions
36
//==============================================================================
37

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

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

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

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

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

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

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

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

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

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

125
//==============================================================================
126

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

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

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

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

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

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

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

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

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

200
      // Set the material.
201
      p.material_last() = p.material();
2,147,483,647 ✔
202
      p.material() = c.material(p.cell_instance());
2,147,483,647 ✔
203

204
      // Set the temperature.
205
      p.sqrtkT_last() = p.sqrtkT();
2,147,483,647 ✔
206
      if (settings::temperature_field_on && p.tf_bin() != C_NONE) {
2,147,483,647 ✔
207
        p.sqrtkT() = simulation::temperature_field.get_sqrtkT(p.tf_bin());
2,266,231 ✔
208
      } else {
209
        p.sqrtkT() = c.sqrtkT(p.cell_instance());
2,147,483,647 ✔
210
      }
211

212
      // Set the density multiplier.
213
      p.density_mult_last() = p.density_mult();
2,147,483,647 ✔
214
      p.density_mult() = c.density_mult(p.cell_instance());
2,147,483,647 ✔
215

216
      return true;
2,147,483,647 ✔
217

218
    } else if (c.type_ == Fill::UNIVERSE) {
1,158,634,967 ✔
219
      //========================================================================
220
      //! Found a lower universe, update this coord level then search the next.
221

222
      // Set the lower coordinate level universe.
223
      auto& coord {p.coord(p.n_coord())};
1,003,639,390 ✔
224
      coord.universe() = c.fill_;
1,003,639,390 ✔
225

226
      // Set the position and direction.
227
      coord.r() = p.r_local();
1,003,639,390 ✔
228
      coord.u() = p.u_local();
1,003,639,390 ✔
229

230
      // Apply translation.
231
      coord.r() -= c.translation_;
1,003,639,390 ✔
232

233
      // Apply rotation.
234
      if (!c.rotation_.empty()) {
1,003,639,390 ✔
235
        coord.rotate(c.rotation_);
66,157,597 ✔
236
      }
237

238
    } else if (c.type_ == Fill::LATTICE) {
154,995,577 !
239
      //========================================================================
240
      //! Found a lower lattice, update this coord level then search the next.
241

242
      Lattice& lat {*model::lattices[c.fill_]};
154,995,577 ✔
243

244
      // Set the position and direction.
245
      auto& coord {p.coord(p.n_coord())};
154,995,577 ✔
246
      coord.r() = p.r_local();
154,995,577 ✔
247
      coord.u() = p.u_local();
154,995,577 ✔
248

249
      // Apply translation.
250
      coord.r() -= c.translation_;
154,995,577 ✔
251

252
      // Apply rotation.
253
      if (!c.rotation_.empty()) {
154,995,577 ✔
254
        coord.rotate(c.rotation_);
358,336 ✔
255
      }
256

257
      // Determine lattice indices.
258
      auto& i_xyz {coord.lattice_index()};
154,995,577 ✔
259
      lat.get_indices(coord.r(), coord.u(), i_xyz);
154,995,577 ✔
260

261
      // Get local position in appropriate lattice cell
262
      coord.r() = lat.get_local_position(coord.r(), i_xyz);
154,995,577 ✔
263

264
      // Set lattice indices.
265
      coord.lattice() = c.fill_;
154,995,577 ✔
266

267
      // Set the lower coordinate level universe.
268
      if (lat.are_valid_indices(i_xyz)) {
154,995,577 ✔
269
        coord.universe() = lat[i_xyz];
148,587,956 ✔
270
      } else {
271
        if (lat.outer_ != NO_OUTER_UNIVERSE) {
6,407,621 !
272
          coord.universe() = lat.outer_;
6,407,621 ✔
273
        } else {
274
          p.mark_as_lost(fmt::format(
×
275
            "Particle {} left lattice {}, but it has no outer definition.",
276
            p.id(), lat.id_));
×
277
        }
278
      }
279
    }
280
    i_cell = C_NONE; // trip non-neighbor cell search at next iteration
1,158,634,967 ✔
281
    found = false;
1,158,634,967 ✔
282
  }
283

284
  return found;
285
}
286

287
//==============================================================================
288

289
bool neighbor_list_find_cell(GeometryState& p, bool verbose)
2,147,483,647 ✔
290
{
291

292
#ifdef OPENMC_DAGMC_ENABLED
293
  // A CSG crossing can move the particle into another instance of the same
294
  // DAGMC universe, where the previous facet history is no longer valid.
295
  if (p.surface() != SURFACE_NONE) {
259,458,911 ✔
296
    const auto& surf = model::surfaces[p.surface_index()];
259,458,910 ✔
297
    if (surf->geom_type() == GeometryType::CSG)
259,458,910 ✔
298
      p.history().reset();
259,458,726 ✔
299
  }
300
#endif
301

302
  // Reset all the deeper coordinate levels.
303
  for (int i = p.n_coord(); i < model::n_coord_levels; i++) {
2,147,483,647 ✔
304
    p.coord(i).reset();
1,918,145,889 ✔
305
  }
306

307
  // Get the cell this particle was in previously.
308
  auto coord_lvl = p.n_coord() - 1;
2,147,483,647 ✔
309
  auto i_cell = p.coord(coord_lvl).cell();
2,147,483,647 ✔
310
  Cell& c {*model::cells[i_cell]};
2,147,483,647 ✔
311

312
  // Search for the particle in that cell's neighbor list.  Return if we
313
  // found the particle.
314
  bool found = find_cell_inner(p, &c.neighbors_, verbose);
2,147,483,647 ✔
315
  if (found)
2,147,483,647 ✔
316
    return found;
317

318
  // The particle could not be found in the neighbor list.  Try searching all
319
  // cells in this universe, and update the neighbor list if we find a new
320
  // neighboring cell.
321
  found = find_cell_inner(p, nullptr, verbose);
1,639,508 ✔
322
  if (found)
1,639,508 ✔
323
    c.neighbors_.push_back(p.coord(coord_lvl).cell());
30,411 ✔
324
  return found;
325
}
326

327
void reconcile_cell_after_collision(GeometryState& p)
66 ✔
328
{
329
  // Find the first coordinate level whose current cell is inconsistent with
330
  // the particle's post-collision direction.
331
  int invalid_level = C_NONE;
66 ✔
332
  for (int level = 0; level < p.n_coord(); ++level) {
154 ✔
333
    const auto& coord {p.coord(level)};
99 !
334
    if (coord.cell() == C_NONE || !model::cells[coord.cell()]->contains(
99 !
335
                                    coord.r(), coord.u(), SURFACE_NONE)) {
99 ✔
336
      invalid_level = level;
337
      break;
338
    }
339
  }
340

341
  if (invalid_level == C_NONE)
66 ✔
342
    return;
343

344
  // Search from the first inconsistent level so that parent cells and
345
  // transformed lower universes are both handled correctly.
346
  if (p.coord(invalid_level).cell() != C_NONE) {
11 !
347
    p.n_coord() = invalid_level + 1;
11 ✔
348
    if (neighbor_list_find_cell(p))
11 !
349
      return;
350
  }
351

352
  // The current cell may not have a complete neighbor list yet. Fall back to
353
  // the normal exhaustive search used after a failed surface crossing.
UNCOV
354
  p.n_coord() = 1;
×
UNCOV
355
  if (!exhaustive_find_cell(p)) {
×
UNCOV
356
    p.mark_as_lost("Could not find particle after a collision near a surface.");
×
357
  }
358
}
359

360
bool exhaustive_find_cell(GeometryState& p, bool verbose)
1,310,536,779 ✔
361
{
362
  int i_universe = p.lowest_coord().universe();
1,310,536,779 ✔
363
  if (i_universe == C_NONE) {
1,310,536,779 ✔
364
    p.coord(0).universe() = model::root_universe;
272,700,358 ✔
365
    p.n_coord() = 1;
272,700,358 ✔
366
    i_universe = model::root_universe;
272,700,358 ✔
367
  }
368
  // Reset all the deeper coordinate levels.
369
  for (int i = p.n_coord(); i < model::n_coord_levels; i++) {
1,402,268,071 ✔
370
    p.coord(i).reset();
91,731,292 ✔
371
  }
372
  return find_cell_inner(p, nullptr, verbose);
1,310,536,779 ✔
373
}
374

375
//==============================================================================
376

377
void cross_lattice(GeometryState& p, const BoundaryInfo& boundary, bool verbose)
815,667,176 ✔
378
{
379
  auto& coord {p.lowest_coord()};
815,667,176 !
380
  auto& lat {*model::lattices[coord.lattice()]};
815,667,176 !
381

382
  if (verbose) {
815,667,176 !
UNCOV
383
    write_message(
×
UNCOV
384
      fmt::format("    Crossing lattice {}. Current position ({},{},{}). r={}",
×
UNCOV
385
        lat.id_, coord.lattice_index()[0], coord.lattice_index()[1],
×
UNCOV
386
        coord.lattice_index()[2], p.r()),
×
387
      1);
388
  }
389

390
  // Set the lattice indices.
391
  coord.lattice_index()[0] += boundary.lattice_translation()[0];
815,667,176 ✔
392
  coord.lattice_index()[1] += boundary.lattice_translation()[1];
815,667,176 ✔
393
  coord.lattice_index()[2] += boundary.lattice_translation()[2];
815,667,176 ✔
394

395
  // Set the new coordinate position.
396
  const auto& upper_coord {p.coord(p.n_coord() - 2)};
815,667,176 ✔
397
  const auto& cell {model::cells[upper_coord.cell()]};
815,667,176 ✔
398
  Position r = upper_coord.r();
815,667,176 ✔
399
  r -= cell->translation_;
815,667,176 ✔
400
  if (!cell->rotation_.empty()) {
815,667,176 ✔
401
    r = r.rotate(cell->rotation_);
471,647 ✔
402
  }
403
  p.r_local() = lat.get_local_position(r, coord.lattice_index());
815,667,176 ✔
404

405
  if (!lat.are_valid_indices(coord.lattice_index())) {
815,667,176 ✔
406
    // The particle is outside the lattice.  Search for it from the base coords.
407
    p.n_coord() = 1;
4,898,696 ✔
408
    bool found = exhaustive_find_cell(p);
4,898,696 ✔
409

410
    if (!found) {
4,898,696 !
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
  } else {
417
    // Find cell in next lattice element.
418
    p.lowest_coord().universe() = lat[coord.lattice_index()];
810,768,480 ✔
419
    bool found = exhaustive_find_cell(p);
810,768,480 ✔
420

421
    if (!found) {
810,768,480 !
422
      // A particle crossing the corner of a lattice tile may not be found.  In
423
      // this case, search for it from the base coords.
UNCOV
424
      p.n_coord() = 1;
×
UNCOV
425
      bool found = exhaustive_find_cell(p);
×
UNCOV
426
      if (!found) {
×
UNCOV
427
        p.mark_as_lost(fmt::format("Particle {} could not be located after "
×
428
                                   "crossing a boundary of lattice {}",
UNCOV
429
          p.id(), lat.id_));
×
430
      }
431
    }
432
  }
433
}
815,667,176 ✔
434

435
//==============================================================================
436

437
BoundaryInfo distance_to_boundary(GeometryState& p)
2,147,483,647 ✔
438
{
439
  BoundaryInfo info;
2,147,483,647 ✔
440
  double d_lat = INFINITY;
2,147,483,647 ✔
441
  double d_surf = INFINITY;
2,147,483,647 ✔
442
  int32_t level_surf_cross;
2,147,483,647 ✔
443
  array<int, 3> level_lat_trans {};
2,147,483,647 ✔
444

445
  // Loop over each coordinate level.
446
  for (int i = 0; i < p.n_coord(); i++) {
2,147,483,647 ✔
447
    const auto& coord {p.coord(i)};
2,147,483,647 ✔
448
    const Position& r {coord.r()};
2,147,483,647 ✔
449
    const Direction& u {coord.u()};
2,147,483,647 ✔
450
    Cell& c {*model::cells[coord.cell()]};
2,147,483,647 ✔
451

452
    // Find the oncoming surface in this cell and the distance to it.
453
    auto surface_distance = c.distance(r, u, p.surface(), &p);
2,147,483,647 ✔
454
    d_surf = surface_distance.first;
2,147,483,647 ✔
455
    level_surf_cross = surface_distance.second;
2,147,483,647 ✔
456

457
    // Find the distance to the next lattice tile crossing.
458
    if (coord.lattice() != C_NONE) {
2,147,483,647 ✔
459
      auto& lat {*model::lattices[coord.lattice()]};
1,412,244,621 !
460
      // TODO: refactor so both lattice use the same position argument (which
461
      // also means the lat.type attribute can be removed)
462
      std::pair<double, array<int, 3>> lattice_distance;
1,412,244,621 ✔
463
      switch (lat.type_) {
1,412,244,621 !
464
      case LatticeType::rect:
1,326,741,324 ✔
465
        lattice_distance = lat.distance(r, u, coord.lattice_index());
1,326,741,324 ✔
466
        break;
1,326,741,324 ✔
467
      case LatticeType::hex:
85,503,297 ✔
468
        auto& cell_above {model::cells[p.coord(i - 1).cell()]};
85,503,297 ✔
469
        Position r_hex {p.coord(i - 1).r()};
85,503,297 ✔
470
        r_hex -= cell_above->translation_;
85,503,297 ✔
471
        if (coord.rotated()) {
85,503,297 ✔
472
          r_hex = r_hex.rotate(cell_above->rotation_);
701,954 ✔
473
        }
474
        r_hex.z = coord.r().z;
85,503,297 ✔
475
        lattice_distance = lat.distance(r_hex, u, coord.lattice_index());
85,503,297 ✔
476
        break;
85,503,297 ✔
477
      }
478
      d_lat = lattice_distance.first;
1,412,244,621 ✔
479
      level_lat_trans = lattice_distance.second;
1,412,244,621 ✔
480

481
      if (d_lat < 0) {
1,412,244,621 !
UNCOV
482
        p.mark_as_lost(fmt::format("Particle {} had a negative distance "
×
483
                                   "to a lattice boundary.",
UNCOV
484
          p.id()));
×
485
      }
486
    }
487

488
    // If the boundary on this coordinate level is coincident with a boundary on
489
    // a higher level then we need to make sure that the higher level boundary
490
    // is selected.  This logic must consider floating point precision.
491
    double& d = info.distance();
2,147,483,647 ✔
492
    if (d_surf < d_lat - FP_COINCIDENT) {
2,147,483,647 ✔
493
      if (d == INFINITY || (d - d_surf) / d >= FP_REL_PRECISION) {
2,147,483,647 ✔
494
        // Update closest distance
495
        d = d_surf;
2,147,483,647 ✔
496

497
        // If the cell is not simple, it is possible that both the negative and
498
        // positive half-space were given in the region specification. Thus, we
499
        // have to explicitly check which half-space the particle would be
500
        // traveling into if the surface is crossed
501
        if (c.is_simple() || d == INFTY) {
2,147,483,647 ✔
502
          info.surface() = level_surf_cross;
2,147,483,647 ✔
503
        } else {
504
          Position r_hit = r + d_surf * u;
188,489,444 ✔
505
          Surface& surf {*model::surfaces[std::abs(level_surf_cross) - 1]};
188,489,444 ✔
506
          Direction norm = surf.normal(r_hit);
188,489,444 ✔
507
          if (u.dot(norm) > 0) {
188,489,444 ✔
508
            info.surface() = std::abs(level_surf_cross);
177,663,050 ✔
509
          } else {
510
            info.surface() = -std::abs(level_surf_cross);
10,826,394 ✔
511
          }
512
        }
513

514
        info.lattice_translation()[0] = 0;
2,147,483,647 ✔
515
        info.lattice_translation()[1] = 0;
2,147,483,647 ✔
516
        info.lattice_translation()[2] = 0;
2,147,483,647 ✔
517
        info.coord_level() = i + 1;
2,147,483,647 ✔
518
      }
519
    } else {
520
      if (d == INFINITY || (d - d_lat) / d >= FP_REL_PRECISION) {
1,177,708,641 !
521
        d = d_lat;
966,583,740 ✔
522
        info.surface() = SURFACE_NONE;
966,583,740 ✔
523
        info.lattice_translation() = level_lat_trans;
966,583,740 ✔
524
        info.coord_level() = i + 1;
966,583,740 ✔
525
      }
526
    }
527
  }
528
  return info;
2,147,483,647 ✔
529
}
530

531
//==============================================================================
532
// C API
533
//==============================================================================
534

535
extern "C" int openmc_find_cell(
600,308 ✔
536
  const double* xyz, int32_t* index, int32_t* instance)
537
{
538
  GeometryState geom_state;
600,308 ✔
539

540
  geom_state.r() = Position {xyz};
600,308 ✔
541
  geom_state.u() = {0.0, 0.0, 1.0};
600,308 ✔
542

543
  if (!exhaustive_find_cell(geom_state)) {
600,308 ✔
544
    set_errmsg(
11 ✔
545
      fmt::format("Could not find cell at position {}.", geom_state.r()));
11 ✔
546
    return OPENMC_E_GEOMETRY;
11 ✔
547
  }
548

549
  *index = geom_state.lowest_coord().cell();
600,297 ✔
550
  *instance = geom_state.cell_instance();
600,297 ✔
551
  return 0;
600,297 ✔
552
}
600,308 ✔
553

554
extern "C" int openmc_global_bounding_box(double* llc, double* urc)
11 ✔
555
{
556
  auto bbox = model::universes.at(model::root_universe)->bounding_box();
11 ✔
557

558
  // set lower left corner values
559
  llc[0] = bbox.min.x;
11 ✔
560
  llc[1] = bbox.min.y;
11 ✔
561
  llc[2] = bbox.min.z;
11 ✔
562

563
  // set upper right corner values
564
  urc[0] = bbox.max.x;
11 ✔
565
  urc[1] = bbox.max.y;
11 ✔
566
  urc[2] = bbox.max.z;
11 ✔
567

568
  return 0;
11 ✔
569
}
570

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