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

openmc-dev / openmc / 24227695169

10 Apr 2026 05:16AM UTC coverage: 81.399% (+0.06%) from 81.34%
24227695169

Pull #3566

github

web-flow
Merge d6d41c9c7 into fd1bc26af
Pull Request #3566: Resolve merge conflicts for PR #3516 - Implement Virtual Lattice Method for Efficient Simulation of Dispersed Fuels

17904 of 25783 branches covered (69.44%)

Branch coverage included in aggregate %.

388 of 443 new or added lines in 10 files covered. (87.58%)

58573 of 68170 relevant lines covered (85.92%)

44469925.31 hits per line

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

77.03
/src/cell.cpp
1

2
#include "openmc/cell.h"
3

4
#include <algorithm>
5
#include <cassert>
6
#include <cctype>
7
#include <cmath>
8
#include <iterator>
9
#include <set>
10
#include <sstream>
11
#include <string>
12

13
#include <fmt/core.h>
14

15
#include "openmc/capi.h"
16
#include "openmc/constants.h"
17
#include "openmc/dagmc.h"
18
#include "openmc/error.h"
19
#include "openmc/geometry.h"
20
#include "openmc/hdf5_interface.h"
21
#include "openmc/lattice.h"
22
#include "openmc/material.h"
23
#include "openmc/nuclide.h"
24
#include "openmc/settings.h"
25
#include "openmc/xml_interface.h"
26

