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

openmc-dev / openmc / 14515233533

17 Apr 2025 12:06PM UTC coverage: 83.16% (-2.3%) from 85.414%
14515233533

Pull #3087

github

web-flow
Merge 6ed397e9d into 47ca2916a
Pull Request #3087: wheel building with scikit build core

140769 of 169274 relevant lines covered (83.16%)

11830168.1 hits per line

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

81.9
/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
} // namespace model
30

31
//==============================================================================
32
// Non-member functions
33
//==============================================================================
34

35
bool check_cell_overlap(GeometryState& p, bool error)
1,320,000✔
36
{
37
  int n_coord = p.n_coord();
1,320,000✔
38

39
  // Loop through each coordinate level
40
  for (int j = 0; j < n_coord; j++) {
2,266,044✔
41
    Universe& univ = *model::universes[p.coord(j).universe];
1,320,000✔
42

43
    // Loop through each cell on this level
44
    for (auto index_cell : univ.cells_) {
4,466,044✔
45
      Cell& c = *model::cells[index_cell];
3,520,000✔
46
      if (c.contains(p.coord(j).r, p.coord(j).u, p.surface())) {
3,520,000✔
47
        if (index_cell != p.coord(j).cell) {
1,249,622✔
48
          if (error) {
373,956✔
49
            fatal_error(
×
50
              fmt::format("Overlapping cells detected: {}, {} on universe {}",
×
51
                c.id_, model::cells[p.coord(j).cell]->id_, univ.id_));
×
52
          }
53
          return true;
373,956✔
54
        }
55
#pragma omp atomic
477,636✔
56
        ++model::overlap_check_count[index_cell];
875,666✔
57
      }
58
    }
59
  }
60

61
  return false;
946,044✔
62
}
63

64
//==============================================================================
65

66
int cell_instance_at_level(const GeometryState& p, int level)
1,752,713,095✔
67
{
68
  // throw error if the requested level is too deep for the geometry
69
  if (level > model::n_coord_levels) {
1,752,713,095✔
70
    fatal_error(fmt::format("Cell instance at level {} requested, but only {} "
×
71
                            "levels exist in the geometry.",
72
      level, p.n_coord()));
73
  }
74

75
  // determine the cell instance
76
  Cell& c {*model::cells[p.coord(level).cell]};
1,752,713,095✔
77

78
  // quick exit if this cell doesn't have distribcell instances
79
  if (c.distribcell_index_ == C_NONE)
1,752,713,095✔
80
    return C_NONE;
×
81

82
  // compute the cell's instance
83
  int instance = 0;
1,752,713,095✔
84
  for (int i = 0; i < level; i++) {
2,147,483,647✔
85
    const auto& c_i {*model::cells[p.coord(i).cell]};
1,798,935,411✔
86
    if (c_i.type_ == Fill::UNIVERSE) {
1,798,935,411✔
87
      instance += c_i.offset_[c.distribcell_index_];
780,924,218✔
88
    } else if (c_i.type_ == Fill::LATTICE) {
1,018,011,193✔
89
      instance += c_i.offset_[c.distribcell_index_];
1,018,011,193✔
90
      auto& lat {*model::lattices[p.coord(i + 1).lattice]};
1,018,011,193✔
91
      const auto& i_xyz {p.coord(i + 1).lattice_i};
1,018,011,193✔
92
      if (lat.are_valid_indices(i_xyz)) {
1,018,011,193✔
93
        instance += lat.offset(c.distribcell_index_, i_xyz);
1,013,125,268✔
94
      }
95
    }
96
  }
97
  return instance;
1,752,713,095✔
98
}
99

100
//==============================================================================
101

102
bool find_cell_inner(
1,926,287,756✔
103
  GeometryState& p, const NeighborList* neighbor_list, bool verbose)
