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

openmc-dev / openmc / 37216251345

04 Oct 2026 04:17PM UTC coverage: 81.295% (-0.1%) from 81.4%
37216251345

Pull #4166

github

web-flow
Merge a29a4711a into 4c0448d33
Pull Request #4166: Cache surface distances and senses when finding boundaries in complex cells

20094 of 29178 branches covered (68.87%)

Branch coverage included in aggregate %.

249 of 263 new or added lines in 5 files covered. (94.68%)

205 existing lines in 3 files now uncovered.

62624 of 72572 relevant lines covered (86.29%)

42980343.05 hits per line

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

77.08
/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 <string>
10

11
#include <fmt/core.h>
12

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

26
namespace openmc {
27

28
//==============================================================================
29
// Global variables
30
//==============================================================================
31

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

36
} // namespace model
37

38
//==============================================================================
39
// Cell implementation
40
//==============================================================================
41

42
int32_t Cell::n_instances() const
13,137 ✔
43
{
44
  return model::universes[universe_]->n_instances_;
13,137 ✔
45
}
46

47
void Cell::set_rotation(const vector<double>& rot)
300 ✔
48
{
49
  if (fill_ == C_NONE) {
300 !
50
    fatal_error(fmt::format("Cannot apply a rotation to cell {}"
×
51
                            " because it is not filled with another universe",
52
      id_));
×
53
  }
54

55
  if (rot.size() != 3 && rot.size() != 9) {
300 !
56
    fatal_error(fmt::format("Non-3D rotation vector applied to cell {}", id_));
×
57
  }
58

59
  // Compute and store the inverse rotation matrix for the angles given.
60
  rotation_.clear();
300 ✔
61
  rotation_.reserve(rot.size() == 9 ? 9 : 12);
600 !
62
  if (rot.size() == 3) {
300 !
63
    double phi = -rot[0] * PI / 180.0;
300 ✔
64
    double theta = -rot[1] * PI / 180.0;
300 ✔
65
    double psi = -rot[2] * PI / 180.0;
300 ✔
66
    rotation_.push_back(std::cos(theta) * std::cos(psi));
300 ✔
67
    rotation_.push_back(-std::cos(phi) * std::sin(psi) +
300 ✔
68
                        std::sin(phi) * std::sin(theta) * std::cos(psi));
300 ✔
69
    rotation_.push_back(std::sin(phi) * std::sin(psi) +
300 ✔
70
                        std::cos(phi) * std::sin(theta) * std::cos(psi));
300 ✔
71
    rotation_.push_back(std::cos(theta) * std::sin(psi));
300 ✔
72
    rotation_.push_back(std::cos(phi) * std::cos(psi) +
300 ✔
73
                        std::sin(phi) * std::sin(theta) * std::sin(psi));
300 ✔
74
    rotation_.push_back(-std::sin(phi) * std::cos(psi) +
300 ✔
75
                        std::cos(phi) * std::sin(theta) * std::sin(psi));
300 ✔
76
    rotation_.push_back(-std::sin(theta));
300 ✔
77
    rotation_.push_back(std::sin(phi) * std::cos(theta));
300 ✔
78
    rotation_.push_back(std::cos(phi) * std::cos(theta));
300 ✔
79

80
    // When user specifies angles, write them at end of vector
81
    rotation_.push_back(rot[0]);
300 ✔
82
    rotation_.push_back(rot[1]);
300 ✔
83
    rotation_.push_back(rot[2]);
300 ✔
84
  } else {
85
    std::copy(rot.begin(), rot.end(), std::back_inserter(rotation_));
×
86
  }
87
}
300 ✔
88

89
double Cell::temperature(int32_t instance) const
7,006 ✔
90
{
91
  if (sqrtkT_.size() < 1) {
7,006 !
92
    throw std::runtime_error {"Cell temperature has not yet been set."};
×
93
  }
94

95
  if (instance >= 0) {
7,006 ✔
96
    double sqrtkT = sqrtkT_.size() == 1 ? sqrtkT_.at(0) : sqrtkT_.at(instance);
6,944 ✔
97
    return sqrtkT * sqrtkT / K_BOLTZMANN;
6,944 ✔
98
  } else {
99
    return sqrtkT_[0] * sqrtkT_[0] / K_BOLTZMANN;
62 ✔
100
  }
101
}
102

103
double Cell::density_mult(int32_t instance) const
2,147,483,647 ✔
104
{
105
  if (instance >= 0) {
2,147,483,647 ✔
106
    return density_mult_.size() == 1 ? density_mult_.at(0)
2,147,483,647 ✔
107
                                     : density_mult_.at(instance);
3,678,988 ✔
108
  } else {
109
    return density_mult_[0];
56 ✔
110
  }
111
}
112

113
double Cell::density(int32_t instance) const
880,348 ✔
114
{
115
  const int32_t mat_index = material(instance);
880,348 ✔
116
  if (mat_index == MATERIAL_VOID)
880,348 !
117
    return 0.0;
118

119
  return density_mult(instance) * model::materials[mat_index]->density_gpcc();
1,760,696 ✔
120
}
121

122
void Cell::set_temperature(double T, int32_t instance, bool set_contained)
7,266 ✔
123
{
124
  if (settings::temperature_method == TemperatureMethod::INTERPOLATION) {
7,266 !
125
    if (T < (data::temperature_min - settings::temperature_tolerance)) {
×
126
      throw std::runtime_error {
×
127
        fmt::format("Temperature of {} K is below minimum temperature at "
×
128
                    "which data is available of {} K.",
129
          T, data::temperature_min)};
×
130
    } else if (T > (data::temperature_max + settings::temperature_tolerance)) {
×
131
      throw std::runtime_error {
×
132
        fmt::format("Temperature of {} K is above maximum temperature at "
×
133
                    "which data is available of {} K.",
134
          T, data::temperature_max)};
×
135
    }
136
  }
137

138
  if (type_ == Fill::MATERIAL) {
7,266 ✔
139
    if (instance >= 0) {
7,246 ✔
140
      // If temperature vector is not big enough, resize it first
141
      if (sqrtkT_.size() != n_instances())
7,190 ✔
142
        sqrtkT_.resize(n_instances(), sqrtkT_[0]);
30 ✔
143

144
      // Set temperature for the corresponding instance
145
      sqrtkT_.at(instance) = std::sqrt(K_BOLTZMANN * T);
7,190 ✔
146
    } else {
147
      // Set temperature for all instances
148
      for (auto& T_ : sqrtkT_) {
112 ✔
149
        T_ = std::sqrt(K_BOLTZMANN * T);
56 ✔
150
      }
151
    }
152
  } else {
153
    if (!set_contained) {
20 !
154
      throw std::runtime_error {
×
155
        fmt::format("Attempted to set the temperature of cell {} "
×
156
                    "which is not filled by a material.",
157
          id_)};
×
158
    }
159

160
    auto contained_cells = this->get_contained_cells(instance);
20 ✔
161
    for (const auto& entry : contained_cells) {
80 ✔
162
      auto& cell = model::cells[entry.first];
60 !
163
      assert(cell->type_ == Fill::MATERIAL);
60 !
164
      auto& instances = entry.second;
60 ✔
165
      for (auto instance : instances) {
210 ✔
166
        cell->set_temperature(T, instance);
150 ✔
167
      }
168
    }
169
  }
20 ✔
170
}
7,266 ✔
171

172
void Cell::set_density(double density, int32_t instance, bool set_contained)
248 ✔
173
{
174
  if (type_ != Fill::MATERIAL && !set_contained) {
248 !
175
    fatal_error(
×
176
      fmt::format("Attempted to set the density multiplier of cell {} "
×
177
                  "which is not filled by a material.",
178
        id_));
×
179
  }
180

181
  if (type_ == Fill::MATERIAL) {
248 ✔
182
    const int32_t mat_index = material(instance);
238 !
183
    if (mat_index == MATERIAL_VOID)
238 !
184
      return;
185

186
    if (instance >= 0) {
238 ✔
187
      // If density multiplier vector is not big enough, resize it first
188
      if (density_mult_.size() != n_instances())
182 ✔
189
        density_mult_.resize(n_instances(), density_mult_[0]);
78 ✔
190

191
      // Set density multiplier for the corresponding instance
192
      density_mult_.at(instance) =
182 ✔
193
        density / model::materials[mat_index]->density_gpcc();
364 !
194
    } else {
195
      // Set density multiplier for all instances
196
      for (auto& x : density_mult_) {
112 ✔
197
        x = density / model::materials[mat_index]->density_gpcc();
112 !
198
      }
199
    }
200
  } else {
201
    auto contained_cells = this->get_contained_cells(instance);
10 ✔
202
    for (const auto& entry : contained_cells) {
40 ✔
203
      auto& cell = model::cells[entry.first];
30 !
204
      assert(cell->type_ == Fill::MATERIAL);
30 !
205
      auto& instances = entry.second;
30 ✔
206
      for (auto instance : instances) {
60 ✔
207
        cell->set_density(density, instance);
30 ✔
208
      }
209
    }
210
  }
10 ✔
211
}
212

213
void Cell::export_properties_hdf5(hid_t group) const
168 ✔
214
{
215
  // Create a group for this cell.
216
  auto cell_group = create_group(group, fmt::format("cell {}", id_));
168 ✔
217

218
  // Write temperature in [K] for one or more cell instances
219
  vector<double> temps;
168 ✔
220
  for (auto sqrtkT_val : sqrtkT_)
312 ✔
221
    temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
144 ✔
222
  write_dataset(cell_group, "temperature", temps);
168 ✔
223

224
  // Write density for one or more cell instances
225
  if (type_ == Fill::MATERIAL && material_.size() > 0) {
168 ✔
226
    vector<double> density;
144 ✔
227
    for (int32_t i = 0; i < density_mult_.size(); ++i)
288 ✔
228
      density.push_back(this->density(i));
144 ✔
229

230
    write_dataset(cell_group, "density", density);
144 ✔
231
  }
144 ✔
232

233
  close_group(cell_group);
168 ✔
234
}
168 ✔
235