27
namespace openmc {
28

29
//==============================================================================
30
// Global variables
31
//==============================================================================
32

33
namespace model {
34
std::unordered_map<int32_t, int32_t> cell_map;
35
vector<unique_ptr<Cell>> cells;
36

37
} // namespace model
38

39
vector<vector<int32_t>> generate_triso_distribution(vector<int> lattice_shape,
15✔
40
  vector<double> lattice_pitch, vector<double> lattice_lower_left,
41
  vector<std::int32_t> cell_rpn, int id)
42
{
43
  vector<vector<int32_t>> triso_distribution(
15✔
44
    lattice_shape[0] * lattice_shape[1] * lattice_shape[2]);
15✔
45
  vector<double> mesh_center(3);
15✔
46
  vector<int> mesh_ind(3);
15✔
47

48
  for (int32_t token : cell_rpn) {
1,515✔
49
    if (token >= OP_UNION)
1,500!
NEW
50
      continue;
×
51
    vector<double> triso_center = model::surfaces[abs(token) - 1]->get_center();
1,500✔
52
    for (int i = 0; i < 3; i++) {
6,000✔
53
      mesh_ind[i] =
4,500✔
54
        floor((triso_center[i] - lattice_lower_left[i]) / lattice_pitch[i]);
4,500✔
55
    }
56
    for (int i = mesh_ind[0] - 1; i <= mesh_ind[0] + 1; i++) {
6,000✔
57
      for (int j = mesh_ind[1] - 1; j <= mesh_ind[1] + 1; j++) {
18,000✔
58
        for (int k = mesh_ind[2] - 1; k <= mesh_ind[2] + 1; k++) {
54,000✔
59
          if (i < 0 || i >= lattice_shape[0] || j < 0 ||
35,775✔
60
              j >= lattice_shape[1] || k < 0 || k >= lattice_shape[2])
68,715✔
61
            continue;
21,360✔
62
          mesh_center[0] = (i + 0.5) * lattice_pitch[0] + lattice_lower_left[0];
19,140✔
63
          mesh_center[1] = (j + 0.5) * lattice_pitch[1] + lattice_lower_left[1];
19,140✔
64
          mesh_center[2] = (k + 0.5) * lattice_pitch[2] + lattice_lower_left[2];
19,140✔
65
          if (model::surfaces[abs(token) - 1]->triso_in_mesh(
19,140✔
66
                mesh_center, lattice_pitch)) {
67
            triso_distribution[i + j * lattice_shape[0] +
2,415✔
68
                               k * lattice_shape[0] * lattice_shape[1]]
2,415✔
69
              .push_back(token);
2,415✔
70
            model::surfaces[abs(token) - 1]->connect_to_triso_base(id, "base");
4,830✔
71
          }
72
        }
73
      }
74
    }
75
  }
1,500✔
76

77
  return triso_distribution;
15✔
78
}
15✔
79

80
//==============================================================================
81
// Cell implementation
82
//==============================================================================
83

84
int32_t Cell::n_instances() const
15,385✔
85
{
86
  return model::universes[universe_]->n_instances_;
15,385✔
87
}
88

89
void Cell::set_rotation(const vector<double>& rot)
423✔
90
{
91
  if (fill_ == C_NONE) {
423!
92
    fatal_error(fmt::format("Cannot apply a rotation to cell {}"
×
93
                            " because it is not filled with another universe",
94
      id_));
×
95
  }
96

97
  if (rot.size() != 3 && rot.size() != 9) {
423!
98
    fatal_error(fmt::format("Non-3D rotation vector applied to cell {}", id_));
×
99
  }
100

101
  // Compute and store the inverse rotation matrix for the angles given.
102
  rotation_.clear();
423✔
103
  rotation_.reserve(rot.size() == 9 ? 9 : 12);
846!
104
  if (rot.size() == 3) {
423!
105
    double phi = -rot[0] * PI / 180.0;
423✔
106
    double theta = -rot[1] * PI / 180.0;
423✔
107
    double psi = -rot[2] * PI / 180.0;
423✔
108
    rotation_.push_back(std::cos(theta) * std::cos(psi));
423✔
109
    rotation_.push_back(-std::cos(phi) * std::sin(psi) +
423✔
110
                        std::sin(phi) * std::sin(theta) * std::cos(psi));
423✔
111
    rotation_.push_back(std::sin(phi) * std::sin(psi) +
423✔
112
                        std::cos(phi) * std::sin(theta) * std::cos(psi));
423✔
113
    rotation_.push_back(std::cos(theta) * std::sin(psi));
423✔
114
    rotation_.push_back(std::cos(phi) * std::cos(psi) +
423✔
115
                        std::sin(phi) * std::sin(theta) * std::sin(psi));
423✔
116
    rotation_.push_back(-std::sin(phi) * std::cos(psi) +
423✔
117
                        std::cos(phi) * std::sin(theta) * std::sin(psi));
423✔
118
    rotation_.push_back(-std::sin(theta));
423✔
119
    rotation_.push_back(std::sin(phi) * std::cos(theta));
423✔
120
    rotation_.push_back(std::cos(phi) * std::cos(theta));
423✔
121

122
    // When user specifies angles, write them at end of vector
123
    rotation_.push_back(rot[0]);
423✔
124
    rotation_.push_back(rot[1]);
423✔
125
    rotation_.push_back(rot[2]);
423✔
126
  } else {
127
    std::copy(rot.begin(), rot.end(), std::back_inserter(rotation_));
×
128
  }
129
}
423✔
130

131
double Cell::temperature(int32_t instance) const
9,634✔
132
{
133
  if (sqrtkT_.size() < 1) {
9,634!
134
    throw std::runtime_error {"Cell temperature has not yet been set."};
×
135
  }
136

137
  if (instance >= 0) {
9,634✔
138
    double sqrtkT = sqrtkT_.size() == 1 ? sqrtkT_.at(0) : sqrtkT_.at(instance);
9,548✔
139
    return sqrtkT * sqrtkT / K_BOLTZMANN;
9,548✔
140
  } else {
141
    return sqrtkT_[0] * sqrtkT_[0] / K_BOLTZMANN;
86✔
142
  }
143
}
144

145
double Cell::density_mult(int32_t instance) const
2,147,483,647✔
146
{
147
  if (instance >= 0) {
2,147,483,647✔
148
    return density_mult_.size() == 1 ? density_mult_.at(0)
2,147,483,647✔
149
                                     : density_mult_.at(instance);
5,034,975✔
150
  } else {
151
    return density_mult_[0];
77✔
152
  }
153
}
154

155
double Cell::density(int32_t instance) const
198✔
156
{
157
  const int32_t mat_index = material(instance);
198!
158
  if (mat_index == MATERIAL_VOID)
198!
159
    return 0.0;
160

161
  return density_mult(instance) * model::materials[mat_index]->density_gpcc();
396!
162
}
163

164
void Cell::set_temperature(double T, int32_t instance, bool set_contained)
10,012✔
165
{
166
  if (settings::temperature_method == TemperatureMethod::INTERPOLATION) {
10,012!
167
    if (T < (data::temperature_min - settings::temperature_tolerance)) {
×
168
      throw std::runtime_error {
×
169
        fmt::format("Temperature of {} K is below minimum temperature at "
×
170
                    "which data is available of {} K.",
171
          T, data::temperature_min)};
×
172
    } else if (T > (data::temperature_max + settings::temperature_tolerance)) {
×
173
      throw std::runtime_error {
×
174
        fmt::format("Temperature of {} K is above maximum temperature at "
×
175
                    "which data is available of {} K.",
176
          T, data::temperature_max)};
×
177
    }
178
  }
179

180
  if (type_ == Fill::MATERIAL) {
10,012✔
181
    if (instance >= 0) {
9,982✔
182
      // If temperature vector is not big enough, resize it first
183
      if (sqrtkT_.size() != n_instances())
9,905✔
184
        sqrtkT_.resize(n_instances(), sqrtkT_[0]);
45✔
185

186
      // Set temperature for the corresponding instance
187
      sqrtkT_.at(instance) = std::sqrt(K_BOLTZMANN * T);
9,905✔
188
    } else {
189
      // Set temperature for all instances
190
      for (auto& T_ : sqrtkT_) {
154✔
191
        T_ = std::sqrt(K_BOLTZMANN * T);
77✔
192
      }
193
    }
194
  } else {
195
    if (!set_contained) {
30!
196
      throw std::runtime_error {
×
197
        fmt::format("Attempted to set the temperature of cell {} "
×
198
                    "which is not filled by a material.",
199
          id_)};
×
200
    }
201

202
    auto contained_cells = this->get_contained_cells(instance);
30✔
203
    for (const auto& entry : contained_cells) {
120✔
204
      auto& cell = model::cells[entry.first];
90!
205
      assert(cell->type_ == Fill::MATERIAL);
90!
206
      auto& instances = entry.second;
90✔
207
      for (auto instance : instances) {
315✔
208
        cell->set_temperature(T, instance);
225✔
209
      }
210
    }
211
  }
30✔
212
}
10,012✔
213

214
void Cell::set_density(double density, int32_t instance, bool set_contained)
346✔
215
{
216
  if (type_ != Fill::MATERIAL && !set_contained) {
346!
217
    fatal_error(
×
218
      fmt::format("Attempted to set the density multiplier of cell {} "
×
219
                  "which is not filled by a material.",
220
        id_));
×
221
  }
222

223
  if (type_ == Fill::MATERIAL) {
346✔
224
    const int32_t mat_index = material(instance);
331!
225
    if (mat_index == MATERIAL_VOID)
331!
226
      return;
227

228
    if (instance >= 0) {
331✔
229
      // If density multiplier vector is not big enough, resize it first
230
      if (density_mult_.size() != n_instances())
254✔
231
        density_mult_.resize(n_instances(), density_mult_[0]);
111✔
232

233
      // Set density multiplier for the corresponding instance
234
      density_mult_.at(instance) =
254✔
235
        density / model::materials[mat_index]->density_gpcc();
508!
236
    } else {
237
      // Set density multiplier for all instances
238
      for (auto& x : density_mult_) {
154✔
239
        x = density / model::materials[mat_index]->density_gpcc();
154!
240
      }
241
    }
242
  } else {
243
    auto contained_cells = this->get_contained_cells(instance);
15✔
244
    for (const auto& entry : contained_cells) {
60✔
245
      auto& cell = model::cells[entry.first];
45!
246
      assert(cell->type_ == Fill::MATERIAL);
45!
247
      auto& instances = entry.second;
45✔
248
      for (auto instance : instances) {
90✔
249
        cell->set_density(density, instance);
45✔
250
      }
251
    }
252
  }
15✔
253
}
254

255
void Cell::export_properties_hdf5(hid_t group) const
231✔
256
{
257
  // Create a group for this cell.
258
  auto cell_group = create_group(group, fmt::format("cell {}", id_));
231✔
259

260
  // Write temperature in [K] for one or more cell instances
261
  vector<double> temps;
231✔
262
  for (auto sqrtkT_val : sqrtkT_)
429✔
263
    temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
198✔
264
  write_dataset(cell_group, "temperature", temps);
231✔
265

266
  // Write density for one or more cell instances
267
  if (type_ == Fill::MATERIAL && material_.size() > 0) {
231✔
268
    vector<double> density;
198✔
269
    for (int32_t i = 0; i < density_mult_.size(); ++i)
396✔
270
      density.push_back(this->density(i));
198✔
271

272
    write_dataset(cell_group, "density", density);
198✔
273
  }
198✔
274

275
  close_group(cell_group);
231✔
276
}
231✔
277

278
void Cell::import_properties_hdf5(hid_t group)
253✔
279
{
280
  auto cell_group = open_group(group, fmt::format("cell {}", id_));
253✔
281

282
  // Read temperatures from file
283
  vector<double> temps;
253✔
284
  read_dataset(cell_group, "temperature", temps);
253✔
285

286
  // Ensure number of temperatures makes sense
287
  auto n_temps = temps.size();
253✔
288
  if (n_temps > 1 && n_temps != n_instances()) {
253!
289
    fatal_error(fmt::format(
×
290
      "Number of temperatures for cell {} doesn't match number of instances",
291
      id_));
×
292
  }
293

294
  // Modify temperatures for the cell
295
  sqrtkT_.clear();
253✔
296
  sqrtkT_.resize(temps.size());
253✔
297
  for (int64_t i = 0; i < temps.size(); ++i) {
9,922✔
298
    this->set_temperature(temps[i], i);
9,669✔
299
  }
300

301
  // Read densities
302
  if (object_exists(cell_group, "density")) {
253✔
303
    vector<double> density;
198✔
304
    read_dataset(cell_group, "density", density);
198✔
305

306
    // Ensure number of densities makes sense
307
    auto n_density = density.size();
198!
308
    if (n_density > 1 && n_density != n_instances()) {
198!
309
      fatal_error(fmt::format("Number of densities for cell {} "
×
310
                              "doesn't match number of instances",
311
        id_));
×
312
    }
313

314
    // Set densities.
315
    for (int32_t i = 0; i < n_density; ++i) {
396✔
316
      this->set_density(density[i], i);
198✔
317
    }
318
  }
198✔
319

320
  close_group(cell_group);
253✔
321
}
253✔
322

323
void Cell::to_hdf5(hid_t cell_group) const
28,786✔
324
{
325

326
  // Create a group for this cell.
327
  auto group = create_group(cell_group, fmt::format("cell {}", id_));
28,786✔
328

329
  if (!name_.empty()) {
28,786✔
330
    write_string(group, "name", name_, false);
6,276✔
331
  }
332

333
  write_dataset(group, "universe", model::universes[universe_]->id_);
28,786✔
334

335
  to_hdf5_inner(group);
28,786✔
336

337
  // Write fill information.
338
  if (type_ == Fill::MATERIAL) {
28,786✔
339
    write_dataset(group, "fill_type", "material");
23,349✔
340
    std::vector<int32_t> mat_ids;
23,349✔
341
    for (auto i_mat : material_) {
47,991✔
342
      if (i_mat != MATERIAL_VOID) {
24,642✔
343
        mat_ids.push_back(model::materials[i_mat]->id_);
16,239✔
344
      } else {
345
        mat_ids.push_back(MATERIAL_VOID);
8,403✔
346
      }
347
    }
348
    if (mat_ids.size() == 1) {
23,349✔
349
      write_dataset(group, "material", mat_ids[0]);
23,158✔
350
    } else {
351
      write_dataset(group, "material", mat_ids);
191✔
352
    }
353

354
    std::vector<double> temps;
23,349✔
355
    for (auto sqrtkT_val : sqrtkT_)
58,344✔
356
      temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
34,995✔
357
    write_dataset(group, "temperature", temps);
23,349✔
358

359
    write_dataset(group, "density_mult", density_mult_);
23,349✔
360

361
  } else if (type_ == Fill::UNIVERSE) {
28,786✔
362
    write_dataset(group, "fill_type", "universe");
3,939✔
363
    write_dataset(group, "fill", model::universes[fill_]->id_);
3,939✔
364
    if (translation_ != Position(0, 0, 0)) {
3,939✔
365
      write_dataset(group, "translation", translation_);
1,837✔
366
    }
367
    if (!rotation_.empty()) {
3,939✔
368
      if (rotation_.size() == 12) {
264!
369
        std::array<double, 3> rot {rotation_[9], rotation_[10], rotation_[11]};
264✔
370
        write_dataset(group, "rotation", rot);
264✔
371
      } else {
372
        write_dataset(group, "rotation", rotation_);
×
373
      }
374
    }
375

376
  } else if (type_ == Fill::LATTICE) {
1,498!
377
    write_dataset(group, "fill_type", "lattice");
1,498✔
378
    write_dataset(group, "lattice", model::lattices[fill_]->id_);
1,498✔
379
  }
380

381
  close_group(group);
28,786✔
382
}
28,786✔
383

384
//==============================================================================
385
// CSGCell implementation
386
//==============================================================================
387

388
CSGCell::CSGCell(pugi::xml_node cell_node)
35,862✔
389
{
390
  if (check_for_node(cell_node, "id")) {
35,862!
391
    id_ = std::stoi(get_node_value(cell_node, "id"));
71,724✔
392
  } else {
393
    fatal_error("Must specify id of cell in geometry XML file.");
×
394
  }
395

396
  if (check_for_node(cell_node, "name")) {
35,862✔
397
    name_ = get_node_value(cell_node, "name");
8,163✔
398
  }
399

400
  if (check_for_node(cell_node, "universe")) {
35,862✔
401
    universe_ = std::stoi(get_node_value(cell_node, "universe"));
69,454✔
402
  } else {
403
    universe_ = 0;
1,135✔
404
  }
405

406
  // Check if the cell is the base of a virtual triso lattice
407
  bool virtual_lattice_present = check_for_node(cell_node, "virtual_lattice");
35,862✔
408
  if (virtual_lattice_present) {
35,862✔
409
    virtual_lattice_ = get_node_value_bool(cell_node, "virtual_lattice");
15✔
410
    if (virtual_lattice_) {
15!
411
      if (check_for_node(cell_node, "lower_left") &&
30!
412
          check_for_node(cell_node, "pitch") &&
30!
413
          check_for_node(cell_node, "shape")) {
15✔
414
        vl_lower_left_ = get_node_array<double>(cell_node, "lower_left");
15✔
415
        vl_pitch_ = get_node_array<double>(cell_node, "pitch");
15✔
416
        vl_shape_ = get_node_array<int>(cell_node, "shape");
15✔
417
      } else {
NEW
418
        fatal_error(fmt::format("Lower_left, pitch and shape of the virtual "
×
419
                                "lattice must be specified for cell {}",
NEW
420
          id_));
×
421
      }
422
    }
423
  } else {
424
    virtual_lattice_ = false;
35,847✔
425
  }
426

427
  if (check_for_node(cell_node, "triso_particle")) {
35,862✔
428
    triso_particle_ = get_node_value_bool(cell_node, "triso_particle");
1,500✔
429
  } else {
430
    triso_particle_ = false;
34,362✔
431
  }
432

433
  // Make sure that either material or fill was specified, but not both.
434
  bool fill_present = check_for_node(cell_node, "fill");
35,862✔
435
  bool material_present = check_for_node(cell_node, "material");
35,862✔
436
  if (!(fill_present || material_present)) {
35,862!
437
    fatal_error(
×
438
      fmt::format("Neither material nor fill was specified for cell {}", id_));
×
439
  }
440
  if (fill_present && material_present) {
35,862!
441
    fatal_error(fmt::format("Cell {} has both a material and a fill specified; "
×
442
                            "only one can be specified per cell",
443
      id_));
×
444
  }
445

446
  if (fill_present) {
35,862✔
447
    fill_ = std::stoi(get_node_value(cell_node, "fill"));
17,248✔
448
    if (fill_ == universe_) {
8,624!
449
      fatal_error(fmt::format("Cell {} is filled with the same universe that "
×
450
                              "it is contained in.",
451
        id_));
×
452
    }
453
  } else {
454
    fill_ = C_NONE;
27,238✔
455
  }
456

457
  // Read the material element.  There can be zero materials (filled with a
458
  // universe), more than one material (distribmats), and some materials may
459
  // be "void".
460
  if (material_present) {
35,862✔
461
    vector<std::string> mats {
27,238✔
462
      get_node_array<std::string>(cell_node, "material", true)};
27,238✔
463
    if (mats.size() > 0) {
27,238!
464
      material_.reserve(mats.size());
27,238✔
465
      for (std::string mat : mats) {
55,796✔
466
        if (mat.compare("void") == 0) {
28,558✔
467
          material_.push_back(MATERIAL_VOID);
8,733✔
468
        } else {
469
          material_.push_back(std::stoi(mat));
19,825✔
470
        }
471
      }
28,558✔
472
    } else {
473
      fatal_error(fmt::format(
×
474
        "An empty material element was specified for cell {}", id_));
×
475
    }
476
  }
27,238✔
477

478
  // Read the temperature element which may be distributed like materials.
479
  if (check_for_node(cell_node, "temperature")) {
35,862✔
480
    sqrtkT_ = get_node_array<double>(cell_node, "temperature");
386✔
481
    sqrtkT_.shrink_to_fit();
386✔
482

483
    // Make sure this is a material-filled cell.
484
    if (material_.size() == 0) {
386!
485
      fatal_error(fmt::format(
×
486
        "Cell {} was specified with a temperature but no material. Temperature"
487
        "specification is only valid for cells filled with a material.",
488
        id_));
×
489
    }
490

491
    // Make sure all temperatures are non-negative.
492
    for (auto T : sqrtkT_) {
1,852✔
493
      if (T < 0) {
1,466!
494
        fatal_error(fmt::format(
×
495
          "Cell {} was specified with a negative temperature", id_));
×
496
      }
497
    }
498

499
    // Convert to sqrt(k*T).
500
    for (auto& T : sqrtkT_) {
1,852✔
501
      T = std::sqrt(K_BOLTZMANN * T);
1,466✔
502
    }
503
  }
504

505
  // Read the density element which can be distributed similar to temperature.
506
  // These get assigned to the density multiplier, requiring a division by
507
  // the material density.
508
  // Note: calculating the actual density multiplier is deferred until materials
509
  // are finalized. density_mult_ contains the true density in the meantime.
510
  if (check_for_node(cell_node, "density")) {
35,862✔
511
    density_mult_ = get_node_array<double>(cell_node, "density");
75✔
512
    density_mult_.shrink_to_fit();
75✔
513

514
    // Make sure this is a material-filled cell.
515
    if (material_.size() == 0) {
75!
516
      fatal_error(fmt::format(
×
517
        "Cell {} was specified with a density but no material. Density"
518
        "specification is only valid for cells filled with a material.",
519
        id_));
×
520
    }
521

522
    // Make sure this is a non-void material.
523
    for (auto mat_id : material_) {
150✔
524
      if (mat_id == MATERIAL_VOID) {
75!
525
        fatal_error(fmt::format(
×
526
          "Cell {} was specified with a density, but contains a void "
527
          "material. Density specification is only valid for cells "
528
          "filled with a non-void material.",
529
          id_));
×
530
      }
531
    }
532

533
    // Make sure all densities are non-negative and greater than zero.
534
    for (auto rho : density_mult_) {
1,230✔
535
      if (rho <= 0) {
1,155!
536
        fatal_error(fmt::format(
×
537
          "Cell {} was specified with a density less than or equal to zero",
538
          id_));
×
539
      }
540
    }
541
  }
542

543
  // Read the region specification.
544
  std::string region_spec;
35,862✔
545
  if (check_for_node(cell_node, "region")) {
35,862✔
546
    region_spec = get_node_value(cell_node, "region");
26,868✔
547
  }
548

549
  // Get a tokenized representation of the region specification and apply De
550
  // Morgans law
551
  Region region(region_spec, id_);
35,862✔
552
  region_ = region;
35,862✔
553
  vector<int32_t> rpn = region_.generate_postfix(id_);
35,862✔
554

555
  if (virtual_lattice_) {
35,862✔
556
    vl_triso_distribution_ = generate_triso_distribution(
45✔
557
      vl_shape_, vl_pitch_, vl_lower_left_, rpn, id_);
30✔
558
  }
559

560
  if (triso_particle_) {
35,862✔
561
    if (rpn.size() != 1) {
1,500!
NEW
562
      fatal_error(
×
NEW
563
        fmt::format("Wrong surface definition of triso particle cell {}", id_));
×
564
    } else {
565
      model::surfaces[abs(rpn[0]) - 1]->connect_to_triso_base(id_, "particle");
3,000✔
566
    }
567
  }
568

569
  // Read the translation vector.
570
  if (check_for_node(cell_node, "translation")) {
35,862✔
571
    if (fill_ == C_NONE) {
4,057!
572
      fatal_error(fmt::format("Cannot apply a translation to cell {}"
×
573
                              " because it is not filled with another universe",
574
        id_));
×
575
    }
576

577
    auto xyz {get_node_array<double>(cell_node, "translation")};
4,057✔
578
    if (xyz.size() != 3) {
4,057!
579
      fatal_error(
×
580
        fmt::format("Non-3D translation vector applied to cell {}", id_));
×
581
    }
582
    translation_ = xyz;
4,057✔
583
  }
4,057✔
584

585
  // Read the rotation transform.
586
  if (check_for_node(cell_node, "rotation")) {
35,862✔
587
    auto rot {get_node_array<double>(cell_node, "rotation")};
390✔
588
    set_rotation(rot);
390✔
589
  }
390✔
590
}
35,862✔
591

592
//==============================================================================
593

594
void CSGCell::to_hdf5_inner(hid_t group_id) const
28,622✔
595
{
596
  write_string(group_id, "geom_type", "csg", false);
28,622✔
597
  write_string(group_id, "region", region_.str(), false);
28,622✔
598
}
28,622✔
599

600
//==============================================================================
601

602
vector<int32_t>::iterator CSGCell::find_left_parenthesis(
×
603
  vector<int32_t>::iterator start, const vector<int32_t>& infix)
604
{
605
  // start search at zero
606
  int parenthesis_level = 0;
×
607
  auto it = start;
×
608
  while (it != infix.begin()) {
×
609
    // look at two tokens at a time
610
    int32_t one = *it;
×
611
    int32_t two = *(it - 1);
×
612

613
    // decrement parenthesis level if there are two adjacent surfaces
614
    if (one < OP_UNION && two < OP_UNION) {
×
615
      parenthesis_level--;
×
616
      // increment if there are two adjacent operators
617
    } else if (one >= OP_UNION && two >= OP_UNION) {
×
618
      parenthesis_level++;
×
619
    }
620

621
    // if the level gets to zero, return the position
622
    if (parenthesis_level == 0) {
×
623
      // move the iterator back one before leaving the loop
624
      // so that all tokens in the parenthesis block are included
625
      it--;
×
626
      break;
627
    }
628

629
    // continue loop, one token at a time
630
    it--;
631
  }
632
  return it;
×
633
}
634
std::pair<double, int32_t> CSGCell::distance(
2,147,483,647✔
635
  Position r, Direction u, int32_t on_surface, GeometryState* p) const
636
{
637
  if (virtual_lattice_) {
2,147,483,647✔
638
    return distance_in_virtual_lattice(r, u, on_surface, p);
4,899,400✔
639
  } else {
640
    return region_.distance(r, u, on_surface);
2,147,483,647✔
641
  }
642
}
643

644
std::pair<double, int32_t> CSGCell::distance_in_virtual_lattice(
4,899,400✔
645
  Position r, Direction u, int32_t on_surface, GeometryState* p) const
646
{
647
  double min_dist {INFTY};
4,899,400✔
648
  int32_t i_surf {std::numeric_limits<int32_t>::max()};
4,899,400✔
649
  double min_dis_vl;
4,899,400✔
650
  int32_t i_surf_vl;
4,899,400✔
651

652
  double max_dis = p->collision_distance();
4,899,400✔
653
  double tol_dis = 0;
4,899,400✔
654
  vector<double> dis_to_bou(3), dis_to_bou_max(3);
4,899,400✔
655
  double u_value = sqrt(pow(u.x, 2) + pow(u.y, 2) +
9,798,800✔
656
                        pow(u.z, 2)); // don't know if u has been normalized
4,899,400✔
657
  vector<double> norm_u = {u.x / u_value, u.y / u_value, u.z / u_value};
4,899,400✔
658
  vector<int> lat_ind(3);
4,899,400✔
659
  vector<double> temp_pos = {r.x, r.y, r.z};
4,899,400✔
660
  int loop_time;
4,899,400✔
661
  for (int i = 0; i < 3; i++) {
19,597,600✔
662
    lat_ind[i] = floor((temp_pos[i] - vl_lower_left_[i]) / vl_pitch_[i]);
14,698,200✔
663
    if (lat_ind[i] == vl_shape_[i] && norm_u[i] < 0) {
14,698,200!
664
      lat_ind[i] = vl_shape_[i] - 1;
1,585,540✔
665
    }
666
    if (lat_ind[i] == -1 && norm_u[i] > 0) {
14,698,200!
667
      lat_ind[i] = 0;
10,956✔
668
    }
669
  }
670

671
  dis_to_bou = {INFTY, INFTY, INFTY};
4,899,400✔
672
  for (int i = 0; i < 3; i++) {
19,597,600✔
673
    if (norm_u[i] > 0) {
14,698,200✔
674
      dis_to_bou[i] = std::abs(
7,353,841✔
675
        ((lat_ind[i] + 1) * vl_pitch_[i] + vl_lower_left_[i] - temp_pos[i]) /
7,353,841✔
676
        norm_u[i]);
7,353,841✔
677
      dis_to_bou_max[i] = vl_pitch_[i] / norm_u[i];
7,353,841✔
678
    } else if (norm_u[i] < 0) {
7,344,359!
679
      dis_to_bou[i] =
7,344,359✔
680
        std::abs((lat_ind[i] * vl_pitch_[i] + vl_lower_left_[i] - temp_pos[i]) /
7,344,359✔
681
                 norm_u[i]);
7,344,359✔
682
      dis_to_bou_max[i] = -vl_pitch_[i] / norm_u[i];
7,344,359✔
683
    }
684
  }
685

686
  while (true) {
14,198,646✔
687
    if (lat_ind[0] < 0 || lat_ind[0] >= vl_shape_[0] || lat_ind[1] < 0 ||
14,198,646✔
688
        lat_ind[1] >= vl_shape_[1] || lat_ind[2] < 0 ||
26,807,880✔
689
        lat_ind[2] >= vl_shape_[2])
11,553,113✔
690
      break;
691

692
    for (int token :
66,449,009✔
693
      vl_triso_distribution_[lat_ind[0] + lat_ind[1] * vl_shape_[0] +
11,023,848✔
694
                             lat_ind[2] * vl_shape_[0] * vl_shape_[1]]) {
77,472,857✔
695
      bool coincident {std::abs(token) == std::abs(on_surface)};
66,449,009✔
696
      double d {model::surfaces[abs(token) - 1]->distance(r, u, coincident)};
66,449,009✔
697
      if (d < min_dist) {
66,449,009✔
698
        if (min_dist - d >= FP_PRECISION * min_dist) {
1,262,503!
699
          min_dist = d;
1,262,503✔
700
          i_surf = -token;
1,262,503✔
701
        }
702
      }
703
    }
704

705
    int mes_bou_crossed = 0;
11,023,848✔
706
    if (dis_to_bou[1] < dis_to_bou[0]) {
11,023,848✔
707
      mes_bou_crossed = 1;
5,499,956✔
708
    }
709
    if (dis_to_bou[2] < dis_to_bou[mes_bou_crossed]) {
11,023,848✔
710
      mes_bou_crossed = 2;
3,669,820✔
711
    }
712

713
    tol_dis = dis_to_bou[mes_bou_crossed];
11,023,848✔
714

715
    if (min_dist < tol_dis) {
11,023,848✔
716
      break;
717
    }
718

719
    if (norm_u[mes_bou_crossed] > 0) {
9,800,450✔
720
      lat_ind[mes_bou_crossed] += 1;
4,899,433✔
721
    } else {
722
      lat_ind[mes_bou_crossed] += -1;
4,901,017✔
723
    }
724

725
    dis_to_bou[mes_bou_crossed] += dis_to_bou_max[mes_bou_crossed];
9,800,450✔
726

727
    if (tol_dis > max_dis) {
9,800,450✔
728
      break;
729
    }
730
  }
731
  return {min_dist, i_surf};
4,899,400✔
732
}
4,899,400✔
733

734
//==============================================================================
735
// Region implementation
736
//==============================================================================
737

738
Region::Region(std::string region_spec, int32_t cell_id)
35,961✔
739
{
740
  // Check if region_spec is not empty.
741
  if (!region_spec.empty()) {
35,961✔
742
    // Parse all halfspaces and operators except for intersection (whitespace).
743
    for (int i = 0; i < region_spec.size();) {
154,962✔
744
      if (region_spec[i] == '(') {
127,995✔
745
        expression_.push_back(OP_LEFT_PAREN);
1,290✔
746
        i++;
1,290✔
747

748
      } else if (region_spec[i] == ')') {
126,705✔
749
        expression_.push_back(OP_RIGHT_PAREN);
1,290✔
750
        i++;
1,290✔
751

752
      } else if (region_spec[i] == '|') {
125,415✔
753
        expression_.push_back(OP_UNION);
3,823✔
754
        i++;
3,823✔
755

756
      } else if (region_spec[i] == '~') {
121,592✔
757
        expression_.push_back(OP_COMPLEMENT);
30✔
758
        i++;
30✔
759

760
      } else if (region_spec[i] == '-' || region_spec[i] == '+' ||
205,032!
761
                 std::isdigit(region_spec[i])) {
83,470✔
762
        // This is the start of a halfspace specification.  Iterate j until we
763
        // find the end, then push-back everything between i and j.
764
        int j = i + 1;
71,607✔
765
        while (j < region_spec.size() && std::isdigit(region_spec[j])) {
149,178✔
766
          j++;
77,571✔
767
        }
768
        expression_.push_back(std::stoi(region_spec.substr(i, j - i)));
143,214✔
769
        i = j;
71,607✔
770

771
      } else if (std::isspace(region_spec[i])) {
49,955!
772
        i++;
49,955✔
773

774
      } else {
775
        auto err_msg =
×
776
          fmt::format("Region specification contains invalid character, \"{}\"",
777
            region_spec[i]);
×
778
        fatal_error(err_msg);
×
779
      }
×
780
    }
781

782
    // Add in intersection operators where a missing operator is needed.
783
    int i = 0;
784
    while (i < expression_.size() - 1) {
118,857✔
785
      bool left_compat {
91,890✔
786
        (expression_[i] < OP_UNION) || (expression_[i] == OP_RIGHT_PAREN)};
91,890✔
787
      bool right_compat {(expression_[i + 1] < OP_UNION) ||
91,890✔
788
                         (expression_[i + 1] == OP_LEFT_PAREN) ||
91,890✔
789
                         (expression_[i + 1] == OP_COMPLEMENT)};
5,173✔
790
      if (left_compat && right_compat) {
91,890✔
791
        expression_.insert(expression_.begin() + i + 1, OP_INTERSECTION);
40,817✔
792
      }
793
      i++;
794
    }
795

796
    // Remove complement operators using DeMorgan's laws
797
    auto it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
26,967✔
798
    while (it != expression_.end()) {
26,997✔
799
      // Erase complement
800
      expression_.erase(it);
30✔
801

802
      // Define stop given left parenthesis or not
803
      auto stop = it;
30✔
804
      if (*it == OP_LEFT_PAREN) {
30!
805
        int depth = 1;
806
        do {
240✔
807
          stop++;
240✔
808
          if (*stop > OP_COMPLEMENT) {
240✔
809
            if (*stop == OP_RIGHT_PAREN) {
30!
810
              depth--;
30✔
811
            } else {
812
              depth++;
×
813
            }
814
          }
815
        } while (depth > 0);
240✔
816
        it++;
30✔
817
      }
818

819
      // apply DeMorgan's law to any surfaces/operators between these
820
      // positions in the RPN
821
      apply_demorgan(it, stop);
30✔
822
      // update iterator position
823
      it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
30✔
824
    }
825

826
    // Convert user IDs to surface indices.
827
    for (auto& r : expression_) {
145,794✔
828
      if (r < OP_UNION) {
118,827✔
829
        const auto& it {model::surface_map.find(abs(r))};
71,607!
830
        if (it == model::surface_map.end()) {
71,607!
831
          throw std::runtime_error {
×
832
            "Invalid surface ID " + std::to_string(abs(r)) +
×
833
            " specified in region for cell " + std::to_string(cell_id) + "."};
×
834
        }
835
        r = (r > 0) ? it->second + 1 : -(it->second + 1);
71,607✔
836
      }
837
    }
838

839
    // Check if this is a simple cell.
840
    simple_ = true;
26,967✔
841
    for (int32_t token : expression_) {
132,764✔
842
      if (token == OP_UNION) {
106,985✔
843
        simple_ = false;
1,188✔
844
        // Ensure intersections have precedence over unions
845
        enforce_precedence();
1,188✔
846
        break;
847
      }
848
    }
849

850
    // If this cell is simple, remove all the superfluous operator tokens.
851
    if (simple_) {
26,967✔
852
      for (auto it = expression_.begin(); it != expression_.end(); it++) {
125,218✔
853
        if (*it == OP_INTERSECTION || *it > OP_COMPLEMENT) {
99,439!
854
          expression_.erase(it);
36,830✔
855
          it--;
99,439✔
856
        }
857
      }
858
    }
859
    expression_.shrink_to_fit();
26,967✔
860

861
  } else {
862
    simple_ = true;
8,994✔
863
  }
864
}
35,961✔
865

866
//==============================================================================
867

868
void Region::apply_demorgan(
30✔
869
  vector<int32_t>::iterator start, vector<int32_t>::iterator stop)
870
{
871
  do {
210✔
872
    if (*start < OP_UNION) {
210✔
873
      *start *= -1;
120✔
874
    } else if (*start == OP_UNION) {
90!
875
      *start = OP_INTERSECTION;
×
876
    } else if (*start == OP_INTERSECTION) {
90!
877
      *start = OP_UNION;
90✔
878
    }
879
    start++;
210✔
880
  } while (start < stop);
210✔
881
}
30✔
882

883
//==============================================================================
884
//! Add precedence for infix regions so intersections have higher
885
//! precedence than unions using parentheses.
886
//==============================================================================
887

888
void Region::add_parentheses(int64_t start)
96✔
889
{
890
  int32_t start_token = expression_[start];
96!
891
  // Add left parenthesis and set new position to be after parenthesis
892
  if (start_token == OP_UNION) {
96!
893
    start += 2;
×
894
  }
895
  expression_.insert(expression_.begin() + start - 1, OP_LEFT_PAREN);
96✔
896

897
  // Add right parenthesis
898
  // While the start iterator is within the bounds of infix
899
  while (start + 1 < expression_.size()) {
430✔
900
    start++;
408✔
901

902
    // If the current token is an operator and is different than the start token
903
    if (expression_[start] >= OP_UNION && expression_[start] != start_token) {
408✔
904
      // Skip wrapped regions but save iterator position to check precedence and
905
      // add right parenthesis, right parenthesis position depends on the
906
      // operator, when the operator is a union then do not include the operator
907
      // in the region, when the operator is an intersection then include the
908
      // operator and next surface
909
      if (expression_[start] == OP_LEFT_PAREN) {
85✔
910
        int depth = 1;
911
        do {
44✔
912
          start++;
44✔
913
          if (expression_[start] > OP_COMPLEMENT) {
44✔
914
            if (expression_[start] == OP_RIGHT_PAREN) {
11!
915
              depth--;
11✔
916
            } else {
917
              depth++;
×
918
            }
919
          }
920
        } while (depth > 0);
44✔
921
      } else {
922
        if (start_token == OP_UNION) {
74!
923
          --start;
×
924
        }
925
        expression_.insert(expression_.begin() + start, OP_RIGHT_PAREN);
74✔
926
        return;
74✔
927
      }
928
    }
929
  }
930
  // If we get here a right parenthesis hasn't been placed
931
  expression_.push_back(OP_RIGHT_PAREN);
22✔
932
}
933

934
//==============================================================================
935
//! Add parentheses to enforce operator precedence in region expressions
936
//!
937
//! This function ensures that intersection operators have higher precedence
938
//! than union operators by adding parentheses where needed. For example:
939
//!   "1 2 | 3" becomes "(1 2) | 3"
940
//!   "1 | 2 3" becomes "1 | (2 3)"
941
//!
942
//! The algorithm uses stacks to track the current operator type and its
943
//! position at each parenthesis depth level. When it encounters a different
944
//! operator at the same depth, it adds parentheses to group the
945
//! higher-precedence operations.
946
//==============================================================================
947

948
void Region::enforce_precedence()
1,188✔
949
{
950
  // Stack tracking the operator type at each depth (0 = no operator seen yet)
951
  vector<int32_t> op_stack = {0};
1,188✔
952

953
  // Stack tracking where the operator sequence started at each depth
954
  vector<std::size_t> pos_stack = {0};
1,188✔
955

956
  for (int64_t i = 0; i < expression_.size(); ++i) {
21,593✔
957
    int32_t token = expression_[i];
20,405✔
958

959
    if (token == OP_LEFT_PAREN) {
20,405✔
960
      // Entering a new parenthesis level - push new tracking state
961
      op_stack.push_back(0);
1,486✔
962
      pos_stack.push_back(0);
1,486✔
963
      continue;
1,486✔
964
    } else if (token == OP_RIGHT_PAREN) {
18,919✔
965
      // Exiting a parenthesis level - pop tracking state (keep at least one)
966
      if (op_stack.size() > 1) {
1,445!
967
        op_stack.pop_back();
1,445✔
968
        pos_stack.pop_back();
1,445✔
969
      }
970
      continue;
1,445✔
971
    }
972

973
    if (token == OP_UNION || token == OP_INTERSECTION) {
17,474✔
974
      if (op_stack.back() == 0) {
8,143✔
975
        // First operator at this depth - record it and its position
976
        op_stack.back() = token;
2,718✔
977
        pos_stack.back() = i;
2,718✔
978
      } else if (token != op_stack.back()) {
5,425✔
979
        // Encountered a different operator at the same depth - need to add
980
        // parentheses to enforce precedence. Intersection has higher
981
        // precedence, so we parenthesize the intersection terms.
982
        if (op_stack.back() == OP_INTERSECTION) {
96✔
983
          add_parentheses(pos_stack.back());
48✔
984
        } else {
985
          add_parentheses(i);
48✔
986
        }
987

988
        // Restart the scan since we modified the expression
989
        i = -1; // Will be incremented to 0 by the for loop
96✔
990
        op_stack = {0};
96✔
991
        pos_stack = {0};
96✔
992
      }
993
    }
994
  }
995
}
1,188✔
996

997
//==============================================================================
998
//! Convert infix region specification to Reverse Polish Notation (RPN)
999
//!
1000
//! This function uses the shunting-yard algorithm.
1001
//==============================================================================
1002

1003
vector<int32_t> Region::generate_postfix(int32_t cell_id) const
35,906✔
1004
{
1005
  vector<int32_t> rpn;
35,906✔
1006
  vector<int32_t> stack;
35,906✔
1007

1008
  for (int32_t token : expression_) {
118,084✔
1009
    if (token < OP_UNION) {
82,178✔
1010
      // If token is not an operator, add it to output
1011
      rpn.push_back(token);
71,618✔
1012
    } else if (token < OP_RIGHT_PAREN) {
10,560✔
1013
      // Regular operators union, intersection, complement
1014
      while (stack.size() > 0) {
13,321✔
1015
        int32_t op = stack.back();
9,977✔
1016

1017
        if (op < OP_RIGHT_PAREN && ((token == OP_COMPLEMENT && token < op) ||
9,977!
1018
                                     (token != OP_COMPLEMENT && token <= op))) {
5,423!
1019
          // While there is an operator, op, on top of the stack, if the token
1020
          // is left-associative and its precedence is less than or equal to
1021
          // that of op or if the token is right-associative and its precedence
1022
          // is less than that of op, move op to the output queue and push the
1023
          // token on to the stack. Note that only complement is
1024
          // right-associative.
1025
          rpn.push_back(op);
5,423✔
1026
          stack.pop_back();
5,423✔
1027
        } else {
1028
          break;
1029
        }
1030
      }
1031

1032
      stack.push_back(token);
7,898✔
1033

1034
    } else if (token == OP_LEFT_PAREN) {
2,662✔
1035
      // If the token is a left parenthesis, push it onto the stack
1036
      stack.push_back(token);
1,331✔
1037

1038
    } else {
1039
      // If the token is a right parenthesis, move operators from the stack to
1040
      // the output queue until reaching the left parenthesis.
1041
      for (auto it = stack.rbegin(); *it != OP_LEFT_PAREN; it++) {
2,662✔
1042
        // If we run out of operators without finding a left parenthesis, it
1043
        // means there are mismatched parentheses.
1044
        if (it == stack.rend()) {
1,331!
1045
          fatal_error(fmt::format(
×
1046
            "Mismatched parentheses in region specification for cell {}",
1047
            cell_id));
1048
        }
1049
        rpn.push_back(stack.back());
1,331✔
1050
        stack.pop_back();
1,331✔
1051
      }
1052

1053
      // Pop the left parenthesis.
1054
      stack.pop_back();
82,178✔
1055
    }
1056
  }
1057

1058
  while (stack.size() > 0) {
35,906✔
1059
    int32_t op = stack.back();
1,144!
1060

1061
    // If the operator is a parenthesis it is mismatched.
1062
    if (op >= OP_RIGHT_PAREN) {
1,144!
1063
      fatal_error(fmt::format(
×
1064
        "Mismatched parentheses in region specification for cell {}", cell_id));
1065
    }
1066

1067
    rpn.push_back(stack.back());
1,144✔
1068
    stack.pop_back();
37,050✔
1069
  }
1070

1071
  return rpn;
35,906✔
1072
}
35,906✔
1073

1074
//==============================================================================
1075

1076
std::string Region::str() const
28,721✔
1077
{
1078
  std::stringstream region_spec {};
28,721✔
1079
  if (!expression_.empty()) {
28,721✔
1080
    for (int32_t token : expression_) {
85,370✔
1081
      if (token == OP_LEFT_PAREN) {
64,956✔
1082
        region_spec << " (";
1,218✔
1083
      } else if (token == OP_RIGHT_PAREN) {
63,738✔
1084
        region_spec << " )";
1,218✔
1085
      } else if (token == OP_COMPLEMENT) {
62,520!
1086
        region_spec << " ~";
×
1087
      } else if (token == OP_INTERSECTION) {
62,520✔
1088
      } else if (token == OP_UNION) {
59,179✔
1089
        region_spec << " |";
3,417✔
1090
      } else {
1091
        // Note the off-by-one indexing
1092
        auto surf_id = model::surfaces[abs(token) - 1]->id_;
55,762✔
1093
        region_spec << " " << ((token > 0) ? surf_id : -surf_id);
55,762✔
1094
      }
1095
    }
1096
  }
1097
  return region_spec.str();
57,442✔
1098
}
28,721✔
1099

1100
//==============================================================================
1101

1102
std::pair<double, int32_t> Region::distance(
2,147,483,647✔
1103
  Position r, Direction u, int32_t on_surface) const
1104
{
1105
  double min_dist {INFTY};
2,147,483,647✔
1106
  int32_t i_surf {std::numeric_limits<int32_t>::max()};
2,147,483,647✔
1107

1108
  for (int32_t token : expression_) {
2,147,483,647✔
1109
    // Ignore this token if it corresponds to an operator rather than a region.
1110
    if (token >= OP_UNION)
2,147,483,647✔
1111
      continue;
174,174,736✔
1112

1113
    // Calculate the distance to this surface.
1114
    // Note the off-by-one indexing
1115
    bool coincident {std::abs(token) == std::abs(on_surface)};
2,147,483,647✔
1116
    double d {model::surfaces[abs(token) - 1]->distance(r, u, coincident)};
2,147,483,647✔
1117

1118
    // Check if this distance is the new minimum.
1119
    if (d < min_dist) {
2,147,483,647✔
1120
      if (min_dist - d >= FP_PRECISION * min_dist) {
2,147,483,647!
1121
        min_dist = d;
2,147,483,647✔
1122
        i_surf = -token;
2,147,483,647✔
1123
      }
1124
    }
1125
  }
1126

1127
  return {min_dist, i_surf};
2,147,483,647✔
1128
}
1129

1130
//==============================================================================
1131

1132
bool Region::contains(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
1133
{
1134
  if (simple_) {
2,147,483,647✔
1135
    return contains_simple(r, u, on_surface);
2,147,483,647✔
1136
  } else {
1137
    return contains_complex(r, u, on_surface);
7,414,332✔
1138
  }
1139
}
1140

1141
//==============================================================================
1142

1143
bool Region::contains_simple(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
1144
{
1145
  for (int32_t token : expression_) {
2,147,483,647✔
1146
    // Assume that no tokens are operators. Evaluate the sense of particle with
1147
    // respect to the surface and see if the token matches the sense. If the
1148
    // particle's surface attribute is set and matches the token, that
1149
    // overrides the determination based on sense().
1150
    if (token == on_surface) {
2,147,483,647✔
1151
    } else if (-token == on_surface) {
2,147,483,647✔
1152
      return false;
1153
    } else {
1154
      // Note the off-by-one indexing
1155
      bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
2,147,483,647✔
1156
      if (sense != (token > 0)) {
2,147,483,647✔
1157
        return false;
1158
      }
1159
    }
1160
  }
1161
  return true;
1162
}
1163

1164
//==============================================================================
1165

1166
bool Region::contains_complex(Position r, Direction u, int32_t on_surface) const
7,414,332✔
1167
{
1168
  bool in_cell = true;
7,414,332✔
1169
  int total_depth = 0;
7,414,332✔
1170

1171
  // For each token
1172
  for (auto it = expression_.begin(); it != expression_.end(); it++) {
116,387,006✔
1173
    int32_t token = *it;
110,921,243✔
1174

1175
    // If the token is a surface evaluate the sense
1176
    // If the token is a union or intersection check to
1177
    // short circuit
1178
    if (token < OP_UNION) {
110,921,243✔
1179
      if (token == on_surface) {
48,482,443✔
1180
        in_cell = true;
1181
      } else if (-token == on_surface) {
44,822,457✔
1182
        in_cell = false;
1183
      } else {
1184
        // Note the off-by-one indexing
1185
        bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
44,112,139✔
1186
        in_cell = (sense == (token > 0));
44,112,139✔
1187
      }
1188
    } else if ((token == OP_UNION && in_cell == true) ||
62,438,800✔
1189
               (token == OP_INTERSECTION && in_cell == false)) {
30,063,045✔
1190
      // If the total depth is zero return
1191
      if (total_depth == 0) {
6,679,420✔
1192
        return in_cell;
1,948,569✔
1193
      }
1194

1195
      total_depth--;
4,730,851✔
1196

1197
      // While the iterator is within the bounds of the vector
1198
      int depth = 1;
4,730,851✔
1199
      do {
29,501,578✔
1200
        // Get next token
1201
        it++;
29,501,578✔
1202
        int32_t next_token = *it;
29,501,578✔
1203

1204
        // If the token is an a parenthesis
1205
        if (next_token > OP_COMPLEMENT) {
29,501,578✔
1206
          // Adjust depth accordingly
1207
          if (next_token == OP_RIGHT_PAREN) {
4,922,311✔
1208
            depth--;
4,826,581✔
1209
          } else {
1210
            depth++;
95,730✔
1211
          }
1212
        }
1213
      } while (depth > 0);
29,501,578✔
1214
    } else if (token == OP_LEFT_PAREN) {
55,759,380✔
1215
      total_depth++;
9,711,060✔
1216
    } else if (token == OP_RIGHT_PAREN) {
46,048,320✔
1217
      total_depth--;
4,980,209✔
1218
    }
1219
  }
1220
  return in_cell;
1221
}
1222

1223
//==============================================================================
1224

1225
BoundingBox Region::bounding_box(int32_t cell_id) const
88✔
1226
{
1227
  if (simple_) {
88✔
1228
    return bounding_box_simple();
44✔
1229
  } else {
1230
    auto postfix = generate_postfix(cell_id);
44✔
1231
    return bounding_box_complex(postfix);
88✔
1232
  }
44✔
1233
}
1234

1235
//==============================================================================
1236

1237
BoundingBox Region::bounding_box_simple() const
44✔
1238
{
1239
  BoundingBox bbox;
44✔
1240
  for (int32_t token : expression_) {
176✔
1241
    bbox &= model::surfaces[abs(token) - 1]->bounding_box(token > 0);
132✔
1242
  }
1243
  return bbox;
44✔
1244
}
1245

1246
//==============================================================================
1247

1248
BoundingBox Region::bounding_box_complex(vector<int32_t> postfix) const
44✔
1249
{
1250
  vector<BoundingBox> stack(postfix.size());
44✔
1251
  int i_stack = -1;
44✔
1252

1253
  for (auto& token : postfix) {
792✔
1254
    if (token == OP_UNION) {
748✔
1255
      stack[i_stack - 1] = stack[i_stack - 1] | stack[i_stack];
154✔
1256
      i_stack--;
154✔
1257
    } else if (token == OP_INTERSECTION) {
594✔
1258
      stack[i_stack - 1] = stack[i_stack - 1] & stack[i_stack];
198✔
1259
      i_stack--;
198✔
1260
    } else {
1261
      i_stack++;
396✔
1262
      stack[i_stack] = model::surfaces[abs(token) - 1]->bounding_box(token > 0);
396✔
1263
    }
1264
  }
1265

1266
  assert(i_stack == 0);
44!
1267
  return stack.front();
44✔
1268
}
44✔
1269

1270
//==============================================================================
1271

1272
vector<int32_t> Region::surfaces() const
6,785✔
1273
{
1274
  if (simple_) {
6,785✔
1275
    return expression_;
6,765✔
1276
  }
1277

1278
  vector<int32_t> surfaces = expression_;
20✔
1279

1280
  auto it = std::find_if(surfaces.begin(), surfaces.end(),
20✔
1281
    [&](const auto& value) { return value >= OP_UNION; });
20!
1282

1283
  while (it != surfaces.end()) {
60✔
1284
    surfaces.erase(it);
40✔
1285

1286
    it = std::find_if(surfaces.begin(), surfaces.end(),
40✔
1287
      [&](const auto& value) { return value >= OP_UNION; });
80!
1288
  }
1289

1290
  return surfaces;
20✔
1291
}
6,785✔
1292

1293
//==============================================================================
1294
// Non-method functions
1295
//==============================================================================
1296

1297
void read_cells(pugi::xml_node node)
8,228✔
1298
{
1299
  // Count the number of cells.
1300
  int n_cells = 0;
8,228✔
1301
  for (pugi::xml_node cell_node : node.children("cell")) {
44,090✔
1302
    n_cells++;
35,862✔
1303
  }
1304

1305
  // Loop over XML cell elements and populate the array.
1306
  model::cells.reserve(n_cells);
8,228✔
1307
  for (pugi::xml_node cell_node : node.children("cell")) {
44,090✔
1308
    model::cells.push_back(make_unique<CSGCell>(cell_node));
35,862✔
1309
  }
1310

1311
  // Fill the cell map.
1312
  for (int i = 0; i < model::cells.size(); i++) {
44,090✔
1313
    int32_t id = model::cells[i]->id_;
35,862!
1314
    auto search = model::cell_map.find(id);
35,862!
1315
    if (search == model::cell_map.end()) {
35,862!
1316
      model::cell_map[id] = i;
35,862✔
1317
    } else {
1318
      fatal_error(
×
1319
        fmt::format("Two or more cells use the same unique ID: {}", id));
×
1320
    }
1321
  }
1322

1323
  read_dagmc_universes(node);
8,228✔
1324

1325
  populate_universes();
8,226✔
1326

1327
  // Allocate the cell overlap count if necessary.
1328
  if (settings::check_overlaps) {
8,226✔
1329
    model::overlap_check_count.resize(model::cells.size(), 0);
119✔
1330
  }
1331

1332
  if (model::cells.size() == 0) {
8,226!
1333
    fatal_error("No cells were found in the geometry.xml file");
×
1334
  }
1335
}
8,226✔
1336

1337
void populate_universes()
8,228✔
1338
{
1339
  // Used to map universe index to the index of an implicit complement cell for
1340
  // DAGMC universes
1341
  std::unordered_map<int, int> implicit_comp_cells;
8,228✔
1342

1343
  // Populate the Universe vector and map.
1344
  for (int index_cell = 0; index_cell < model::cells.size(); index_cell++) {
44,306✔
1345
    int32_t uid = model::cells[index_cell]->universe_;
36,078✔
1346
    auto it = model::universe_map.find(uid);
36,078✔
1347
    if (it == model::universe_map.end()) {
36,078✔
1348
      model::universes.push_back(make_unique<Universe>());
39,706✔
1349
      model::universes.back()->id_ = uid;
19,853✔
1350
      model::universes.back()->cells_.push_back(index_cell);
19,853✔
1351
      model::universe_map[uid] = model::universes.size() - 1;
19,853✔
1352
    } else {
1353
#ifdef OPENMC_DAGMC_ENABLED
1354
      // Skip implicit complement cells for now
1355
      Universe* univ = model::universes[it->second].get();
2,190!
1356
      DAGUniverse* dag_univ = dynamic_cast<DAGUniverse*>(univ);
2,190!
1357
      if (dag_univ && (dag_univ->implicit_complement_idx() == index_cell)) {
2,190✔
1358
        implicit_comp_cells[it->second] = index_cell;
46✔
1359
        continue;
46✔
1360
      }
1361
#endif
1362

1363
      model::universes[it->second]->cells_.push_back(index_cell);
16,179✔
1364
    }
1365
    if (model::cells[index_cell]->virtual_lattice_) {
36,032✔
1366
      model::universes[it->second]->filled_with_triso_base_ =
15✔
1367
        model::cells[index_cell]->id_;
15✔
1368
    }
1369
  }
1370

1371
  // Add DAGUniverse implicit complement cells last
1372
  for (const auto& it : implicit_comp_cells) {
8,274✔
1373
    int index_univ = it.first;
46✔
1374
    int index_cell = it.second;
46✔
1375
    model::universes[index_univ]->cells_.push_back(index_cell);
46!
1376
  }
1377

1378
  model::universes.shrink_to_fit();
8,228✔
1379
}
8,228✔
1380

1381
//==============================================================================
1382
// C-API functions
1383
//==============================================================================
1384

1385
extern "C" int openmc_cell_get_fill(
259✔
1386
  int32_t index, int* type, int32_t** indices, int32_t* n)
1387
{
1388
  if (index >= 0 && index < model::cells.size()) {
259!
1389
    Cell& c {*model::cells[index]};
259✔
1390
    *type = static_cast<int>(c.type_);
259✔
1391
    if (c.type_ == Fill::MATERIAL) {
259✔
1392
      *indices = c.material_.data();
248✔
1393
      *n = c.material_.size();
248✔
1394
    } else {
1395
      *indices = &c.fill_;
11✔
1396
      *n = 1;
11✔
1397
    }
1398
  } else {
1399
    set_errmsg("Index in cells array is out of bounds.");
×
1400
    return OPENMC_E_OUT_OF_BOUNDS;
×
1401
  }
1402
  return 0;
1403
}
1404

1405
extern "C" int openmc_cell_set_fill(
11✔
1406
  int32_t index, int type, int32_t n, const int32_t* indices)
1407
{
1408
  Fill filltype = static_cast<Fill>(type);
11✔
1409
  if (index >= 0 && index < model::cells.size()) {
11!
1410
    Cell& c {*model::cells[index]};
11!
1411
    if (filltype == Fill::MATERIAL) {
11!
1412
      c.type_ = Fill::MATERIAL;
11✔
1413
      c.material_.clear();
11!
1414
      for (int i = 0; i < n; i++) {
22✔
1415
        int i_mat = indices[i];
11✔
1416
        if (i_mat == MATERIAL_VOID) {
11!
1417
          c.material_.push_back(MATERIAL_VOID);
×
1418
        } else if (i_mat >= 0 && i_mat < model::materials.size()) {
11!
1419
          c.material_.push_back(i_mat);
11✔
1420
        } else {
1421
          set_errmsg("Index in materials array is out of bounds.");
×
1422
          return OPENMC_E_OUT_OF_BOUNDS;
×
1423
        }
1424
      }
1425
      c.material_.shrink_to_fit();
11✔
1426
    } else if (filltype == Fill::UNIVERSE) {
×
1427
      c.type_ = Fill::UNIVERSE;
×
1428
    } else {
1429
      c.type_ = Fill::LATTICE;
×
1430
    }
1431
  } else {
1432
    set_errmsg("Index in cells array is out of bounds.");
×
1433
    return OPENMC_E_OUT_OF_BOUNDS;
×
1434
  }
1435
  return 0;
1436
}
1437

1438
extern "C" int openmc_cell_set_temperature(
88✔
1439
  int32_t index, double T, const int32_t* instance, bool set_contained)
1440
{
1441
  if (index < 0 || index >= model::cells.size()) {
88!
1442
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
1443
    return OPENMC_E_OUT_OF_BOUNDS;
×
1444
  }
1445

1446
  int32_t instance_index = instance ? *instance : -1;
88✔
1447
  try {
88✔
1448
    model::cells[index]->set_temperature(T, instance_index, set_contained);
88✔
1449
  } catch (const std::exception& e) {
×
1450
    set_errmsg(e.what());
×
1451
    return OPENMC_E_UNASSIGNED;
×
1452
  }
×
1453
  return 0;
1454
}
1455

1456
extern "C" int openmc_cell_set_density(
88✔
1457
  int32_t index, double density, const int32_t* instance, bool set_contained)
1458
{
1459
  if (index < 0 || index >= model::cells.size()) {
88!
1460
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
1461
    return OPENMC_E_OUT_OF_BOUNDS;
×
1462
  }
1463

1464
  int32_t instance_index = instance ? *instance : -1;
88✔
1465
  try {
88✔
1466
    model::cells[index]->set_density(density, instance_index, set_contained);
88✔
1467
  } catch (const std::exception& e) {
×
1468
    set_errmsg(e.what());
×
1469
    return OPENMC_E_UNASSIGNED;
×
1470
  }
×
1471
  return 0;
1472
}
1473

1474
extern "C" int openmc_cell_get_temperature(
9,628✔
1475
  int32_t index, const int32_t* instance, double* T)
1476
{
1477
  if (index < 0 || index >= model::cells.size()) {
9,628!
1478
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
1479
    return OPENMC_E_OUT_OF_BOUNDS;
×
1480
  }
1481

1482
  int32_t instance_index = instance ? *instance : -1;
9,628✔
1483
  try {
9,628✔
1484
    *T = model::cells[index]->temperature(instance_index);
9,628✔
1485
  } catch (const std::exception& e) {
×
1486
    set_errmsg(e.what());
×
1487
    return OPENMC_E_UNASSIGNED;
×
1488
  }
×
1489
  return 0;
9,628✔
1490
}
1491

1492
extern "C" int openmc_cell_get_density(
88✔
1493
  int32_t index, const int32_t* instance, double* density)
1494
{
1495
  if (index < 0 || index >= model::cells.size()) {
88!
1496
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
1497
    return OPENMC_E_OUT_OF_BOUNDS;
×
1498
  }
1499

1500
  int32_t instance_index = instance ? *instance : -1;
88✔
1501
  try {
88✔
1502
    if (model::cells[index]->type_ != Fill::MATERIAL) {
88!
1503
      fatal_error(
×
1504
        fmt::format("Cell {}, instance {} is not filled with a material.",
×
1505
          model::cells[index]->id_, instance_index));
×
1506
    }
1507

1508
    int32_t mat_index = model::cells[index]->material(instance_index);
88!
1509
    if (mat_index == MATERIAL_VOID) {
88!
1510
      *density = 0.0;
×
1511
    } else {
1512
      *density = model::cells[index]->density_mult(instance_index) *
88✔
1513
                 model::materials[mat_index]->density_gpcc();
176!
1514
    }
1515
  } catch (const std::exception& e) {
×
1516
    set_errmsg(e.what());
×
1517
    return OPENMC_E_UNASSIGNED;
×
1518
  }
×
1519
  return 0;
1520
}
1521

1522
//! Get the bounding box of a cell
1523
extern "C" int openmc_cell_bounding_box(
55✔
1524
  const int32_t index, double* llc, double* urc)
1525
{
1526

1527
  BoundingBox bbox;
55✔
1528

1529
  const auto& c = model::cells[index];
55✔
1530
  bbox = c->bounding_box();
55✔
1531

1532
  // set lower left corner values
1533
  llc[0] = bbox.min.x;
55✔
1534
  llc[1] = bbox.min.y;
55✔
1535
  llc[2] = bbox.min.z;
55✔
1536

1537
  // set upper right corner values
1538
  urc[0] = bbox.max.x;
55✔
1539
  urc[1] = bbox.max.y;
55✔
1540
  urc[2] = bbox.max.z;
55✔
1541

1542
  return 0;
55✔
1543
}
1544

1545
//! Get the name of a cell
1546
extern "C" int openmc_cell_get_name(int32_t index, const char** name)
22✔
1547
{
1548
  if (index < 0 || index >= model::cells.size()) {
22!
1549
    set_errmsg("Index in cells array is out of bounds.");
×
1550
    return OPENMC_E_OUT_OF_BOUNDS;
×
1551
  }
1552

1553
  *name = model::cells[index]->name().data();
22✔
1554

1555
  return 0;
22✔
1556
}
1557

1558
//! Set the name of a cell
1559
extern "C" int openmc_cell_set_name(int32_t index, const char* name)
11✔
1560
{
1561
  if (index < 0 || index >= model::cells.size()) {
11!
1562
    set_errmsg("Index in cells array is out of bounds.");
×
1563
    return OPENMC_E_OUT_OF_BOUNDS;
×
1564
  }
1565

1566
  model::cells[index]->set_name(name);
22✔
1567

1568
  return 0;
11✔
1569
}
1570

1571
//==============================================================================
1572
//! Define a containing (parent) cell
1573
//==============================================================================
1574

1575
//! Used to locate a universe fill in the geometry
1576
struct ParentCell {
1577
  bool operator==(const ParentCell& other) const
135✔
1578
  {
1579
    return cell_index == other.cell_index &&
135!
1580
           lattice_index == other.lattice_index;
135!
1581
  }
1582

1583
  bool operator<(const ParentCell& other) const
1584
  {
1585
    return cell_index < other.cell_index ||
1586
           (cell_index == other.cell_index &&
1587
             lattice_index < other.lattice_index);
1588
  }
1589

1590
  int64_t cell_index;
1591
  int64_t lattice_index;
1592
};
1593

1594
//! Structure used to insert ParentCell into hashed STL data structures
1595
struct ParentCellHash {
1596
  std::size_t operator()(const ParentCell& p) const
631✔
1597
  {
1598
    return 4096 * p.cell_index + p.lattice_index;
631!
1599
  }
1600
};
1601

1602
//! Used to manage a traversal stack when locating parent cells of a cell
1603
//! instance in the model
1604
struct ParentCellStack {
106✔
1605

1606
  //! push method that adds to the parent_cells visited cells for this search
1607
  //! universe
1608
  void push(int32_t search_universe, const ParentCell& pc)
105✔
1609
  {
1610
    parent_cells_.push_back(pc);
105✔
1611
    // add parent cell to the set of cells we've visited for this search
1612
    // universe
1613
    visited_cells_[search_universe].insert(pc);
105✔
1614
  }
105✔
1615

1616
  //! removes the last parent_cell and clears the visited cells for the popped
1617
  //! cell's universe
1618
  void pop()
75✔
1619
  {
1620
    visited_cells_[this->current_univ()].clear();
75✔
1621
    parent_cells_.pop_back();
75✔
1622
  }
75✔
1623

1624
  //! checks whether or not the parent cell has been visited already for this
1625
  //! search universe
1626
  bool visited(int32_t search_universe, const ParentCell& parent_cell)
526✔
1627
  {
1628
    return visited_cells_[search_universe].count(parent_cell) != 0;
526✔
1629
  }
1630

1631
  //! return the next universe to search for a parent cell
1632
  int32_t current_univ() const
75✔
1633
  {
1634
    return model::cells[parent_cells_.back().cell_index]->universe_;
75✔
1635
  }
1636

1637
  //! indicates whether nor not parent cells are present on the stack
1638
  bool empty() const { return parent_cells_.empty(); }
75✔
1639

1640
  //! compute an instance for the provided distribcell index
1641
  int32_t compute_instance(int32_t distribcell_index) const
181✔
1642
  {
1643
    if (distribcell_index == C_NONE)
181✔
1644
      return 0;
1645

1646
    int32_t instance = 0;
120✔
1647
    for (const auto& parent_cell : this->parent_cells_) {
225✔
1648
      auto& cell = model::cells[parent_cell.cell_index];
105!
1649
      if (cell->type_ == Fill::UNIVERSE) {
105!
1650
        instance += cell->offset_[distribcell_index];
×
1651
      } else if (cell->type_ == Fill::LATTICE) {
105!
1652
        auto& lattice = model::lattices[cell->fill_];
105✔
1653
        instance +=
105✔
1654
          lattice->offset(distribcell_index, parent_cell.lattice_index);
105✔
1655
      }
1656
    }
1657
    return instance;
1658
  }
1659

1660
  // Accessors
1661
  vector<ParentCell>& parent_cells() { return parent_cells_; }
106✔
1662
  const vector<ParentCell>& parent_cells() const { return parent_cells_; }
1663

1664
  // Data Members
1665
  vector<ParentCell> parent_cells_;
1666
  std::unordered_map<int32_t, std::unordered_set<ParentCell, ParentCellHash>>
1667
    visited_cells_;
1668
};
1669

1670
vector<ParentCell> Cell::find_parent_cells(
×
1671
  int32_t instance, const Position& r) const
1672
{
1673

1674
  // create a temporary particle
1675
  GeometryState dummy_particle {};
×
1676
  dummy_particle.r() = r;
×
1677
  dummy_particle.u() = {0., 0., 1.};
×
1678

1679
  return find_parent_cells(instance, dummy_particle);
×
1680
}
×
1681

1682
vector<ParentCell> Cell::find_parent_cells(
×
1683
  int32_t instance, GeometryState& p) const
1684
{
1685
  // look up the particle's location
1686
  exhaustive_find_cell(p);
×
1687
  const auto& coords = p.coord();
×
1688

1689
  // build a parent cell stack from the particle coordinates
1690
  ParentCellStack stack;
×
1691
  bool cell_found = false;
×
1692
  for (auto it = coords.begin(); it != coords.end(); it++) {
×
1693
    const auto& coord = *it;
×
1694
    const auto& cell = model::cells[coord.cell()];
×
1695
    // if the cell at this level matches the current cell, stop adding to the
1696
    // stack
1697
    if (coord.cell() == model::cell_map[this->id_]) {
×
1698
      cell_found = true;
1699
      break;
1700
    }
1701

1702
    // if filled with a lattice, get the lattice index from the next
1703
    // level in the coordinates to push to the stack
1704
    int lattice_idx = C_NONE;
×
1705
    if (cell->type_ == Fill::LATTICE) {
×
1706
      const auto& next_coord = *(it + 1);
×
1707
      lattice_idx = model::lattices[next_coord.lattice()]->get_flat_index(
×
1708
        next_coord.lattice_index());
1709
    }
1710
    stack.push(coord.universe(), {coord.cell(), lattice_idx});
×
1711
  }
1712

1713
  // if this loop finished because the cell was found and
1714
  // the instance matches the one requested in the call
1715
  // we have the correct path and can return the stack
1716
  if (cell_found &&
×
1717
      stack.compute_instance(this->distribcell_index_) == instance) {
×
1718
    return stack.parent_cells();
×
1719
  }
1720

1721
  // fall back on an exhaustive search for the cell's parents
1722
  return exhaustive_find_parent_cells(instance);
×
1723
}
×
1724

1725
vector<ParentCell> Cell::exhaustive_find_parent_cells(int32_t instance) const
106✔
1726
{
1727
  ParentCellStack stack;
106✔
1728
  // start with this cell's universe
1729
  int32_t prev_univ_idx;
106✔
1730
  int32_t univ_idx = this->universe_;
106✔
1731

1732
  while (true) {
181✔
1733
    const auto& univ = model::universes[univ_idx];
181✔
1734
    prev_univ_idx = univ_idx;
181✔
1735

1736
    // search for a cell that is filled w/ this universe
1737
    for (const auto& cell : model::cells) {
1,159✔
1738
      // if this is a material-filled cell, move on
1739
      if (cell->type_ == Fill::MATERIAL)
1,083✔
1740
        continue;
602✔
1741

1742
      if (cell->type_ == Fill::UNIVERSE) {
481✔
1743
        // if this is in the set of cells previously visited for this universe,
1744
        // move on
1745
        if (stack.visited(univ_idx, {model::cell_map[cell->id_], C_NONE}))
286!
1746
          continue;
×
1747

1748
        // if this cell contains the universe we're searching for, add it to the
1749
        // stack
1750
        if (cell->fill_ == univ_idx) {
286!
1751
          stack.push(univ_idx, {model::cell_map[cell->id_], C_NONE});
×
1752
          univ_idx = cell->universe_;
×
1753
        }
1754
      } else if (cell->type_ == Fill::LATTICE) {
195!
1755
        // retrieve the lattice and lattice universes
1756
        const auto& lattice = model::lattices[cell->fill_];
195✔
1757
        const auto& lattice_univs = lattice->universes_;
195✔
1758

1759
        // start search for universe
1760
        auto lat_it = lattice_univs.begin();
195✔
1761
        while (true) {
465✔
1762
          // find the next lattice cell with this universe
1763
          lat_it = std::find(lat_it, lattice_univs.end(), univ_idx);
330✔
1764
          if (lat_it == lattice_univs.end())
330✔
1765
            break;
1766

1767
          int lattice_idx = lat_it - lattice_univs.begin();
240✔
1768

1769
          // move iterator forward one to avoid finding the same entry
1770
          lat_it++;
240✔
1771
          if (stack.visited(
480✔
1772
                univ_idx, {model::cell_map[cell->id_], lattice_idx}))
240✔
1773
            continue;
135✔
1774

1775
          // add this cell and lattice index to the stack and exit loop
1776
          stack.push(univ_idx, {model::cell_map[cell->id_], lattice_idx});
105✔
1777
          univ_idx = cell->universe_;
105✔
1778
          break;
105✔
1779
        }
135✔
1780
      }
1781
      // if we've updated the universe, break
1782
      if (prev_univ_idx != univ_idx)
481✔
1783
        break;
1784
    } // end cell loop search for universe
1785

1786
    // if we're at the top of the geometry and the instance matches, we're done
1787
    if (univ_idx == model::root_universe &&
217!
1788
        stack.compute_instance(this->distribcell_index_) == instance)
181✔
1789
      break;
1790

1791
    // if there is no match on the original cell's universe, report an error
1792
    if (univ_idx == this->universe_) {
75!
1793
      fatal_error(
×
1794
        fmt::format("Could not find the parent cells for cell {}, instance {}.",
×
1795
          this->id_, instance));
×
1796
    }
1797

1798
    // if we don't find a suitable update, adjust the stack and continue
1799
    if (univ_idx == model::root_universe || univ_idx == prev_univ_idx) {
75!
1800
      stack.pop();
75✔
1801
      univ_idx = stack.empty() ? this->universe_ : stack.current_univ();
75!
1802
    }
1803

1804
  } // end while
1805

1806
  // reverse the stack so the highest cell comes first
1807
  std::reverse(stack.parent_cells().begin(), stack.parent_cells().end());
106✔
1808
  return stack.parent_cells();
212✔
1809
}
106✔
1810

1811
std::unordered_map<int32_t, vector<int32_t>> Cell::get_contained_cells(
151✔
1812
  int32_t instance, Position* hint) const
1813
{
1814
  std::unordered_map<int32_t, vector<int32_t>> contained_cells;
151✔
1815

1816
  // if this is a material-filled cell it has no contained cells
1817
  if (this->type_ == Fill::MATERIAL)
151✔
1818
    return contained_cells;
1819

1820
  // find the pathway through the geometry to this cell
1821
  vector<ParentCell> parent_cells;
106!
1822

1823
  // if a positional hint is provided, attempt to do a fast lookup
1824
  // of the parent cells
1825
  parent_cells = hint ? find_parent_cells(instance, *hint)
106!
1826
                      : exhaustive_find_parent_cells(instance);
106✔
1827

1828
  // if this cell is filled w/ a material, it contains no other cells
1829
  if (type_ != Fill::MATERIAL) {
106!
1830
    this->get_contained_cells_inner(contained_cells, parent_cells);
106✔
1831
  }
1832

1833
  return contained_cells;
106✔
1834
}
151✔
1835

1836
//! Get all cells within this cell
1837
void Cell::get_contained_cells_inner(
82,278✔
1838
  std::unordered_map<int32_t, vector<int32_t>>& contained_cells,
1839
  vector<ParentCell>& parent_cells) const
1840
{
1841

1842
  // filled by material, determine instance based on parent cells
1843
  if (type_ == Fill::MATERIAL) {
82,278✔
1844
    int instance = 0;
81,692✔
1845
    if (this->distribcell_index_ >= 0) {
81,692!
1846
      for (auto& parent_cell : parent_cells) {
245,074✔
1847
        auto& cell = model::cells[parent_cell.cell_index];
163,382✔
1848
        if (cell->type_ == Fill::UNIVERSE) {
163,382✔
1849
          instance += cell->offset_[distribcell_index_];
80,192✔
1850
        } else if (cell->type_ == Fill::LATTICE) {
83,190!
1851
          auto& lattice = model::lattices[cell->fill_];
83,190✔
1852
          instance += lattice->offset(
83,190✔
1853
            this->distribcell_index_, parent_cell.lattice_index);
83,190✔
1854
        }
1855
      }
1856
    }
1857
    // add entry to contained cells
1858
    contained_cells[model::cell_map[id_]].push_back(instance);
81,692✔
1859
    // filled with universe, add the containing cell to the parent cells
1860
    // and recurse
1861
  } else if (type_ == Fill::UNIVERSE) {
586✔
1862
    parent_cells.push_back({model::cell_map[id_], -1});
496✔
1863
    auto& univ = model::universes[fill_];
496✔
1864
    for (auto cell_index : univ->cells_) {
2,973✔
1865
      auto& cell = model::cells[cell_index];
2,477✔
1866
      cell->get_contained_cells_inner(contained_cells, parent_cells);
2,477✔
1867
    }
1868
    parent_cells.pop_back();
496✔
1869
    // filled with a lattice, visit each universe in the lattice
1870
    // with a recursive call to collect the cell instances
1871
  } else if (type_ == Fill::LATTICE) {
90!
1872
    auto& lattice = model::lattices[fill_];
90✔
1873
    for (auto i = lattice->begin(); i != lattice->end(); ++i) {
79,470✔
1874
      auto& univ = model::universes[*i];
79,380✔
1875
      parent_cells.push_back({model::cell_map[id_], i.indx_});
79,380✔
1876
      for (auto cell_index : univ->cells_) {
159,075✔
1877
        auto& cell = model::cells[cell_index];
79,695✔
1878
        cell->get_contained_cells_inner(contained_cells, parent_cells);
79,695✔
1879
      }
1880
      parent_cells.pop_back();
79,380✔
1881
    }
1882
  }
1883
}
82,278✔
1884

1885
//! Return the index in the cells array of a cell with a given ID
1886
extern "C" int openmc_get_cell_index(int32_t id, int32_t* index)
635✔
1887
{
1888
  auto it = model::cell_map.find(id);
635✔
1889
  if (it != model::cell_map.end()) {
635✔
1890
    *index = it->second;
624✔
1891
    return 0;
624✔
1892
  } else {
1893
    set_errmsg("No cell exists with ID=" + std::to_string(id) + ".");
11✔
1894
    return OPENMC_E_INVALID_ID;
11✔
1895
  }
1896
}
1897

1898
//! Return the ID of a cell
1899
extern "C" int openmc_cell_get_id(int32_t index, int32_t* id)
601,273✔
1900
{
1901
  if (index >= 0 && index < model::cells.size()) {
601,273!
1902
    *id = model::cells[index]->id_;
601,273✔
1903
    return 0;
601,273✔
1904
  } else {
1905
    set_errmsg("Index in cells array is out of bounds.");
×
1906
    return OPENMC_E_OUT_OF_BOUNDS;
×
1907
  }
1908
}
1909

1910
//! Set the ID of a cell
1911
extern "C" int openmc_cell_set_id(int32_t index, int32_t id)
22✔
1912
{
1913
  if (index >= 0 && index < model::cells.size()) {
22!
1914
    model::cells[index]->id_ = id;
22✔
1915
    model::cell_map[id] = index;
22✔
1916
    return 0;
22✔
1917
  } else {
1918
    set_errmsg("Index in cells array is out of bounds.");
×
1919
    return OPENMC_E_OUT_OF_BOUNDS;
×
1920
  }
1921
}
1922

1923
//! Return the translation vector of a cell
1924
extern "C" int openmc_cell_get_translation(int32_t index, double xyz[])
55✔
1925
{
1926
  if (index >= 0 && index < model::cells.size()) {
55!
1927
    auto& cell = model::cells[index];
55✔
1928
    xyz[0] = cell->translation_.x;
55✔
1929
    xyz[1] = cell->translation_.y;
55✔
1930
    xyz[2] = cell->translation_.z;
55✔
1931
    return 0;
55✔
1932
  } else {
1933
    set_errmsg("Index in cells array is out of bounds.");
×
1934
    return OPENMC_E_OUT_OF_BOUNDS;
×
1935
  }
1936
}
1937

1938
//! Set the translation vector of a cell
1939
extern "C" int openmc_cell_set_translation(int32_t index, const double xyz[])
33✔
1940
{
1941
  if (index >= 0 && index < model::cells.size()) {
33!
1942
    if (model::cells[index]->fill_ == C_NONE) {
33✔
1943
      set_errmsg(fmt::format("Cannot apply a translation to cell {}"
11✔
1944
                             " because it is not filled with another universe",
1945
        index));
1946
      return OPENMC_E_GEOMETRY;
11✔
1947
    }
1948
    model::cells[index]->translation_ = Position(xyz);
22✔
1949
    return 0;
22✔
1950
  } else {
1951
    set_errmsg("Index in cells array is out of bounds.");
×
1952
    return OPENMC_E_OUT_OF_BOUNDS;
×
1953
  }
1954
}
1955

1956
//! Return the rotation matrix of a cell
1957
extern "C" int openmc_cell_get_rotation(int32_t index, double rot[], size_t* n)
55✔
1958
{
1959
  if (index >= 0 && index < model::cells.size()) {
55!
1960
    auto& cell = model::cells[index];
55✔
1961
    *n = cell->rotation_.size();
55✔
1962
    std::memcpy(rot, cell->rotation_.data(), *n * sizeof(cell->rotation_[0]));
55✔
1963
    return 0;
55✔
1964
  } else {
1965
    set_errmsg("Index in cells array is out of bounds.");
×
1966
    return OPENMC_E_OUT_OF_BOUNDS;
×
1967
  }
1968
}
1969

1970
//! Set the flattened rotation matrix of a cell
1971
extern "C" int openmc_cell_set_rotation(
44✔
1972
  int32_t index, const double rot[], size_t rot_len)
1973
{
1974
  if (index >= 0 && index < model::cells.size()) {
44!
1975
    if (model::cells[index]->fill_ == C_NONE) {
44✔
1976
      set_errmsg(fmt::format("Cannot apply a rotation to cell {}"
11✔
1977
                             " because it is not filled with another universe",
1978
        index));
1979
      return OPENMC_E_GEOMETRY;
11✔
1980
    }
1981
    std::vector<double> vec_rot(rot, rot + rot_len);
33✔
1982
    model::cells[index]->set_rotation(vec_rot);
33✔
1983
    return 0;
33✔
1984
  } else {
44✔
1985
    set_errmsg("Index in cells array is out of bounds.");
×
1986
    return OPENMC_E_OUT_OF_BOUNDS;
×
1987
  }
1988
}
1989

1990
//! Get the number of instances of the requested cell
1991
extern "C" int openmc_cell_get_num_instances(
77✔
1992
  int32_t index, int32_t* num_instances)
1993
{
1994
  if (index < 0 || index >= model::cells.size()) {
77!
1995
    set_errmsg("Index in cells array is out of bounds.");
×
1996
    return OPENMC_E_OUT_OF_BOUNDS;
×
1997
  }
1998
  *num_instances = model::cells[index]->n_instances();
77✔
1999
  return 0;
77✔
2000
}
2001

2002
//! Extend the cells array by n elements
2003
extern "C" int openmc_extend_cells(
22✔
2004
  int32_t n, int32_t* index_start, int32_t* index_end)
2005
{
2006
  if (index_start)
22!
2007
    *index_start = model::cells.size();
22✔
2008
  if (index_end)
22!
2009
    *index_end = model::cells.size() + n - 1;
×
2010
  for (int32_t i = 0; i < n; i++) {
44✔
2011
    model::cells.push_back(make_unique<CSGCell>());
22✔
2012
  }
2013
  return 0;
22✔
2014
}
2015

2016
extern "C" int cells_size()
55✔
2017
{
2018
  return model::cells.size();
55✔
2019
}
2020

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