104
{
105
  // Find which cell of this universe the particle is in.  Use the neighbor list
106
  // to shorten the search if one was provided.
107
  bool found = false;
1,926,287,756✔
108
  int32_t i_cell = C_NONE;
1,926,287,756✔
109
  if (neighbor_list) {
1,926,287,756✔
110
    for (auto it = neighbor_list->cbegin(); it != neighbor_list->cend(); ++it) {
936,213,503✔
111
      i_cell = *it;
934,620,254✔
112

113
      // Make sure the search cell is in the same universe.
114
      int i_universe = p.lowest_coord().universe;
934,620,254✔
115
      if (model::cells[i_cell]->universe_ != i_universe)
934,620,254✔
116
        continue;
×
117

118
      // Check if this cell contains the particle.
119
      Position r {p.r_local()};
934,620,254✔
120
      Direction u {p.u_local()};
934,620,254✔
121
      auto surf = p.surface();
934,620,254✔
122
      if (model::cells[i_cell]->contains(r, u, surf)) {
934,620,254✔
123
        p.lowest_coord().cell = i_cell;
788,817,709✔
124
        found = true;
788,817,709✔
125
        break;
788,817,709✔
126
      }
127
    }
128

129
    // If we're attempting a neighbor list search and fail, we
130
    // now know we should return false. This will trigger an
131
    // exhaustive search from neighbor_list_find_cell and make
132
    // the result from that be appended to the neighbor list.
133
    if (!found) {
790,410,958✔
134
      return found;
1,593,249✔
135
    }
136
  }
137

138
  // Check successively lower coordinate levels until finding material fill
139
  for (;; ++p.n_coord()) {
482,290,868✔
140
    // If we did not attempt to use neighbor lists, i_cell is still C_NONE.  In
141
    // that case, we should now do an exhaustive search to find the right value
142
    // of i_cell.
143
    //
144
    // Alternatively, neighbor list searches could have succeeded, but we found
145
    // that the fill of the neighbor cell was another universe. As such, in the
146
    // code below this conditional, we set i_cell back to C_NONE to indicate
147
    // that.
148
    if (i_cell == C_NONE) {
2,147,483,647✔
149
      int i_universe = p.lowest_coord().universe;
1,377,022,232✔
150
      const auto& univ {model::universes[i_universe]};
1,377,022,232✔
151
      found = univ->find_cell(p);
1,377,022,232✔
152
    }
153

154
    if (!found) {
2,147,483,647✔
155
      return found;
173,892,461✔
156
    }
157
    i_cell = p.lowest_coord().cell;
1,991,947,480✔
158

159
    // Announce the cell that the particle is entering.
160
    if (found && verbose) {
1,991,947,480✔
161
      auto msg = fmt::format("    Entering cell {}", model::cells[i_cell]->id_);
×
162
      write_message(msg, 1);
×
163
    }
164

165
    Cell& c {*model::cells[i_cell]};
1,991,947,480✔
166
    if (c.type_ == Fill::MATERIAL) {
1,991,947,480✔
167
      // Found a material cell which means this is the lowest coord level.
168

169
      p.cell_instance() = 0;
1,750,802,046✔
170
      // Find the distribcell instance number.
171
      if (c.distribcell_index_ >= 0) {
1,750,802,046✔
172
        p.cell_instance() = cell_instance_at_level(p, p.n_coord() - 1);
1,750,801,443✔
173
      }
174

175
      // Set the material and temperature.
176
      p.material_last() = p.material();
1,750,802,046✔
177
      p.material() = c.material(p.cell_instance());
1,750,802,046✔
178
      p.sqrtkT_last() = p.sqrtkT();
1,750,802,046✔
179
      p.sqrtkT() = c.sqrtkT(p.cell_instance());
1,750,802,046✔
180

181
      return true;
1,750,802,046✔
182

183
    } else if (c.type_ == Fill::UNIVERSE) {
241,145,434✔
184
      //========================================================================
185
      //! Found a lower universe, update this coord level then search the next.
186

187
      // Set the lower coordinate level universe.
188
      auto& coord {p.coord(p.n_coord())};
135,937,152✔
189
      coord.universe = c.fill_;
135,937,152✔
190

191
      // Set the position and direction.
192
      coord.r = p.r_local();
135,937,152✔
193
      coord.u = p.u_local();
135,937,152✔
194

195
      // Apply translation.
196
      coord.r -= c.translation_;
135,937,152✔
197

198
      // Apply rotation.
199
      if (!c.rotation_.empty()) {
135,937,152✔
200
        coord.rotate(c.rotation_);
2,189,110✔
201
      }
202

203
    } else if (c.type_ == Fill::LATTICE) {
105,208,282✔
204
      //========================================================================
205
      //! Found a lower lattice, update this coord level then search the next.
206

207
      Lattice& lat {*model::lattices[c.fill_]};
105,208,282✔
208

209
      // Set the position and direction.
210
      auto& coord {p.coord(p.n_coord())};
105,208,282✔
211
      coord.r = p.r_local();
105,208,282✔
212
      coord.u = p.u_local();
105,208,282✔
213

214
      // Apply translation.
215
      coord.r -= c.translation_;
105,208,282✔
216

217
      // Apply rotation.
218
      if (!c.rotation_.empty()) {
105,208,282✔
219
        coord.rotate(c.rotation_);
354,519✔
220
      }
221

222
      // Determine lattice indices.
223
      auto& i_xyz {coord.lattice_i};
105,208,282✔
224
      lat.get_indices(coord.r, coord.u, i_xyz);
105,208,282✔
225

226
      // Get local position in appropriate lattice cell
227
      coord.r = lat.get_local_position(coord.r, i_xyz);
105,208,282✔
228

229
      // Set lattice indices.
230
      coord.lattice = c.fill_;
105,208,282✔
231

232
      // Set the lower coordinate level universe.
233
      if (lat.are_valid_indices(i_xyz)) {
105,208,282✔
234
        coord.universe = lat[i_xyz];
100,322,357✔
235
      } else {
236
        if (lat.outer_ != NO_OUTER_UNIVERSE) {
4,885,925✔
237
          coord.universe = lat.outer_;
4,885,925✔
238
        } else {
239
          p.mark_as_lost(fmt::format(
×
240
            "Particle {} left lattice {}, but it has no outer definition.",
241
            p.id(), lat.id_));
×
242
        }
243
      }
244
    }
245
    i_cell = C_NONE; // trip non-neighbor cell search at next iteration
241,145,434✔
246
    found = false;
241,145,434✔
247
  }
241,145,434✔
248

249
  return found;
250
}
251