236
void Cell::import_properties_hdf5(hid_t group)
184 ✔
237
{
238
  auto cell_group = open_group(group, fmt::format("cell {}", id_));
184 ✔
239

240
  // Read temperatures from file
241
  vector<double> temps;
184 ✔
242
  read_dataset(cell_group, "temperature", temps);
184 ✔
243

244
  // Ensure number of temperatures makes sense
245
  auto n_temps = temps.size();
184 ✔
246
  if (n_temps > 1 && n_temps != n_instances()) {
184 !
247
    fatal_error(fmt::format(
×
248
      "Number of temperatures for cell {} doesn't match number of instances",
249
      id_));
×
250
  }
251

252
  // Modify temperatures for the cell
253
  sqrtkT_.clear();
184 ✔
254
  sqrtkT_.resize(temps.size());
184 ✔
255
  for (int64_t i = 0; i < temps.size(); ++i) {
7,216 ✔
256
    this->set_temperature(temps[i], i);
7,032 ✔
257
  }
258

259
  // Read densities
260
  if (object_exists(cell_group, "density")) {
184 ✔
261
    vector<double> density;
144 ✔
262
    read_dataset(cell_group, "density", density);
144 ✔
263

264
    // Ensure number of densities makes sense
265
    auto n_density = density.size();
144 !
266
    if (n_density > 1 && n_density != n_instances()) {
144 !
267
      fatal_error(fmt::format("Number of densities for cell {} "
×
268
                              "doesn't match number of instances",
269
        id_));
×
270
    }
271

272
    // Set densities.
273
    for (int32_t i = 0; i < n_density; ++i) {
288 ✔
274
      this->set_density(density[i], i);
144 ✔
275
    }
276
  }
144 ✔
277

278
  close_group(cell_group);
184 ✔
279
}
184 ✔
280

281
void Cell::to_hdf5(hid_t cell_group) const
27,199 ✔
282
{
283

284
  // Create a group for this cell.
285
  auto group = create_group(cell_group, fmt::format("cell {}", id_));
27,199 ✔
286

287
  if (!name_.empty()) {
27,199 ✔
288
    write_string(group, "name", name_, false);
5,958 ✔
289
  }
290

291
  write_dataset(group, "universe", model::universes[universe_]->id_);
27,199 ✔
292

293
  to_hdf5_inner(group);
27,199 ✔
294

295
  // Write fill information.
296
  if (type_ == Fill::MATERIAL) {
27,199 ✔
297
    write_dataset(group, "fill_type", "material");
22,887 ✔
298
    std::vector<int32_t> mat_ids;
22,887 ✔
299
    for (auto i_mat : material_) {
46,775 ✔
300
      if (i_mat != MATERIAL_VOID) {
23,888 ✔
301
        mat_ids.push_back(model::materials[i_mat]->id_);
13,479 ✔
302
      } else {
303
        mat_ids.push_back(MATERIAL_VOID);
10,409 ✔
304
      }
305
    }
306
    if (mat_ids.size() == 1) {
22,887 ✔
307
      write_dataset(group, "material", mat_ids[0]);
22,740 ✔
308
    } else {
309
      write_dataset(group, "material", mat_ids);
147 ✔
310
    }
311

312
    std::vector<double> temps;
22,887 ✔
313
    for (auto sqrtkT_val : sqrtkT_)
54,302 ✔
314
      temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
31,415 ✔
315
    write_dataset(group, "temperature", temps);
22,887 ✔
316

317
    write_dataset(group, "density_mult", density_mult_);
22,887 ✔
318

319
  } else if (type_ == Fill::UNIVERSE) {
27,199 ✔
320
    write_dataset(group, "fill_type", "universe");
3,082 ✔
321
    write_dataset(group, "fill", model::universes[fill_]->id_);
3,082 ✔
322
    if (translation_ != Position(0, 0, 0)) {
3,082 ✔
323
      write_dataset(group, "translation", translation_);
1,336 ✔
324
    }
325
    if (!rotation_.empty()) {
3,082 ✔
326
      if (rotation_.size() == 12) {
192 !
327
        std::array<double, 3> rot {rotation_[9], rotation_[10], rotation_[11]};
192 ✔
328
        write_dataset(group, "rotation", rot);
192 ✔
329
      } else {
330
        write_dataset(group, "rotation", rotation_);
×
331
      }
332
    }
333

334
  } else if (type_ == Fill::LATTICE) {
1,230 !
335
    write_dataset(group, "fill_type", "lattice");
1,230 ✔
336
    write_dataset(group, "lattice", model::lattices[fill_]->id_);
1,230 ✔
337
  }
338

339
  close_group(group);
27,199 ✔
340
}
27,199 ✔
341

342
//==============================================================================
343
// XML parsing helpers for <cell> nodes
344
//==============================================================================
345

346
vector<int32_t> parse_cell_material_xml(pugi::xml_node node, int32_t cell_id)
25,195 ✔
347
{
348
  vector<std::string> mats {
25,195 ✔
349
    get_node_array<std::string>(node, "material", true)};
25,195 ✔
350
  if (mats.empty()) {
25,195 !
351
    fatal_error(fmt::format(
×
352
      "An empty material element was specified for cell {}", cell_id));
353
  }
354
  vector<int32_t> material;
25,195 ✔
355
  material.reserve(mats.size());
25,195 ✔
356
  for (const auto& mat : mats) {
51,409 ✔
357
    if (mat == "void") {
26,214 ✔
358
      material.push_back(MATERIAL_VOID);
10,703 ✔
359
    } else {
360
      material.push_back(std::stoi(mat));
15,511 ✔
361
    }
362
  }
363
  return material;
25,195 ✔
364
}
25,195 ✔
365

366
vector<double> parse_cell_temperature_xml(pugi::xml_node node, int32_t cell_id)
288 ✔
367
{
368
  auto temperatures = get_node_array<double>(node, "temperature");
288 ✔
369
  if (temperatures.empty()) {
288 !
370
    fatal_error(fmt::format(
×
371
      "An empty temperature element was specified for cell {}", cell_id));
372
  }
373
  for (auto T : temperatures) {
1,296 ✔
374
    if (T < 0) {
1,008 !
375
      fatal_error(fmt::format(
×
376
        "Cell {} was specified with a negative temperature", cell_id));
377
    }
378
  }
379
  return temperatures;
288 ✔
380
}
×
381

382
vector<double> parse_cell_density_xml(pugi::xml_node node, int32_t cell_id)
50 ✔
383
{
384
  auto densities = get_node_array<double>(node, "density");
50 ✔
385
  if (densities.empty()) {
50 !
386
    fatal_error(fmt::format(
×
387
      "An empty density element was specified for cell {}", cell_id));
388
  }
389
  for (auto rho : densities) {
820 ✔
390
    if (rho <= 0) {
770 !
391
      fatal_error(fmt::format(
×
392
        "Cell {} was specified with a density less than or equal to zero",
393
        cell_id));
394
    }
395
  }
396
  return densities;
50 ✔
397
}
×
398

399
//==============================================================================
400
// CSGCell implementation
401
//==============================================================================
402

403
CSGCell::CSGCell(pugi::xml_node cell_node)
30,414 ✔
404
{
405
  if (check_for_node(cell_node, "id")) {
30,414 !
406
    id_ = std::stoi(get_node_value(cell_node, "id"));
60,828 ✔
407
  } else {
408
    fatal_error("Must specify id of cell in geometry XML file.");
×
409
  }
410

411
  if (check_for_node(cell_node, "name")) {
30,414 ✔
412
    name_ = get_node_value(cell_node, "name");
7,095 ✔
413
  }
414

415
  if (check_for_node(cell_node, "universe")) {
30,414 ✔
416
    universe_ = std::stoi(get_node_value(cell_node, "universe"));
59,204 ✔
417
  } else {
418
    universe_ = 0;
812 ✔
419
  }
420

421
  // Make sure that either material or fill was specified, but not both.
422
  bool fill_present = check_for_node(cell_node, "fill");
30,414 ✔
423
  bool material_present = check_for_node(cell_node, "material");
30,414 ✔
424
  if (!(fill_present || material_present)) {
30,414 !
425
    fatal_error(
×
426
      fmt::format("Neither material nor fill was specified for cell {}", id_));
×
427
  }
428
  if (fill_present && material_present) {
30,414 !
429
    fatal_error(fmt::format("Cell {} has both a material and a fill specified; "
×
430
                            "only one can be specified per cell",
431
      id_));
×
432
  }
433

434
  if (fill_present) {
30,414 ✔
435
    fill_ = std::stoi(get_node_value(cell_node, "fill"));
10,456 ✔
436
    if (fill_ == universe_) {
5,228 !
437
      fatal_error(fmt::format("Cell {} is filled with the same universe that "
×
438
                              "it is contained in.",
439
        id_));
×
440
    }
441
  } else {
442
    fill_ = C_NONE;
25,186 ✔
443
  }
444

445
  // Read the material element.  There can be zero materials (filled with a
446
  // universe), more than one material (distribmats), and some materials may
447
  // be "void".
448
  if (material_present) {
30,414 ✔
449
    material_ = parse_cell_material_xml(cell_node, id_);
25,186 ✔
450
  }
451

452
  // Read the temperature element which may be distributed like materials.
453
  if (check_for_node(cell_node, "temperature")) {
30,414 ✔
454
    sqrtkT_ = parse_cell_temperature_xml(cell_node, id_);
288 ✔
455
    sqrtkT_.shrink_to_fit();
288 ✔
456

457
    // Make sure this is a material-filled cell.
458
    if (material_.size() == 0) {
288 !
459
      fatal_error(fmt::format(
×
460
        "Cell {} was specified with a temperature but no material. Temperature"
461
        "specification is only valid for cells filled with a material.",
462
        id_));
×
463
    }
464

465
    // Convert to sqrt(k*T).
466
    for (auto& T : sqrtkT_) {
1,296 ✔
467
      T = std::sqrt(K_BOLTZMANN * T);
1,008 ✔
468
    }
469
  }
470

471
  // Read the density element which can be distributed similar to temperature.
472
  // These get assigned to the density multiplier, requiring a division by
473
  // the material density.
474
  // Note: calculating the actual density multiplier is deferred until materials
475
  // are finalized. density_mult_ contains the true density in the meantime.
476
  if (check_for_node(cell_node, "density")) {
30,414 ✔
477
    density_mult_ = parse_cell_density_xml(cell_node, id_);
50 ✔
478
    density_mult_.shrink_to_fit();
50 ✔
479

480
    // Make sure this is a material-filled cell.
481
    if (material_.size() == 0) {
50 !
482
      fatal_error(fmt::format(
×
483
        "Cell {} was specified with a density but no material. Density"
484
        "specification is only valid for cells filled with a material.",
485
        id_));
×
486
    }
487

488
    // Make sure this is a non-void material.
489
    for (auto mat_id : material_) {
100 ✔
490
      if (mat_id == MATERIAL_VOID) {
50 !
491
        fatal_error(fmt::format(
×
492
          "Cell {} was specified with a density, but contains a void "
493
          "material. Density specification is only valid for cells "
494
          "filled with a non-void material.",
495
          id_));
×
496
      }
497
    }
498
  }
499

500
  // Read the region specification.
501
  std::string region_spec;
30,414 ✔
502
  if (check_for_node(cell_node, "region")) {
30,414 ✔
503
    region_spec = get_node_value(cell_node, "region");
19,807 ✔
504
  }
505

506
  // Get a tokenized representation of the region specification and apply De
507
  // Morgans law
508
  region_ = Region(region_spec, id_);
60,828 ✔
509

510
  // Read the translation vector.
511
  if (check_for_node(cell_node, "translation")) {
30,414 ✔
512
    if (fill_ == C_NONE) {
1,706 !
513
      fatal_error(fmt::format("Cannot apply a translation to cell {}"
×
514
                              " because it is not filled with another universe",
515
        id_));
×
516
    }
517

518
    auto xyz {get_node_array<double>(cell_node, "translation")};
1,706 ✔
519
    if (xyz.size() != 3) {
1,706 !
520
      fatal_error(
×
521
        fmt::format("Non-3D translation vector applied to cell {}", id_));
×
522
    }
523
    translation_ = xyz;
1,706 ✔
524
  }
1,706 ✔
525

526
  // Read the rotation transform.
527
  if (check_for_node(cell_node, "rotation")) {
30,414 ✔
528
    auto rot {get_node_array<double>(cell_node, "rotation")};
260 ✔
529
    set_rotation(rot);
260 ✔
530
  }
260 ✔
531
}
30,414 ✔
532

533
//==============================================================================
534

535
void CSGCell::to_hdf5_inner(hid_t group_id) const
27,039 ✔
536
{
537
  write_string(group_id, "geom_type", "csg", false);
27,039 ✔
538
  write_string(group_id, "region", region_.str(), false);
27,039 ✔
539
}
27,039 ✔
540

541
//==============================================================================
542
// Region implementation
543
//==============================================================================
544

545
namespace {
546

547
//! Expression tree node used while parsing a region specification
548
struct ParseNode {
396,611 ✔
549
  enum class Type { HALFSPACE, INTERSECTION, UNION };
550
  Type type;
551
  int32_t halfspace {0};
552
  vector<ParseNode> children;
553
};
554

555
//! Recursive descent parser for a tokenized region specification.
556
//! Intersection has higher precedence than union, and complement applies to the
557
//! half-space or parenthesized expression that follows it. Complements are
558
//! removed while parsing by applying De Morgan's laws, and nested operators of
559
//! the same type are merged.
560
class RegionParser {
561
public:
562
  RegionParser(const vector<int32_t>& tokens, int32_t cell_id)
19,935 ✔
563
    : tokens_(tokens), cell_id_(cell_id)
19,935 ✔
564
  {}
565

566
  ParseNode parse()
19,935 ✔
567
  {
568
    ParseNode root = parse_union(false);
19,935 ✔
569
    if (pos_ != tokens_.size()) {
19,935 !
NEW
570
      if (tokens_[pos_] == OP_RIGHT_PAREN)
×
NEW
571
        mismatched_parentheses();
×
NEW
572
      invalid();
×
573
    }
574
    return root;
19,935 ✔
NEW
575
  }
×
576

577
private:
578
  ParseNode parse_union(bool negate)
22,971 ✔
579
  {
580
    ParseNode node {
22,971 ✔
581
      negate ? ParseNode::Type::INTERSECTION : ParseNode::Type::UNION};
44,954 ✔
582
    add_child(node, parse_intersection(negate));
22,971 ✔
583
    while (pos_ < tokens_.size() && tokens_[pos_] == OP_UNION) {
28,602 ✔
584
      ++pos_;
5,631 ✔
585
      add_child(node, parse_intersection(negate));
11,262 ✔
586
    }
587
    return collapse(std::move(node));
22,971 ✔
588
  }
22,971 ✔
589

590
  ParseNode parse_intersection(bool negate)
28,602 ✔
591
  {
592
    ParseNode node {
28,602 ✔
593
      negate ? ParseNode::Type::UNION : ParseNode::Type::INTERSECTION};
55,192 ✔
594
    add_child(node, parse_unary(negate));
28,602 ✔
595
    while (pos_ < tokens_.size() && tokens_[pos_] == OP_INTERSECTION) {
63,265 ✔
596
      ++pos_;
34,663 ✔
597
      add_child(node, parse_unary(negate));
69,326 ✔
598
    }
599
    return collapse(std::move(node));
28,602 ✔
600
  }
28,602 ✔
601

602
  ParseNode parse_unary(bool negate)
64,261 ✔
603
  {
604
    if (pos_ >= tokens_.size())
64,261 !
NEW
605
      invalid();
×
606
    int32_t token = tokens_[pos_++];
64,261 ✔
607
    if (token == OP_COMPLEMENT) {
64,261 ✔
608
      return parse_unary(!negate);
996 ✔
609
    } else if (token == OP_LEFT_PAREN) {
63,265 ✔
610
      ParseNode node = parse_union(negate);
3,036 ✔
611
      if (pos_ >= tokens_.size() || tokens_[pos_] != OP_RIGHT_PAREN)
3,036 !
NEW
612
        mismatched_parentheses();
×
613
      ++pos_;
3,036 ✔
614
      return node;
3,036 ✔
615
    } else if (token < OP_UNION) {
63,265 !
616
      ParseNode node {ParseNode::Type::HALFSPACE};
60,229 ✔
617
      node.halfspace = negate ? -token : token;
60,229 ✔
618
      return node;
60,229 ✔
619
    } else if (token == OP_RIGHT_PAREN) {
60,229 !
NEW
620
      mismatched_parentheses();
×
621
    }
NEW
622
    invalid();
×
623
  }
624

625
  //! Add a child to an operator node, merging it into the node if it is an
626
  //! operator node of the same type
627
  static void add_child(ParseNode& parent, ParseNode child)
91,867 ✔
628
  {
629
    if (child.type == parent.type) {
91,867 ✔
630
      for (auto& grandchild : child.children)
7,399 ✔
631
        parent.children.push_back(std::move(grandchild));
5,870 ✔
632
    } else {
633
      parent.children.push_back(std::move(child));
90,338 ✔
634
    }
635
  }
91,867 ✔
636

637
  //! Replace an operator node with a single child by that child
638
  static ParseNode collapse(ParseNode node)
51,573 ✔
639
  {
640
    if (node.children.size() == 1)
51,573 ✔
641
      return std::move(node.children.front());
34,636 ✔
642
    return node;
16,937 ✔
643
  }
644

NEW
645
  [[noreturn]] void mismatched_parentheses() const
×
646
  {
NEW
647
    fatal_error(fmt::format(
×
NEW
648
      "Mismatched parentheses in region specification for cell {}", cell_id_));
×
649
  }
650

NEW
651
  [[noreturn]] void invalid() const
×
652
  {
NEW
653
    fatal_error(
×
NEW
654
      fmt::format("Invalid region specification for cell {}", cell_id_));
×
655
  }
656

657
  const vector<int32_t>& tokens_;
658
  int32_t cell_id_;
659
  std::size_t pos_ {0};
660
};
661

662
} // namespace
663

664
Region::Region(std::string region_spec, int32_t cell_id)
30,542 ✔
665
{
666
  vector<Node> nodes;
30,542 ✔
667

668
  vector<int32_t> tokens;
30,542 ✔
669

670
  // Check if region_spec is not empty.
671
  if (!region_spec.empty()) {
30,542 ✔
672
    // Parse all halfspaces and operators except for intersection (whitespace).
673
    for (int i = 0; i < region_spec.size();) {
139,784 ✔
674
      if (region_spec[i] == '(') {
119,849 ✔
675
        tokens.push_back(OP_LEFT_PAREN);
3,036 ✔
676
        i++;
3,036 ✔
677

678
      } else if (region_spec[i] == ')') {
116,813 ✔
679
        tokens.push_back(OP_RIGHT_PAREN);
3,036 ✔
680
        i++;
3,036 ✔
681

682
      } else if (region_spec[i] == '|') {
113,777 ✔
683
        tokens.push_back(OP_UNION);
5,631 ✔
684
        i++;
5,631 ✔
685

686
      } else if (region_spec[i] == '~') {
108,146 ✔
687
        tokens.push_back(OP_COMPLEMENT);
996 ✔
688
        i++;
996 ✔
689

690
      } else if (region_spec[i] == '-' || region_spec[i] == '+' ||
182,238 ✔
691
                 std::isdigit(region_spec[i])) {
75,088 ✔
692
        // This is the start of a halfspace specification.  Iterate j until we
693
        // find the end, then push-back everything between i and j.
694
        int j = i + 1;
60,229 ✔
695
        while (j < region_spec.size() && std::isdigit(region_spec[j])) {
119,224 ✔
696
          j++;
58,995 ✔
697
        }
698
        tokens.push_back(std::stoi(region_spec.substr(i, j - i)));
120,458 ✔
699
        i = j;
60,229 ✔
700

701
      } else if (std::isspace(region_spec[i])) {
46,921 !
702
        i++;
46,921 ✔
703

704
      } else {
705
        auto err_msg =
×
706
          fmt::format("Region specification contains invalid character, \"{}\"",
707
            region_spec[i]);
×
708
        fatal_error(err_msg);
×
709
      }
×
710
    }
711

712
    // Add in intersection operators where a missing operator is needed.
713
    int i = 0;
714
    while (i + 1 < tokens.size()) {
107,591 ✔
715
      bool left_compat {
87,656 ✔
716
        (tokens[i] < OP_UNION) || (tokens[i] == OP_RIGHT_PAREN)};
87,656 ✔
717
      bool right_compat {(tokens[i + 1] < OP_UNION) ||
87,656 ✔
718
                         (tokens[i + 1] == OP_LEFT_PAREN) ||
87,656 ✔
719
                         (tokens[i + 1] == OP_COMPLEMENT)};
10,027 ✔
720
      if (left_compat && right_compat) {
87,656 ✔
721
        tokens.insert(tokens.begin() + i + 1, OP_INTERSECTION);
34,663 ✔
722
      }
723
      i++;
724
    }
725

726
    // Convert user IDs to surface indices.
727
    for (auto& r : tokens) {
127,526 ✔
728
      if (r < OP_UNION) {
107,591 ✔
729
        const auto& it {model::surface_map.find(abs(r))};
60,229 !
730
        if (it == model::surface_map.end()) {
60,229 !
731
          throw std::runtime_error {
×
732
            "Invalid surface ID " + std::to_string(abs(r)) +
×
733
            " specified in region for cell " + std::to_string(cell_id) + "."};
×
734
        }
735
        r = (r > 0) ? it->second + 1 : -(it->second + 1);
60,229 ✔
736
      }
737
    }
738
  }
739

740
  // An empty specification is the region containing all of space
741
  if (tokens.empty())
30,542 ✔
742
    return;
10,607 ✔
743

744
  // Parse the tokens into an expression tree and store it in pre-order
745
  ParseNode root = RegionParser(tokens, cell_id).parse();
19,935 ✔
746
  bool simple = true;
19,935 ✔
747
  auto append = [&](const ParseNode& node, int32_t parent, auto& self) -> void {
75,637 ✔
748
    int32_t i = nodes.size();
75,637 ✔
749
    Node::Type type;
750
    switch (node.type) {
75,637 ✔
751
    case ParseNode::Type::HALFSPACE:
752
      type = Node::Type::HALFSPACE;
753
      break;
754
    case ParseNode::Type::INTERSECTION:
13,464 ✔
755
      type = Node::Type::INTERSECTION;
13,464 ✔
756
      break;
13,464 ✔
757
    default:
1,944 ✔
758
      type = Node::Type::UNION;
1,944 ✔
759
      simple = false;
1,944 ✔
760
    }
761
    nodes.push_back({type, node.halfspace, 0, parent});
75,637 ✔
762
    for (const auto& child : node.children)
131,339 ✔
763
      self(child, i, self);
55,702 ✔
764
    nodes[i].end = nodes.size();
75,637 ✔
765
  };
95,572 ✔
766
  append(root, -1, append);
19,935 ✔
767

768
  // Store the half-spaces in order. A simple region is just their
769
  // intersection, so its expression tree is not needed.
770
  for (const auto& node : nodes) {
95,572 ✔
771
    if (node.type == Node::Type::HALFSPACE)
75,637 ✔
772
      halfspaces_.push_back(node.halfspace);
60,229 ✔
773
  }
774
  if (!simple) {
19,935 ✔
775
    // Refer to the surfaces of the half-spaces in the tree by their position
776
    // in the list of distinct surfaces, so that quantities can be computed
777
    // once per surface when the region is evaluated
778
    vector<int32_t> surfaces;
1,222 ✔
779
    for (auto& node : nodes) {
17,936 ✔
780
      if (node.type != Node::Type::HALFSPACE)
16,714 ✔
781
        continue;
3,809 ✔
782
      int32_t i_surf = std::abs(node.halfspace);
12,905 ✔
783
      auto it = std::find(surfaces.begin(), surfaces.end(), i_surf);
12,905 ✔
784
      int32_t slot = it - surfaces.begin() + 1;
12,905 ✔
785
      if (it == surfaces.end())
12,905 ✔
786
        surfaces.push_back(i_surf);
9,339 ✔
787
      node.halfspace = node.halfspace > 0 ? slot : -slot;
12,905 ✔
788
    }
789
    model::max_region_surfaces =
1,222 ✔
790
      std::max<int>(model::max_region_surfaces, surfaces.size());
1,222 ✔
791
    complex_ =
1,222 ✔
792
      make_unique<Complex>(Complex {std::move(nodes), std::move(surfaces)});
2,444 ✔
793
  }
1,222 ✔
794
}
30,542 ✔
795