252
//==============================================================================
253

254
bool neighbor_list_find_cell(GeometryState& p, bool verbose)
790,410,958✔
255
{
256

257
  // Reset all the deeper coordinate levels.
258
  for (int i = p.n_coord(); i < model::n_coord_levels; i++) {
1,085,055,646✔
259
    p.coord(i).reset();
294,644,688✔
260
  }
261

262
  // Get the cell this particle was in previously.
263
  auto coord_lvl = p.n_coord() - 1;
790,410,958✔
264
  auto i_cell = p.coord(coord_lvl).cell;
790,410,958✔
265
  Cell& c {*model::cells[i_cell]};
790,410,958✔
266

267
  // Search for the particle in that cell's neighbor list.  Return if we
268
  // found the particle.
269
  bool found = find_cell_inner(p, &c.neighbors_, verbose);
790,410,958✔
270
  if (found)
790,410,958✔
271
    return found;
788,817,709✔
272

273
  // The particle could not be found in the neighbor list.  Try searching all
274
  // cells in this universe, and update the neighbor list if we find a new
275
  // neighboring cell.
276
  found = find_cell_inner(p, nullptr, verbose);
1,593,249✔
277
  if (found)
1,593,249✔
278
    c.neighbors_.push_back(p.coord(coord_lvl).cell);
24,218✔
279
  return found;
1,593,249✔
280
}
281

282
bool exhaustive_find_cell(GeometryState& p, bool verbose)
1,134,283,549✔
283
{
284
  int i_universe = p.lowest_coord().universe;
1,134,283,549✔
285
  if (i_universe == C_NONE) {
1,134,283,549✔
286
    p.coord(0).universe = model::root_universe;
244,397,020✔
287
    p.n_coord() = 1;
244,397,020✔
288
    i_universe = model::root_universe;
244,397,020✔
289
  }
290
  // Reset all the deeper coordinate levels.
291
  for (int i = p.n_coord(); i < model::n_coord_levels; i++) {
1,193,735,123✔
292
    p.coord(i).reset();
59,451,574✔
293
  }
294
  return find_cell_inner(p, nullptr, verbose);
1,134,283,549✔
295
}
296

297
//==============================================================================
298