796
//==============================================================================
797

798
std::string Region::str() const
27,127 ✔
799
{
800
  std::string region_spec;
27,127 ✔
801
  auto write = [&](int32_t i, bool parentheses, auto& self) -> void {
15,850 ✔
802
    const auto& nodes = complex_->nodes;
15,850 ✔
803
    const Node& node = nodes[i];
15,850 ✔
804
    if (node.type == Node::Type::HALFSPACE) {
15,850 ✔
805
      // Note the off-by-one indexing
806
      int32_t token = surface_token(node.halfspace);
12,243 ✔
807
      auto surf_id = model::surfaces[abs(token) - 1]->id_;
12,243 ✔
808
      region_spec += fmt::format(" {}", (token > 0) ? surf_id : -surf_id);
34,907 ✔
809
      return;
12,243 ✔
810
    }
811
    if (parentheses)
3,607 ✔
812
      region_spec += " (";
3,607 ✔
813
    for (int32_t j = i + 1; j < node.end; j = nodes[j].end) {
18,335 ✔
814
      if (j > i + 1 && node.type == Node::Type::UNION)
14,728 ✔
815
        region_spec += " |";
14,728 ✔
816
      self(j, true, self);
14,728 ✔
817
    }
818
    if (parentheses)
3,607 ✔
819
      region_spec += " )";
15,850 ✔
820
  };
27,127 ✔
821
  if (complex_) {
27,127 ✔
822
    write(0, false, write);
1,122 ✔
823
  } else {
824
    for (int32_t token : halfspaces_) {
66,487 ✔
825
      // Note the off-by-one indexing
826
      auto surf_id = model::surfaces[abs(token) - 1]->id_;
40,482 ✔
827
      region_spec += fmt::format(" {}", (token > 0) ? surf_id : -surf_id);
80,964 ✔
828
    }
829
  }
830
  return region_spec;
27,127 ✔
UNCOV
831
}
×
832

833
//==============================================================================
834

835
template<typename F>
836
bool Region::evaluate(F&& in_halfspace) const
624,313,449 ✔
837
{
838
  // Evaluate the expression tree without recursion: descend to the first
839
  // half-space of each operator node and, after evaluating a half-space, move
840
  // up the tree until reaching an operator node whose value is not yet known.
841
  const auto& nodes = complex_->nodes;
624,313,449 ✔
842
  int32_t i = 0;
624,313,449 ✔
843
  while (true) {
844
    // Descend to the first half-space in this subtree
845
    while (nodes[i].type != Node::Type::HALFSPACE)
2,147,483,647 ✔
846
      ++i;
1,606,415,883 ✔
847
    bool value = in_halfspace(nodes[i].halfspace);
2,147,483,647 ✔
848

849
    // Move up the tree until reaching a node with children left to evaluate
850
    while (true) {
851
      int32_t i_parent = nodes[i].parent;
2,147,483,647 ✔
852
      if (i_parent < 0)
2,147,483,647 ✔
853
        return value;
624,313,449 ✔
854
      const Node& parent = nodes[i_parent];
2,147,483,647 ✔
855
      bool intersection = parent.type == Node::Type::INTERSECTION;
2,147,483,647 ✔
856
      int32_t next = nodes[i].end;
2,147,483,647 ✔
857
      if (value != intersection || next == parent.end) {
2,147,483,647 ✔
858
        // The value of the parent is known
859
        i = i_parent;
860
      } else {
861
        i = next;
862
        break;
863
      }
864
    }
865
  }
866
}
867

868
//==============================================================================
869

870
std::pair<double, int32_t> Region::distance(
2,147,483,647 ✔
871
  Position r, Direction u, int32_t on_surface, GeometryState* p) const
872
{
873
  if (!complex_) {
2,147,483,647 ✔
874
    return distance_to_nearest_surface(r, u, on_surface, false);
2,147,483,647 ✔
875
  }
876

877
  // The state of each surface is kept in the particle's working space, so that
878
  // no memory is allocated during transport
879
  if (!p) {
141,751,163 ✔
880
    vector<SurfaceState> local(complex_->surfaces.size());
56 ✔
881
    return distance_complex(r, u, on_surface, local.data());
56 ✔
882
  }
56 ✔
883
  auto& states = p->surface_states();
141,751,107 ✔
884
  if (complex_->surfaces.size() > states.size()) {
141,751,107 ✔
885
    return distance_complex_uncached(r, u, on_surface);
24 ✔
886
  }
887
  return distance_complex(r, u, on_surface, states.data());
141,751,083 ✔
888
}
889

890
//==============================================================================
891

892
std::pair<double, int32_t> Region::distance_to_nearest_surface(Position r,
2,147,483,647 ✔
893
  Direction u, int32_t on_surface, bool ignore_coincident_surfaces) const
894
{
895
  double min_dist {INFTY};
2,147,483,647 ✔
896
  int32_t i_surf {std::numeric_limits<int32_t>::max()};
2,147,483,647 ✔
897

898
  for (int32_t token : halfspaces_) {
2,147,483,647 ✔
899
    // Calculate the distance to this surface.
900
    // Note the off-by-one indexing
901
    bool coincident {std::abs(token) == std::abs(on_surface)};
2,147,483,647 ✔
902
    double d {model::surfaces[abs(token) - 1]->distance(r, u, coincident)};
2,147,483,647 ✔
903

904
    // Different surface definitions can represent the same geometric surface.
905
    // When the ray is already known to be on a surface, ignore intersections
906
    // with other surfaces at the same location to avoid repeatedly crossing
907
    // between them due to roundoff.
908
    if (ignore_coincident_surfaces && d < FP_COINCIDENT)
2,147,483,647 !
UNCOV
909
      continue;
×
910

911
    // Check if this distance is the new minimum.
912
    if (d < min_dist) {
2,147,483,647 ✔
913
      if (min_dist - d >= FP_PRECISION * min_dist) {
2,147,483,647 !
914
        min_dist = d;
2,147,483,647 ✔
915
        i_surf = -token;
2,147,483,647 ✔
916
      }
917
    }
918
  }
919

920
  return {min_dist, i_surf};
2,147,483,647 ✔
921
}
922

923
//==============================================================================
924

925
std::pair<double, int32_t> Region::distance_complex(
141,751,139 ✔
926
  Position r, Direction u, int32_t on_surface, SurfaceState* state) const
927
{
928
  // The boundary is found by moving from one surface crossing to the next
929
  // along the ray until crossing a surface changes whether the ray is in the
930
  // region. Rather than recomputing the distance to and the sense with respect
931
  // to every surface after each crossing, both are computed once per distinct
932
  // surface and then updated: the distance to every surface decreases by the
933
  // distance moved, and only surfaces at the current position (the surface
934
  // crossed and any other surfaces meeting it there) are evaluated again.
935
  // Distances updated by subtraction are only used to select the next
936
  // candidate surface; the distance to the candidate is recomputed from the
937
  // current position so that the result does not depend on roundoff.
938
  const auto& surfaces = complex_->surfaces;
141,751,139 ✔
939
  const int n = surfaces.size();
141,751,139 ✔
940

941
  // Evaluate the distance to and sense with respect to the surface in slot i.
942
  // If the ray is on the surface, its sense is given by on_surface.
943
  auto evaluate_surface = [&](int i, int32_t on_surface) {
2,147,483,647 ✔
944
    int32_t i_surf = surfaces[i];
2,147,483,647 ✔
945
    const auto& surf {*model::surfaces[i_surf - 1]};
2,147,483,647 ✔
946
    bool coincident = i_surf == std::abs(on_surface);
2,147,483,647 ✔
947
    state[i].sense = coincident ? on_surface > 0 : surf.sense(r, u);
2,147,483,647 ✔
948
    state[i].distance = surf.distance(r, u, coincident);
2,147,483,647 ✔
949
    state[i].stale = false;
2,147,483,647 ✔
950
  };
2,147,483,647 ✔
951
  auto in_region_now = [&]() {
745,077,984 ✔
952
    return evaluate([&](int32_t halfspace) {
2,147,483,647 ✔
953
      return state[std::abs(halfspace) - 1].sense == (halfspace > 0);
2,147,483,647 ✔
954
    });
955
  };
141,751,139 ✔
956

957
  for (int i = 0; i < n; ++i) {
1,945,148,960 ✔
958
    evaluate_surface(i, on_surface);
1,803,397,821 ✔
959
  }
960
  const bool in_region = in_region_now();
141,751,139 ✔
961
  double total_distance {0.0};
141,751,139 ✔
962

963
  while (true) {
782,686,734 ✔
964
    // Find the nearest surface crossing. When the ray is on a surface,
965
    // intersections with other surfaces at the same location are ignored to
966
    // avoid repeatedly crossing between them due to roundoff.
967
    double min_dist;
782,686,734 ✔
968
    int i_min;
782,686,734 ✔
969
    while (true) {
1,102,561,440 ✔
970
      min_dist = INFTY;
782,686,734 ✔
971
      i_min = -1;
782,686,734 ✔
972
      for (int i = 0; i < n; ++i) {
2,147,483,647 ✔
973
        double d = state[i].distance;
2,147,483,647 ✔
974
        if (on_surface != 0 && d < FP_COINCIDENT)
2,147,483,647 ✔
975
          continue;
8 ✔
976
        if (d < min_dist && min_dist - d >= FP_PRECISION * min_dist) {
2,147,483,647 !
977
          min_dist = d;
1,823,311,303 ✔
978
          i_min = i;
1,823,311,303 ✔
979
        }
980
      }
981
      if (i_min < 0 || !state[i_min].stale)
782,686,734 ✔
982
        break;
983
      int32_t i_surf = surfaces[i_min];
319,874,706 ✔
984
      state[i_min].distance = model::surfaces[i_surf - 1]->distance(
319,874,706 ✔
985
        r, u, i_surf == std::abs(on_surface));
319,874,706 ✔
986
      state[i_min].stale = false;
319,874,706 ✔
987
    }
319,874,706 ✔
988
    if (min_dist == INFTY) {
462,812,028 ✔
989
      return {INFTY, std::numeric_limits<int32_t>::max()};
1,236,322 ✔
990
    }
991

992
    // Move to the candidate surface and determine which side of it the ray is
993
    // entering. The surface normal is used instead of evaluating the surface
994
    // equation because accumulated roundoff may place the point slightly to
995
    // the wrong side of a curved surface.
996
    r += min_dist * u;
461,575,706 ✔
997
    total_distance += min_dist;
461,575,706 ✔
998
    int32_t i_surf = surfaces[i_min];
461,575,706 ✔
999
    if (u.dot(model::surfaces[i_surf - 1]->normal(r)) <= 0.0) {
461,575,706 ✔
1000
      i_surf = -i_surf;
161,744,140 ✔
1001
    }
1002

1003
    // Update the distances, and reevaluate the surfaces at the new position
1004
    for (int i = 0; i < n; ++i) {
2,147,483,647 ✔
1005
      state[i].distance -= min_dist;
2,147,483,647 ✔
1006
      state[i].stale = true;
2,147,483,647 ✔
1007
      if (i == i_min || state[i].distance < TINY_BIT) {
2,147,483,647 ✔
1008
        evaluate_surface(i, i_surf);
461,575,714 ✔
1009
      }
1010
    }
1011

1012
    // If crossing the candidate changes the region membership, it is a true
1013
    // boundary. Otherwise, continue the search from the virtual crossing.
1014
    if (in_region_now() != in_region) {
461,575,706 ✔
1015
      return {total_distance, i_surf};
140,514,817 ✔
1016
    }
1017
    on_surface = i_surf;
1018
  }
1019
}
1020

1021
//==============================================================================
1022

1023
std::pair<double, int32_t> Region::distance_complex_uncached(
24 ✔
1024
  Position r, Direction u, int32_t on_surface) const
1025
{
1026
  const bool in_region = contains_complex(r, u, on_surface);
24 ✔
1027
  double total_distance {0.0};
1028

1029
  while (true) {
72 ✔
1030
    auto [distance, i_surf] =
96 ✔
1031
      distance_to_nearest_surface(r, u, on_surface, on_surface != 0);
48 !
1032
    if (distance == INFTY) {
48 !
UNCOV
1033
      return {INFTY, std::numeric_limits<int32_t>::max()};
×
1034
    }
1035

1036
    // Move to the candidate surface and determine which side of it the ray is
1037
    // entering. The surface normal is used instead of evaluating the surface
1038
    // equation because accumulated roundoff may place the point slightly to
1039
    // the wrong side of a curved surface.
1040
    r += distance * u;
48 ✔
1041
    total_distance += distance;
48 ✔
1042
    i_surf = std::abs(i_surf);
48 ✔
1043
    const auto& surf {*model::surfaces[i_surf - 1]};
48 ✔
1044
    if (u.dot(surf.normal(r)) <= 0.0) {
48 ✔
1045
      i_surf = -i_surf;
16 ✔
1046
    }
1047

1048
    // If crossing the candidate changes the region membership, it is a true
1049
    // boundary. Otherwise, continue the search from the virtual crossing.
1050
    if (contains_complex(r, u, i_surf) != in_region) {
48 ✔
1051
      return {total_distance, i_surf};
24 ✔
1052
    }
1053
    on_surface = i_surf;
24 ✔
1054
  }
24 ✔
1055
}
1056

1057
//==============================================================================
1058

1059
bool Region::contains(Position r, Direction u, int32_t on_surface) const
2,147,483,647 ✔
1060
{
1061
  if (!complex_) {
2,147,483,647 ✔
1062
    return contains_simple(r, u, on_surface);
2,147,483,647 ✔
1063
  } else {
1064
    return contains_complex(r, u, on_surface);
20,986,532 ✔
1065
  }
1066
}
1067

1068
//==============================================================================
1069

1070
bool Region::contains_simple(Position r, Direction u, int32_t on_surface) const
2,147,483,647 ✔
1071
{
1072
  for (int32_t token : halfspaces_) {
2,147,483,647 ✔
1073
    // Evaluate the sense of particle with respect to the surface and see if
1074
    // the token matches the sense. If the particle's surface attribute is set
1075
    // and matches the token, that overrides the determination based on
1076
    // sense().
1077
    if (token == on_surface) {
2,147,483,647 ✔
1078
    } else if (-token == on_surface) {
2,147,483,647 ✔
1079
      return false;
1080
    } else {
1081
      // Note the off-by-one indexing
1082
      bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
2,147,483,647 ✔
1083
      if (sense != (token > 0)) {
2,147,483,647 ✔
1084
        return false;
1085
      }
1086
    }
1087
  }
1088
  return true;
1089
}
1090

1091
//==============================================================================
1092

1093
bool Region::contains_complex(Position r, Direction u, int32_t on_surface) const
20,986,604 ✔
1094
{
1095
  return evaluate([&](int32_t halfspace) {
120,399,152 ✔
1096
    int32_t token = surface_token(halfspace);
120,399,152 ✔
1097
    if (token == on_surface) {
120,399,152 ✔
1098
      return true;
1099
    } else if (-token == on_surface) {
110,761,083 ✔
1100
      return false;
1101
    } else {
1102
      // Note the off-by-one indexing
1103
      return model::surfaces[abs(token) - 1]->sense(r, u) == (token > 0);
108,627,382 ✔
1104
    }
1105
  });
20,986,604 ✔
1106
}
1107

1108
//==============================================================================
1109

1110
BoundingBox Region::bounding_box() const
72 ✔
1111
{
1112
  if (!complex_) {
72 ✔
1113
    BoundingBox bbox;
40 ✔
1114
    for (int32_t token : halfspaces_) {
160 ✔
1115
      bbox &= model::surfaces[abs(token) - 1]->bounding_box(token > 0);
120 ✔
1116
    }
1117
    return bbox;
40 ✔
1118
  }
1119

1120
  const auto& nodes = complex_->nodes;
32 ✔
1121
  auto box = [&](int32_t i, auto& self) -> BoundingBox {
392 ✔
1122
    const Node& node = nodes[i];
392 ✔
1123
    if (node.type == Node::Type::HALFSPACE) {
392 ✔
1124
      int32_t token = surface_token(node.halfspace);
288 ✔
1125
      return model::surfaces[abs(token) - 1]->bounding_box(token > 0);
288 ✔
1126
    }
1127
    BoundingBox bbox = self(i + 1, self);
104 ✔
1128
    for (int32_t j = nodes[i + 1].end; j < node.end; j = nodes[j].end) {
360 ✔
1129
      if (node.type == Node::Type::INTERSECTION) {
256 ✔
1130
        bbox &= self(j, self);
144 ✔
1131
      } else {
1132
        bbox = bbox | self(j, self);
112 ✔
1133
      }
1134
    }
1135
    return bbox;
104 ✔
1136
  };
32 ✔
1137
  return box(0, box);
32 ✔
1138
}
1139

1140
//==============================================================================
1141

1142
vector<int32_t> Region::surfaces() const
3,542 ✔
1143
{
1144
  return halfspaces_;
3,542 ✔
1145
}
1146

1147
//==============================================================================
1148

1149
int Region::n_surfaces() const
114,158,899 ✔
1150
{
1151
  return halfspaces_.size();
114,158,899 ✔
1152
}
1153

1154
//==============================================================================
1155
// Non-method functions
1156
//==============================================================================
1157