299
void cross_lattice(GeometryState& p, const BoundaryInfo& boundary, bool verbose)
674,639,486✔
300
{
301
  auto& coord {p.lowest_coord()};
674,639,486✔
302
  auto& lat {*model::lattices[coord.lattice]};
674,639,486✔
303

304
  if (verbose) {
674,639,486✔
305
    write_message(
×
306
      fmt::format("    Crossing lattice {}. Current position ({},{},{}). r={}",
×
307
        lat.id_, coord.lattice_i[0], coord.lattice_i[1], coord.lattice_i[2],
×
308
        p.r()),
309
      1);
310
  }
311

312
  // Set the lattice indices.
313
  coord.lattice_i[0] += boundary.lattice_translation[0];
674,639,486✔
314
  coord.lattice_i[1] += boundary.lattice_translation[1];
674,639,486✔
315
  coord.lattice_i[2] += boundary.lattice_translation[2];
674,639,486✔
316

317
  // Set the new coordinate position.
318
  const auto& upper_coord {p.coord(p.n_coord() - 2)};
674,639,486✔
319
  const auto& cell {model::cells[upper_coord.cell]};
674,639,486✔
320
  Position r = upper_coord.r;
674,639,486✔
321
  r -= cell->translation_;
674,639,486✔
322
  if (!cell->rotation_.empty()) {
674,639,486✔
323
    r = r.rotate(cell->rotation_);
470,965✔
324
  }
325
  p.r_local() = lat.get_local_position(r, coord.lattice_i);
674,639,486✔
326

327
  if (!lat.are_valid_indices(coord.lattice_i)) {
674,639,486✔
328
    // The particle is outside the lattice.  Search for it from the base coords.
329
    p.n_coord() = 1;
3,726,107✔
330
    bool found = exhaustive_find_cell(p);
3,726,107✔
331

332
    if (!found) {
3,726,107✔
333
      p.mark_as_lost(fmt::format("Particle {} could not be located after "
×
334
                                 "crossing a boundary of lattice {}",
335
        p.id(), lat.id_));
×
336
    }
337

338
  } else {
339
    // Find cell in next lattice element.
340
    p.lowest_coord().universe = lat[coord.lattice_i];
670,913,379✔
341
    bool found = exhaustive_find_cell(p);
670,913,379✔
342

343
    if (!found) {
670,913,379✔
344
      // A particle crossing the corner of a lattice tile may not be found.  In
345
      // this case, search for it from the base coords.
346
      p.n_coord() = 1;
×
347
      bool found = exhaustive_find_cell(p);
×
348
      if (!found) {
×
349
        p.mark_as_lost(fmt::format("Particle {} could not be located after "
×
350
                                   "crossing a boundary of lattice {}",
351
          p.id(), lat.id_));
×
352
      }
353
    }
354
  }
355
}
674,639,486✔
356

357
//==============================================================================
358