1158
void read_cells(pugi::xml_node node)
6,930 ✔
1159
{
1160
  // Count the number of cells.
1161
  auto cell_nodes = node.children("cell");
6,930 ✔
1162
  int n_cells = std::distance(cell_nodes.begin(), cell_nodes.end());
6,930 ✔
1163

1164
  // Loop over XML cell elements and populate the array.
1165
  model::cells.reserve(n_cells);
6,930 ✔
1166
  for (pugi::xml_node cell_node : node.children("cell")) {
37,320 ✔
1167
    model::cells.push_back(make_unique<CSGCell>(cell_node));
30,390 ✔
1168
  }
1169

1170
  // Fill the cell map.
1171
  for (int i = 0; i < model::cells.size(); i++) {
37,320 ✔
1172
    int32_t id = model::cells[i]->id_;
30,390 !
1173
    auto search = model::cell_map.find(id);
30,390 !
1174
    if (search == model::cell_map.end()) {
30,390 !
1175
      model::cell_map[id] = i;
30,390 ✔
1176
    } else {
1177
      fatal_error(
×
1178
        fmt::format("Two or more cells use the same unique ID: {}", id));
×
1179
    }
1180
  }
1181

1182
  read_dagmc_universes(node);
6,930 ✔
1183

1184
  populate_universes();
6,930 ✔
1185

1186
  // Allocate the cell overlap count if necessary.
1187
  if (settings::check_overlaps) {
6,930 ✔
1188
    model::overlap_check_count.resize(model::cells.size(), 0);
87 ✔
1189
  }
1190

1191
  if (model::cells.size() == 0) {
6,930 !
1192
    fatal_error("No cells were found in the geometry.xml file");
×
1193
  }
1194
}
6,930 ✔
1195

1196
void populate_universes()
6,931 ✔
1197
{
1198
  // Used to map universe index to the index of an implicit complement cell for
1199
  // DAGMC universes
1200
  std::unordered_map<int, int> implicit_comp_cells;
6,931 ✔
1201

1202
  // Populate the Universe vector and map.
1203
  for (int index_cell = 0; index_cell < model::cells.size(); index_cell++) {
37,497 ✔
1204
    int32_t uid = model::cells[index_cell]->universe_;
30,566 ✔
1205
    auto it = model::universe_map.find(uid);
30,566 ✔
1206
    if (it == model::universe_map.end()) {
30,566 ✔
1207
      model::universes.push_back(make_unique<Universe>());
38,690 ✔
1208
      model::universes.back()->id_ = uid;
19,345 ✔
1209
      model::universes.back()->cells_.push_back(index_cell);
19,345 ✔
1210
      model::universe_map[uid] = model::universes.size() - 1;
19,345 ✔
1211
    } else {
1212
#ifdef OPENMC_DAGMC_ENABLED
1213
      // Skip implicit complement cells for now
1214
      Universe* univ = model::universes[it->second].get();
1,468 !
1215
      DAGUniverse* dag_univ = dynamic_cast<DAGUniverse*>(univ);
1,468 !
1216
      if (dag_univ && (dag_univ->implicit_complement_idx() == index_cell)) {
1,468 ✔
1217
        implicit_comp_cells[it->second] = index_cell;
40 ✔
1218
        continue;
40 ✔
1219
      }
1220
#endif
1221

1222
      model::universes[it->second]->cells_.push_back(index_cell);
11,181 ✔
1223
    }
1224
  }
1225

1226
  // Add DAGUniverse implicit complement cells last
1227
  for (const auto& it : implicit_comp_cells) {
6,971 ✔
1228
    int index_univ = it.first;
40 ✔
1229
    int index_cell = it.second;
40 ✔
1230
    model::universes[index_univ]->cells_.push_back(index_cell);
40 !
1231
  }
1232

1233
  model::universes.shrink_to_fit();
6,931 ✔
1234
}
6,931 ✔
1235

1236
//==============================================================================
1237
// C-API functions
1238
//==============================================================================
1239

1240
extern "C" int openmc_cell_get_fill(
199 ✔
1241
  int32_t index, int* type, int32_t** indices, int32_t* n)
1242
{
1243
  if (index >= 0 && index < model::cells.size()) {
199 !
1244
    Cell& c {*model::cells[index]};
199 ✔
1245
    *type = static_cast<int>(c.type_);
199 ✔
1246
    if (c.type_ == Fill::MATERIAL) {
199 ✔
1247
      *indices = c.material_.data();
191 ✔
1248
      *n = c.material_.size();
191 ✔
1249
    } else {
1250
      *indices = &c.fill_;
8 ✔
1251
      *n = 1;
8 ✔
1252
    }
1253
  } else {
1254
    set_errmsg("Index in cells array is out of bounds.");
×
1255
    return OPENMC_E_OUT_OF_BOUNDS;
×
1256
  }
1257
  return 0;
1258
}
1259

1260
extern "C" int openmc_cell_set_fill(
8 ✔
1261
  int32_t index, int type, int32_t n, const int32_t* indices)
1262
{
1263
  Fill filltype = static_cast<Fill>(type);
8 ✔
1264
  if (index >= 0 && index < model::cells.size()) {
8 !
1265
    Cell& c {*model::cells[index]};
8 !
1266
    if (filltype == Fill::MATERIAL) {
8 !
1267
      c.type_ = Fill::MATERIAL;
8 ✔
1268
      c.material_.clear();
8 !
1269
      for (int i = 0; i < n; i++) {
16 ✔
1270
        int i_mat = indices[i];
8 ✔
1271
        if (i_mat == MATERIAL_VOID) {
8 !
1272
          c.material_.push_back(MATERIAL_VOID);
×
1273
        } else if (i_mat >= 0 && i_mat < model::materials.size()) {
8 !
1274
          c.material_.push_back(i_mat);
8 ✔
1275
        } else {
1276
          set_errmsg("Index in materials array is out of bounds.");
×
1277
          return OPENMC_E_OUT_OF_BOUNDS;
×
1278
        }
1279
      }
1280
      c.material_.shrink_to_fit();
8 ✔
1281
    } else if (filltype == Fill::UNIVERSE) {
×
1282
      c.type_ = Fill::UNIVERSE;
×
1283
    } else {
1284
      c.type_ = Fill::LATTICE;
×
1285
    }
1286
  } else {
1287
    set_errmsg("Index in cells array is out of bounds.");
×
1288
    return OPENMC_E_OUT_OF_BOUNDS;
×
1289
  }
1290
  return 0;
1291
}
1292

1293
extern "C" int openmc_cell_set_temperature(
64 ✔
1294
  int32_t index, double T, const int32_t* instance, bool set_contained)
1295
{
1296
  if (index < 0 || index >= model::cells.size()) {
64 !
1297
    set_errmsg("Index in cells array is out of bounds.");
×
1298
    return OPENMC_E_OUT_OF_BOUNDS;
×
1299
  }
1300

1301
  int32_t instance_index = instance ? *instance : -1;
64 ✔
1302
  try {
64 ✔
1303
    model::cells[index]->set_temperature(T, instance_index, set_contained);
64 ✔
1304
  } catch (const std::exception& e) {
×
1305
    set_errmsg(e.what());
×
1306
    return OPENMC_E_UNASSIGNED;
×
1307
  }
×
1308
  return 0;
1309
}
1310

1311
extern "C" int openmc_cell_set_density(
64 ✔
1312
  int32_t index, double density, const int32_t* instance, bool set_contained)
1313
{
1314
  if (index < 0 || index >= model::cells.size()) {
64 !
1315
    set_errmsg("Index in cells array is out of bounds.");
×
1316
    return OPENMC_E_OUT_OF_BOUNDS;
×
1317
  }
1318

1319
  int32_t instance_index = instance ? *instance : -1;
64 ✔
1320
  try {
64 ✔
1321
    model::cells[index]->set_density(density, instance_index, set_contained);
64 ✔
1322
  } catch (const std::exception& e) {
×
1323
    set_errmsg(e.what());
×
1324
    return OPENMC_E_UNASSIGNED;
×
1325
  }
×
1326
  return 0;
1327
}
1328

1329
extern "C" int openmc_cell_get_temperature(
7,003 ✔
1330
  int32_t index, const int32_t* instance, double* T)
1331
{
1332
  if (index < 0 || index >= model::cells.size()) {
7,003 !
1333
    set_errmsg("Index in cells array is out of bounds.");
×
1334
    return OPENMC_E_OUT_OF_BOUNDS;
×
1335
  }
1336

1337
  int32_t instance_index = instance ? *instance : -1;
7,003 ✔
1338
  try {
7,003 ✔
1339
    *T = model::cells[index]->temperature(instance_index);
7,003 ✔
1340
  } catch (const std::exception& e) {
×
1341
    set_errmsg(e.what());
×
1342
    return OPENMC_E_UNASSIGNED;
×
1343
  }
×
1344
  return 0;
7,003 ✔
1345
}
1346

1347
extern "C" int openmc_cell_get_density(
64 ✔
1348
  int32_t index, const int32_t* instance, double* density)
1349
{
1350
  if (index < 0 || index >= model::cells.size()) {
64 !
1351
    set_errmsg("Index in cells array is out of bounds.");
×
1352
    return OPENMC_E_OUT_OF_BOUNDS;
×
1353
  }
1354

1355
  int32_t instance_index = instance ? *instance : -1;
64 ✔
1356
  try {
64 ✔
1357
    if (model::cells[index]->type_ != Fill::MATERIAL) {
64 !
1358
      fatal_error(
×
1359
        fmt::format("Cell {}, instance {} is not filled with a material.",
×
1360
          model::cells[index]->id_, instance_index));
×
1361
    }
1362

1363
    int32_t mat_index = model::cells[index]->material(instance_index);
64 !
1364
    if (mat_index == MATERIAL_VOID) {
64 !
1365
      *density = 0.0;
×
1366
    } else {
1367
      *density = model::cells[index]->density_mult(instance_index) *
64 ✔
1368
                 model::materials[mat_index]->density_gpcc();
128 !
1369
    }
1370
  } catch (const std::exception& e) {
×
1371
    set_errmsg(e.what());
×
1372
    return OPENMC_E_UNASSIGNED;
×
1373
  }
×
1374
  return 0;
1375
}
1376

1377
//! Get the bounding box of a cell
1378
extern "C" int openmc_cell_bounding_box(
48 ✔
1379
  const int32_t index, double* llc, double* urc)
1380
{
1381

1382
  BoundingBox bbox;
48 ✔
1383

1384
  const auto& c = model::cells[index];
48 ✔
1385
  bbox = c->bounding_box();
48 ✔
1386

1387
  // set lower left corner values
1388
  llc[0] = bbox.min.x;
48 ✔
1389
  llc[1] = bbox.min.y;
48 ✔
1390
  llc[2] = bbox.min.z;
48 ✔
1391

1392
  // set upper right corner values
1393
  urc[0] = bbox.max.x;
48 ✔
1394
  urc[1] = bbox.max.y;
48 ✔
1395
  urc[2] = bbox.max.z;
48 ✔
1396

1397
  return 0;
48 ✔
1398
}
1399

1400
//! Get the name of a cell
1401
extern "C" int openmc_cell_get_name(int32_t index, const char** name)
317 ✔
1402
{
1403
  if (index < 0 || index >= model::cells.size()) {
317 !
1404
    set_errmsg("Index in cells array is out of bounds.");
×
1405
    return OPENMC_E_OUT_OF_BOUNDS;
×
1406
  }
1407

1408
  *name = model::cells[index]->name().data();
317 ✔
1409

1410
  return 0;
317 ✔
1411
}
1412

1413
//! Set the name of a cell
1414
extern "C" int openmc_cell_set_name(int32_t index, const char* name)
8 ✔
1415
{
1416
  if (index < 0 || index >= model::cells.size()) {
8 !
1417
    set_errmsg("Index in cells array is out of bounds.");
×
1418
    return OPENMC_E_OUT_OF_BOUNDS;
×
1419
  }
1420

1421
  model::cells[index]->set_name(name);
16 ✔
1422

1423
  return 0;
8 ✔
1424
}
1425

1426
//==============================================================================
1427
//! Define a containing (parent) cell
1428
//==============================================================================
1429

1430
//! Used to locate a universe fill in the geometry
1431
struct ParentCell {
1432
  bool operator==(const ParentCell& other) const
90 ✔
1433
  {
1434
    return cell_index == other.cell_index &&
90 !
1435
           lattice_index == other.lattice_index;
90 !
1436
  }
1437

1438
  bool operator<(const ParentCell& other) const
1439
  {
1440
    return cell_index < other.cell_index ||
1441
           (cell_index == other.cell_index &&
1442
             lattice_index < other.lattice_index);
1443
  }
1444

1445
  int64_t cell_index;
1446
  int64_t lattice_index;
1447
};
1448

1449
//! Structure used to insert ParentCell into hashed STL data structures
1450
struct ParentCellHash {
1451
  std::size_t operator()(const ParentCell& p) const
521 ✔
1452
  {
1453
    return 4096 * p.cell_index + p.lattice_index;
521 !
1454
  }
1455
};
1456

1457
//! Used to manage a traversal stack when locating parent cells of a cell
1458
//! instance in the model
1459
struct ParentCellStack {
101 ✔
1460

1461
  //! push method that adds to the parent_cells visited cells for this search
1462
  //! universe
1463
  void push(int32_t search_universe, const ParentCell& pc)
70 ✔
1464
  {
1465
    parent_cells_.push_back(pc);
70 ✔
1466
    // add parent cell to the set of cells we've visited for this search
1467
    // universe
1468
    visited_cells_[search_universe].insert(pc);
70 ✔
1469
  }
70 ✔
1470

1471
  //! removes the last parent_cell and clears the visited cells for the popped
1472
  //! cell's universe
1473
  void pop()
50 ✔
1474
  {
1475
    visited_cells_[this->current_univ()].clear();
50 ✔
1476
    parent_cells_.pop_back();
50 ✔
1477
  }
50 ✔
1478

1479
  //! checks whether or not the parent cell has been visited already for this
1480
  //! search universe
1481
  bool visited(int32_t search_universe, const ParentCell& parent_cell)
451 ✔
1482
  {
1483
    return visited_cells_[search_universe].count(parent_cell) != 0;
451 ✔
1484
  }
1485

1486
  //! return the next universe to search for a parent cell
1487
  int32_t current_univ() const
50 ✔
1488
  {
1489
    return model::cells[parent_cells_.back().cell_index]->universe_;
50 ✔
1490
  }
1491

1492
  //! indicates whether nor not parent cells are present on the stack
1493
  bool empty() const { return parent_cells_.empty(); }
50 ✔
1494

1495
  //! compute an instance for the provided distribcell index
1496
  int32_t compute_instance(int32_t distribcell_index) const
151 ✔
1497
  {
1498
    if (distribcell_index == C_NONE)
151 ✔
1499
      return 0;
1500

1501
    int32_t instance = 0;
80 ✔
1502
    for (const auto& parent_cell : this->parent_cells_) {
150 ✔
1503
      auto& cell = model::cells[parent_cell.cell_index];
70 !
1504
      if (cell->type_ == Fill::UNIVERSE) {
70 !
1505
        instance += cell->offset_[distribcell_index];
×
1506
      } else if (cell->type_ == Fill::LATTICE) {
70 !
1507
        auto& lattice = model::lattices[cell->fill_];
70 ✔
1508
        instance +=
70 ✔
1509
          lattice->offset(distribcell_index, parent_cell.lattice_index);
70 ✔
1510
      }
1511
    }
1512
    return instance;
1513
  }
1514

1515
  // Accessors
1516
  vector<ParentCell>& parent_cells() { return parent_cells_; }
101 ✔
1517
  const vector<ParentCell>& parent_cells() const { return parent_cells_; }
1518

1519
  // Data Members
1520
  vector<ParentCell> parent_cells_;
1521
  std::unordered_map<int32_t, std::unordered_set<ParentCell, ParentCellHash>>
1522
    visited_cells_;
1523
};
1524

1525
vector<ParentCell> Cell::find_parent_cells(
×
1526
  int32_t instance, const Position& r) const
1527
{
1528

1529
  // create a temporary particle
1530
  GeometryState dummy_particle {};
×
1531
  dummy_particle.r() = r;
×
1532
  dummy_particle.u() = {0., 0., 1.};
×
1533

1534
  return find_parent_cells(instance, dummy_particle);
×
1535
}
×
1536

1537
vector<ParentCell> Cell::find_parent_cells(
×
1538
  int32_t instance, GeometryState& p) const
1539
{
1540
  // look up the particle's location
1541
  exhaustive_find_cell(p);
×
1542
  const auto& coords = p.coord();
×
1543

1544
  // build a parent cell stack from the particle coordinates
1545
  ParentCellStack stack;
×
1546
  bool cell_found = false;
×
1547
  for (auto it = coords.begin(); it != coords.end(); it++) {
×
1548
    const auto& coord = *it;
×
1549
    const auto& cell = model::cells[coord.cell()];
×
1550
    // if the cell at this level matches the current cell, stop adding to the
1551
    // stack
1552
    if (coord.cell() == model::cell_map[this->id_]) {
×
1553
      cell_found = true;
1554
      break;
1555
    }
1556

1557
    // if filled with a lattice, get the lattice index from the next
1558
    // level in the coordinates to push to the stack
1559
    int lattice_idx = C_NONE;
×
1560
    if (cell->type_ == Fill::LATTICE) {
×
1561
      const auto& next_coord = *(it + 1);
×
1562
      lattice_idx = model::lattices[next_coord.lattice()]->get_flat_index(
×
1563
        next_coord.lattice_index());
1564
    }
1565
    stack.push(coord.universe(), {coord.cell(), lattice_idx});
×
1566
  }
1567

1568
  // if this loop finished because the cell was found and
1569
  // the instance matches the one requested in the call
1570
  // we have the correct path and can return the stack
1571
  if (cell_found &&
×
1572
      stack.compute_instance(this->distribcell_index_) == instance) {
×
1573
    return stack.parent_cells();
×
1574
  }
1575

1576
  // fall back on an exhaustive search for the cell's parents
1577
  return exhaustive_find_parent_cells(instance);
×
1578
}
×
1579

1580
vector<ParentCell> Cell::exhaustive_find_parent_cells(int32_t instance) const
101 ✔
1581
{
1582
  ParentCellStack stack;
101 ✔
1583
  // start with this cell's universe
1584
  int32_t prev_univ_idx;
101 ✔
1585
  int32_t univ_idx = this->universe_;
101 ✔
1586

1587
  while (true) {
151 ✔
1588
    prev_univ_idx = univ_idx;
151 ✔
1589

1590
    // search for a cell that is filled w/ this universe
1591
    for (const auto& cell : model::cells) {
1,134 ✔
1592
      // if this is a material-filled cell, move on
1593
      if (cell->type_ == Fill::MATERIAL)
1,053 ✔
1594
        continue;
592 ✔
1595

1596
      if (cell->type_ == Fill::UNIVERSE) {
461 ✔
1597
        // if this is in the set of cells previously visited for this universe,
1598
        // move on
1599
        if (stack.visited(univ_idx, {model::cell_map[cell->id_], C_NONE}))
291 !
1600
          continue;
×
1601

1602
        // if this cell contains the universe we're searching for, add it to the
1603
        // stack
1604
        if (cell->fill_ == univ_idx) {
291 !
1605
          stack.push(univ_idx, {model::cell_map[cell->id_], C_NONE});
×
1606
          univ_idx = cell->universe_;
×
1607
        }
1608
      } else if (cell->type_ == Fill::LATTICE) {
170 !
1609
        // retrieve the lattice and lattice universes
1610
        const auto& lattice = model::lattices[cell->fill_];
170 ✔
1611
        const auto& lattice_univs = lattice->universes_;
170 ✔
1612

1613
        // start search for universe
1614
        auto lat_it = lattice_univs.begin();
170 ✔
1615
        while (true) {
350 ✔
1616
          // find the next lattice cell with this universe
1617
          lat_it = std::find(lat_it, lattice_univs.end(), univ_idx);
260 ✔
1618
          if (lat_it == lattice_univs.end())
260 ✔
1619
            break;
1620

1621
          int lattice_idx = lat_it - lattice_univs.begin();
160 ✔
1622

1623
          // move iterator forward one to avoid finding the same entry
1624
          lat_it++;
160 ✔
1625
          if (stack.visited(
320 ✔
1626
                univ_idx, {model::cell_map[cell->id_], lattice_idx}))
160 ✔
1627
            continue;
90 ✔
1628

1629
          // add this cell and lattice index to the stack and exit loop
1630
          stack.push(univ_idx, {model::cell_map[cell->id_], lattice_idx});
70 ✔
1631
          univ_idx = cell->universe_;
70 ✔
1632
          break;
70 ✔
1633
        }
90 ✔
1634
      }
1635
      // if we've updated the universe, break
1636
      if (prev_univ_idx != univ_idx)
461 ✔
1637
        break;
1638
    } // end cell loop search for universe
1639

1640
    // if we're at the top of the geometry and the instance matches, we're done
1641
    if (univ_idx == model::root_universe &&
167 !
1642
        stack.compute_instance(this->distribcell_index_) == instance)
151 ✔
1643
      break;
1644

1645
    // if there is no match on the original cell's universe, report an error
1646
    if (univ_idx == this->universe_) {
50 !
1647
      fatal_error(
×
1648
        fmt::format("Could not find the parent cells for cell {}, instance {}.",
×
1649
          this->id_, instance));
×
1650
    }
1651

1652
    // if we don't find a suitable update, adjust the stack and continue
1653
    if (univ_idx == model::root_universe || univ_idx == prev_univ_idx) {
50 !
1654
      stack.pop();
50 ✔
1655
      univ_idx = stack.empty() ? this->universe_ : stack.current_univ();
50 !
1656
    }
1657

1658
  } // end while
1659

1660
  // reverse the stack so the highest cell comes first
1661
  std::reverse(stack.parent_cells().begin(), stack.parent_cells().end());
101 ✔
1662
  return stack.parent_cells();
202 ✔
1663
}
101 ✔
1664