359
BoundaryInfo distance_to_boundary(GeometryState& p)
2,147,483,647✔
360
{
361
  BoundaryInfo info;
2,147,483,647✔
362
  double d_lat = INFINITY;
2,147,483,647✔
363
  double d_surf = INFINITY;
2,147,483,647✔
364
  int32_t level_surf_cross;
365
  array<int, 3> level_lat_trans {};
2,147,483,647✔
366

367
  // Loop over each coordinate level.
368
  for (int i = 0; i < p.n_coord(); i++) {
2,147,483,647✔
369
    const auto& coord {p.coord(i)};
2,147,483,647✔
370
    const Position& r {coord.r};
2,147,483,647✔
371
    const Direction& u {coord.u};
2,147,483,647✔
372
    Cell& c {*model::cells[coord.cell]};
2,147,483,647✔
373

374
    // Find the oncoming surface in this cell and the distance to it.
375
    auto surface_distance = c.distance(r, u, p.surface(), &p);
2,147,483,647✔
376
    d_surf = surface_distance.first;
2,147,483,647✔
377
    level_surf_cross = surface_distance.second;
2,147,483,647✔
378

379
    // Find the distance to the next lattice tile crossing.
380
    if (coord.lattice != C_NONE) {
2,147,483,647✔
381
      auto& lat {*model::lattices[coord.lattice]};
1,044,977,541✔
382
      // TODO: refactor so both lattice use the same position argument (which
383
      // also means the lat.type attribute can be removed)
384
      std::pair<double, array<int, 3>> lattice_distance;
1,044,977,541✔
385
      switch (lat.type_) {
1,044,977,541✔
386
      case LatticeType::rect:
959,980,530✔
387
        lattice_distance = lat.distance(r, u, coord.lattice_i);
959,980,530✔
388
        break;
959,980,530✔
389
      case LatticeType::hex:
84,997,011✔
390
        auto& cell_above {model::cells[p.coord(i - 1).cell]};
84,997,011✔
391
        Position r_hex {p.coord(i - 1).r};
84,997,011✔
392
        r_hex -= cell_above->translation_;
84,997,011✔
393
        if (coord.rotated) {
84,997,011✔
394
          r_hex = r_hex.rotate(cell_above->rotation_);
681,417✔
395
        }
396
        r_hex.z = coord.r.z;
84,997,011✔
397
        lattice_distance = lat.distance(r_hex, u, coord.lattice_i);
84,997,011✔
398
        break;
84,997,011✔
399
      }
400
      d_lat = lattice_distance.first;
1,044,977,541✔
401
      level_lat_trans = lattice_distance.second;
1,044,977,541✔
402

403
      if (d_lat < 0) {
1,044,977,541✔
404
        p.mark_as_lost(fmt::format("Particle {} had a negative distance "
×
405
                                   "to a lattice boundary.",
406
          p.id()));
×
407
      }
408
    }
409

410
    // If the boundary on this coordinate level is coincident with a boundary on
411
    // a higher level then we need to make sure that the higher level boundary
412
    // is selected.  This logic must consider floating point precision.
413
    double& d = info.distance;
2,147,483,647✔
414
    if (d_surf < d_lat - FP_COINCIDENT) {
2,147,483,647✔
415
      if (d == INFINITY || (d - d_surf) / d >= FP_REL_PRECISION) {
2,147,483,647✔
416
        // Update closest distance
417
        d = d_surf;
2,147,483,647✔
418

419
        // If the cell is not simple, it is possible that both the negative and
420
        // positive half-space were given in the region specification. Thus, we
421
        // have to explicitly check which half-space the particle would be
422
        // traveling into if the surface is crossed
423
        if (c.is_simple() || d == INFTY) {
2,147,483,647✔
424
          info.surface = level_surf_cross;
2,147,483,647✔
425
        } else {
426
          Position r_hit = r + d_surf * u;
12,743,337✔
427
          Surface& surf {*model::surfaces[std::abs(level_surf_cross) - 1]};
12,743,337✔
428
          Direction norm = surf.normal(r_hit);
12,743,337✔
429
          if (u.dot(norm) > 0) {
12,743,337✔
430
            info.surface = std::abs(level_surf_cross);
6,317,408✔
431
          } else {
432
            info.surface = -std::abs(level_surf_cross);
6,425,929✔
433
          }
434
        }
435

436
        info.lattice_translation[0] = 0;
2,147,483,647✔
437
        info.lattice_translation[1] = 0;
2,147,483,647✔
438
        info.lattice_translation[2] = 0;
2,147,483,647✔
439
        info.coord_level = i + 1;
2,147,483,647✔
440
      }
441
    } else {
442
      if (d == INFINITY || (d - d_lat) / d >= FP_REL_PRECISION) {
912,104,779✔
443
        d = d_lat;
759,830,390✔
444
        info.surface = SURFACE_NONE;
759,830,390✔
445
        info.lattice_translation = level_lat_trans;
759,830,390✔
446
        info.coord_level = i + 1;
759,830,390✔
447
      }
448
    }
449
  }
450
  return info;
2,147,483,647✔
451
}
452

453
//==============================================================================
454
// C API
455
//==============================================================================
456

457
extern "C" int openmc_find_cell(
×
458
  const double* xyz, int32_t* index, int32_t* instance)
459
{
460
  GeometryState geom_state;
×
461

462
  geom_state.r() = Position {xyz};
×
463
  geom_state.u() = {0.0, 0.0, 1.0};
×
464

465
  if (!exhaustive_find_cell(geom_state)) {
×
466
    set_errmsg(
×
467
      fmt::format("Could not find cell at position {}.", geom_state.r()));
×
468
    return OPENMC_E_GEOMETRY;
×
469
  }
470

471
  *index = geom_state.lowest_coord().cell;
×
472
  *instance = geom_state.cell_instance();
×
473
  return 0;
×
474
}
475

476
extern "C" int openmc_global_bounding_box(double* llc, double* urc)
×
477
{
478
  auto bbox = model::universes.at(model::root_universe)->bounding_box();
×
479

480
  // set lower left corner values
481
  llc[0] = bbox.xmin;
×
482
  llc[1] = bbox.ymin;
×
483
  llc[2] = bbox.zmin;
×
484

485
  // set upper right corner values
486
  urc[0] = bbox.xmax;
×
487
  urc[1] = bbox.ymax;
×
488
  urc[2] = bbox.zmax;
×
489

490
  return 0;
×
491
}
492

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

© 2025 Coveralls, Inc