1665
std::unordered_map<int32_t, vector<int32_t>> Cell::get_contained_cells(
131 ✔
1666
  int32_t instance, Position* hint) const
1667
{
1668
  std::unordered_map<int32_t, vector<int32_t>> contained_cells;
131 ✔
1669

1670
  // if this is a material-filled cell it has no contained cells
1671
  if (this->type_ == Fill::MATERIAL)
131 ✔
1672
    return contained_cells;
1673

1674
  // find the pathway through the geometry to this cell
1675
  vector<ParentCell> parent_cells;
101 !
1676

1677
  // if a positional hint is provided, attempt to do a fast lookup
1678
  // of the parent cells
1679
  parent_cells = hint ? find_parent_cells(instance, *hint)
101 !
1680
                      : exhaustive_find_parent_cells(instance);
101 ✔
1681

1682
  // if this cell is filled w/ a material, it contains no other cells
1683
  if (type_ != Fill::MATERIAL) {
101 !
1684
    this->get_contained_cells_inner(contained_cells, parent_cells);
101 ✔
1685
  }
1686

1687
  return contained_cells;
101 ✔
1688
}
131 ✔
1689

1690
//! Get all cells within this cell
1691
void Cell::get_contained_cells_inner(
92,153 ✔
1692
  std::unordered_map<int32_t, vector<int32_t>>& contained_cells,
1693
  vector<ParentCell>& parent_cells) const
1694
{
1695

1696
  // filled by material, determine instance based on parent cells
1697
  if (type_ == Fill::MATERIAL) {
92,153 ✔
1698
    int instance = 0;
91,462 ✔
1699
    if (this->distribcell_index_ >= 0) {
91,462 !
1700
      for (auto& parent_cell : parent_cells) {
274,384 ✔
1701
        auto& cell = model::cells[parent_cell.cell_index];
182,922 ✔
1702
        if (cell->type_ == Fill::UNIVERSE) {
182,922 ✔
1703
          instance += cell->offset_[distribcell_index_];
89,462 ✔
1704
        } else if (cell->type_ == Fill::LATTICE) {
93,460 !
1705
          auto& lattice = model::lattices[cell->fill_];
93,460 ✔
1706
          instance += lattice->offset(
93,460 ✔
1707
            this->distribcell_index_, parent_cell.lattice_index);
93,460 ✔
1708
        }
1709
      }
1710
    }
1711
    // add entry to contained cells
1712
    contained_cells[model::cell_map[id_]].push_back(instance);
91,462 ✔
1713
    // filled with universe, add the containing cell to the parent cells
1714
    // and recurse
1715
  } else if (type_ == Fill::UNIVERSE) {
691 ✔
1716
    parent_cells.push_back({model::cell_map[id_], -1});
591 ✔
1717
    auto& univ = model::universes[fill_];
591 ✔
1718
    for (auto cell_index : univ->cells_) {
3,703 ✔
1719
      auto& cell = model::cells[cell_index];
3,112 ✔
1720
      cell->get_contained_cells_inner(contained_cells, parent_cells);
3,112 ✔
1721
    }
1722
    parent_cells.pop_back();
591 ✔
1723
    // filled with a lattice, visit each universe in the lattice
1724
    // with a recursive call to collect the cell instances
1725
  } else if (type_ == Fill::LATTICE) {
100 !
1726
    auto& lattice = model::lattices[fill_];
100 ✔
1727
    for (auto i = lattice->begin(); i != lattice->end(); ++i) {
88,620 ✔
1728
      auto& univ = model::universes[*i];
88,520 ✔
1729
      parent_cells.push_back({model::cell_map[id_], i.indx_});
88,520 ✔
1730
      for (auto cell_index : univ->cells_) {
177,460 ✔
1731
        auto& cell = model::cells[cell_index];
88,940 ✔
1732
        cell->get_contained_cells_inner(contained_cells, parent_cells);
88,940 ✔
1733
      }
1734
      parent_cells.pop_back();
88,520 ✔
1735
    }
1736
  }
1737
}
92,153 ✔
1738

1739
//! Return the index in the cells array of a cell with a given ID
1740
extern "C" int openmc_get_cell_index(int32_t id, int32_t* index)
768 ✔
1741
{
1742
  auto it = model::cell_map.find(id);
768 ✔
1743
  if (it != model::cell_map.end()) {
768 ✔
1744
    *index = it->second;
760 ✔
1745
    return 0;
760 ✔
1746
  } else {
1747
    set_errmsg("No cell exists with ID=" + std::to_string(id) + ".");
16 ✔
1748
    return OPENMC_E_INVALID_ID;
8 ✔
1749
  }
1750
}
1751

1752
//! Return the ID of a cell
1753
extern "C" int openmc_cell_get_id(int32_t index, int32_t* id)
897,776 ✔
1754
{
1755
  if (index >= 0 && index < model::cells.size()) {
897,776 !
1756
    *id = model::cells[index]->id_;
897,776 ✔
1757
    return 0;
897,776 ✔
1758
  } else {
1759
    set_errmsg("Index in cells array is out of bounds.");
×
1760
    return OPENMC_E_OUT_OF_BOUNDS;
×
1761
  }
1762
}
1763

1764
//! Set the ID of a cell
1765
extern "C" int openmc_cell_set_id(int32_t index, int32_t id)
16 ✔
1766
{
1767
  if (index >= 0 && index < model::cells.size()) {
16 !
1768
    model::cells[index]->id_ = id;
16 ✔
1769
    model::cell_map[id] = index;
16 ✔
1770
    return 0;
16 ✔
1771
  } else {
1772
    set_errmsg("Index in cells array is out of bounds.");
×
1773
    return OPENMC_E_OUT_OF_BOUNDS;
×
1774
  }
1775
}
1776

1777
//! Return the translation vector of a cell
1778
extern "C" int openmc_cell_get_translation(int32_t index, double xyz[])
40 ✔
1779
{
1780
  if (index >= 0 && index < model::cells.size()) {
40 !
1781
    auto& cell = model::cells[index];
40 ✔
1782
    xyz[0] = cell->translation_.x;
40 ✔
1783
    xyz[1] = cell->translation_.y;
40 ✔
1784
    xyz[2] = cell->translation_.z;
40 ✔
1785
    return 0;
40 ✔
1786
  } else {
1787
    set_errmsg("Index in cells array is out of bounds.");
×
1788
    return OPENMC_E_OUT_OF_BOUNDS;
×
1789
  }
1790
}
1791

1792
//! Set the translation vector of a cell
1793
extern "C" int openmc_cell_set_translation(int32_t index, const double xyz[])
40 ✔
1794
{
1795
  if (index >= 0 && index < model::cells.size()) {
40 !
1796
    if (model::cells[index]->fill_ == C_NONE) {
40 ✔
1797
      set_errmsg(fmt::format("Cannot apply a translation to cell {}"
8 ✔
1798
                             " because it is not filled with another universe",
1799
        index));
1800
      return OPENMC_E_GEOMETRY;
8 ✔
1801
    }
1802
    model::cells[index]->translation_ = Position(xyz);
32 ✔
1803
    return 0;
32 ✔
1804
  } else {
1805
    set_errmsg("Index in cells array is out of bounds.");
×
1806
    return OPENMC_E_OUT_OF_BOUNDS;
×
1807
  }
1808
}
1809

1810
//! Return the rotation matrix of a cell
1811
extern "C" int openmc_cell_get_rotation(int32_t index, double rot[], size_t* n)
40 ✔
1812
{
1813
  if (index >= 0 && index < model::cells.size()) {
40 !
1814
    auto& cell = model::cells[index];
40 ✔
1815
    *n = cell->rotation_.size();
40 ✔
1816
    std::memcpy(rot, cell->rotation_.data(), *n * sizeof(cell->rotation_[0]));
40 ✔
1817
    return 0;
40 ✔
1818
  } else {
1819
    set_errmsg("Index in cells array is out of bounds.");
×
1820
    return OPENMC_E_OUT_OF_BOUNDS;
×
1821
  }
1822
}
1823

1824
//! Set the flattened rotation matrix of a cell
1825
extern "C" int openmc_cell_set_rotation(
48 ✔
1826
  int32_t index, const double rot[], size_t rot_len)
1827
{
1828
  if (index >= 0 && index < model::cells.size()) {
48 !
1829
    if (model::cells[index]->fill_ == C_NONE) {
48 ✔
1830
      set_errmsg(fmt::format("Cannot apply a rotation to cell {}"
8 ✔
1831
                             " because it is not filled with another universe",
1832
        index));
1833
      return OPENMC_E_GEOMETRY;
8 ✔
1834
    }
1835
    std::vector<double> vec_rot(rot, rot + rot_len);
40 ✔
1836
    model::cells[index]->set_rotation(vec_rot);
40 ✔
1837
    return 0;
40 ✔
1838
  } else {
48 ✔
1839
    set_errmsg("Index in cells array is out of bounds.");
×
1840
    return OPENMC_E_OUT_OF_BOUNDS;
×
1841
  }
1842
}
1843

1844
//! Get the number of instances of the requested cell
1845
extern "C" int openmc_cell_get_num_instances(
56 ✔
1846
  int32_t index, int32_t* num_instances)
1847
{
1848
  if (index < 0 || index >= model::cells.size()) {
56 !
1849
    set_errmsg("Index in cells array is out of bounds.");
×
1850
    return OPENMC_E_OUT_OF_BOUNDS;
×
1851
  }
1852
  *num_instances = model::cells[index]->n_instances();
56 ✔
1853
  return 0;
56 ✔
1854
}
1855

1856
//! Extend the cells array by n elements
1857
extern "C" int openmc_extend_cells(
16 ✔
1858
  int32_t n, int32_t* index_start, int32_t* index_end)
1859
{
1860
  if (index_start)
16 !
1861
    *index_start = model::cells.size();
16 ✔
1862
  if (index_end)
16 !
1863
    *index_end = model::cells.size() + n - 1;
×
1864
  for (int32_t i = 0; i < n; i++) {
32 ✔
1865
    model::cells.push_back(make_unique<CSGCell>());
16 ✔
1866
  }
1867
  return 0;
16 ✔
1868
}
1869

1870
extern "C" int cells_size()
72 ✔
1871
{
1872
  return model::cells.size();
72 ✔
1873
}
1874

1875
} // namespace openmc
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc