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

openmc-dev / openmc / 25282275446

03 May 2026 02:48PM UTC coverage: 81.376% (+0.002%) from 81.374%
25282275446

Pull #3934

github

web-flow
Merge c0d1ce4ce into 368ea069c
Pull Request #3934: Fix virtual surface crossing

17711 of 25585 branches covered (69.22%)

Branch coverage included in aggregate %.

19 of 19 new or added lines in 1 file covered. (100.0%)

2 existing lines in 2 files now uncovered.

58550 of 68129 relevant lines covered (85.94%)

47674112.13 hits per line

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

75.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 <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
//==============================================================================
40
// Cell implementation
41
//==============================================================================
42

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

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

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

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

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

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

96
  if (instance >= 0) {
9,634✔
97
    double sqrtkT = sqrtkT_.size() == 1 ? sqrtkT_.at(0) : sqrtkT_.at(instance);
9,548✔
98
    return sqrtkT * sqrtkT / K_BOLTZMANN;
9,548✔
99
  } else {
100
    return sqrtkT_[0] * sqrtkT_[0] / K_BOLTZMANN;
86✔
101
  }
102
}
103

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

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

120
  return density_mult(instance) * model::materials[mat_index]->density_gpcc();
396!
121
}
122

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

139
  if (type_ == Fill::MATERIAL) {
10,012✔
140
    if (instance >= 0) {
9,982✔
141
      // If temperature vector is not big enough, resize it first
142
      if (sqrtkT_.size() != n_instances())
9,905✔
143
        sqrtkT_.resize(n_instances(), sqrtkT_[0]);
45✔
144

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

161
    auto contained_cells = this->get_contained_cells(instance);
30✔
162
    for (const auto& entry : contained_cells) {
120✔
163
      auto& cell = model::cells[entry.first];
90!
164
      assert(cell->type_ == Fill::MATERIAL);
90!
165
      auto& instances = entry.second;
90✔
166
      for (auto instance : instances) {
315✔
167
        cell->set_temperature(T, instance);
225✔
168
      }
169
    }
170
  }
30✔
171
}
10,012✔
172

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

182
  if (type_ == Fill::MATERIAL) {
346✔
183
    const int32_t mat_index = material(instance);
331!
184
    if (mat_index == MATERIAL_VOID)
331!
185
      return;
186

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

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

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

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

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

231
    write_dataset(cell_group, "density", density);
198✔
232
  }
198✔
233

234
  close_group(cell_group);
231✔
235
}
231✔
236

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

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

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

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

260
  // Read densities
261
  if (object_exists(cell_group, "density")) {
253✔
262
    vector<double> density;
198✔
263
    read_dataset(cell_group, "density", density);
198✔
264

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

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

279
  close_group(cell_group);
253✔
280
}
253✔
281

282
void Cell::to_hdf5(hid_t cell_group) const
29,259✔
283
{
284

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

288
  if (!name_.empty()) {
29,259✔
289
    write_string(group, "name", name_, false);
6,529✔
290
  }
291

292
  write_dataset(group, "universe", model::universes[universe_]->id_);
29,259✔
293

294
  to_hdf5_inner(group);
29,259✔
295

296
  // Write fill information.
297
  if (type_ == Fill::MATERIAL) {
29,259✔
298
    write_dataset(group, "fill_type", "material");
23,723✔
299
    std::vector<int32_t> mat_ids;
23,723✔
300
    for (auto i_mat : material_) {
48,739✔
301
      if (i_mat != MATERIAL_VOID) {
25,016✔
302
        mat_ids.push_back(model::materials[i_mat]->id_);
16,591✔
303
      } else {
304
        mat_ids.push_back(MATERIAL_VOID);
8,425✔
305
      }
306
    }
307
    if (mat_ids.size() == 1) {
23,723✔
308
      write_dataset(group, "material", mat_ids[0]);
23,532✔
309
    } else {
310
      write_dataset(group, "material", mat_ids);
191✔
311
    }
312

313
    std::vector<double> temps;
23,723✔
314
    for (auto sqrtkT_val : sqrtkT_)
59,092✔
315
      temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
35,369✔
316
    write_dataset(group, "temperature", temps);
23,723✔
317

318
    write_dataset(group, "density_mult", density_mult_);
23,723✔
319

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

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

340
  close_group(group);
29,259✔
341
}
29,259✔
342

343
//==============================================================================
344
// CSGCell implementation
345
//==============================================================================
346

347
CSGCell::CSGCell(pugi::xml_node cell_node)
34,867✔
348
{
349
  if (check_for_node(cell_node, "id")) {
34,867!
350
    id_ = std::stoi(get_node_value(cell_node, "id"));
69,734✔
351
  } else {
352
    fatal_error("Must specify id of cell in geometry XML file.");
×
353
  }
354

355
  if (check_for_node(cell_node, "name")) {
34,867✔
356
    name_ = get_node_value(cell_node, "name");
8,472✔
357
  }
358

359
  if (check_for_node(cell_node, "universe")) {
34,867✔
360
    universe_ = std::stoi(get_node_value(cell_node, "universe"));
67,464✔
361
  } else {
362
    universe_ = 0;
1,135✔
363
  }
364

365
  // Make sure that either material or fill was specified, but not both.
366
  bool fill_present = check_for_node(cell_node, "fill");
34,867✔
367
  bool material_present = check_for_node(cell_node, "material");
34,867✔
368
  if (!(fill_present || material_present)) {
34,867!
369
    fatal_error(
×
370
      fmt::format("Neither material nor fill was specified for cell {}", id_));
×
371
  }
372
  if (fill_present && material_present) {
34,867!
373
    fatal_error(fmt::format("Cell {} has both a material and a fill specified; "
×
374
                            "only one can be specified per cell",
375
      id_));
×
376
  }
377

378
  if (fill_present) {
34,867✔
379
    fill_ = std::stoi(get_node_value(cell_node, "fill"));
14,470✔
380
    if (fill_ == universe_) {
7,235!
381
      fatal_error(fmt::format("Cell {} is filled with the same universe that "
×
382
                              "it is contained in.",
383
        id_));
×
384
    }
385
  } else {
386
    fill_ = C_NONE;
27,632✔
387
  }
388

389
  // Read the material element.  There can be zero materials (filled with a
390
  // universe), more than one material (distribmats), and some materials may
391
  // be "void".
392
  if (material_present) {
34,867✔
393
    vector<std::string> mats {
27,632✔
394
      get_node_array<std::string>(cell_node, "material", true)};
27,632✔
395
    if (mats.size() > 0) {
27,632!
396
      material_.reserve(mats.size());
27,632✔
397
      for (std::string mat : mats) {
56,584✔
398
        if (mat.compare("void") == 0) {
28,952✔
399
          material_.push_back(MATERIAL_VOID);
8,766✔
400
        } else {
401
          material_.push_back(std::stoi(mat));
20,186✔
402
        }
403
      }
28,952✔
404
    } else {
405
      fatal_error(fmt::format(
×
406
        "An empty material element was specified for cell {}", id_));
×
407
    }
408
  }
27,632✔
409

410
  // Read the temperature element which may be distributed like materials.
411
  if (check_for_node(cell_node, "temperature")) {
34,867✔
412
    sqrtkT_ = get_node_array<double>(cell_node, "temperature");
386✔
413
    sqrtkT_.shrink_to_fit();
386✔
414

415
    // Make sure this is a material-filled cell.
416
    if (material_.size() == 0) {
386!
417
      fatal_error(fmt::format(
×
418
        "Cell {} was specified with a temperature but no material. Temperature"
419
        "specification is only valid for cells filled with a material.",
420
        id_));
×
421
    }
422

423
    // Make sure all temperatures are non-negative.
424
    for (auto T : sqrtkT_) {
1,852✔
425
      if (T < 0) {
1,466!
426
        fatal_error(fmt::format(
×
427
          "Cell {} was specified with a negative temperature", id_));
×
428
      }
429
    }
430

431
    // Convert to sqrt(k*T).
432
    for (auto& T : sqrtkT_) {
1,852✔
433
      T = std::sqrt(K_BOLTZMANN * T);
1,466✔
434
    }
435
  }
436

437
  // Read the density element which can be distributed similar to temperature.
438
  // These get assigned to the density multiplier, requiring a division by
439
  // the material density.
440
  // Note: calculating the actual density multiplier is deferred until materials
441
  // are finalized. density_mult_ contains the true density in the meantime.
442
  if (check_for_node(cell_node, "density")) {
34,867✔
443
    density_mult_ = get_node_array<double>(cell_node, "density");
75✔
444
    density_mult_.shrink_to_fit();
75✔
445

446
    // Make sure this is a material-filled cell.
447
    if (material_.size() == 0) {
75!
448
      fatal_error(fmt::format(
×
449
        "Cell {} was specified with a density but no material. Density"
450
        "specification is only valid for cells filled with a material.",
451
        id_));
×
452
    }
453

454
    // Make sure this is a non-void material.
455
    for (auto mat_id : material_) {
150✔
456
      if (mat_id == MATERIAL_VOID) {
75!
457
        fatal_error(fmt::format(
×
458
          "Cell {} was specified with a density, but contains a void "
459
          "material. Density specification is only valid for cells "
460
          "filled with a non-void material.",
461
          id_));
×
462
      }
463
    }
464

465
    // Make sure all densities are non-negative and greater than zero.
466
    for (auto rho : density_mult_) {
1,230✔
467
      if (rho <= 0) {
1,155!
468
        fatal_error(fmt::format(
×
469
          "Cell {} was specified with a density less than or equal to zero",
470
          id_));
×
471
      }
472
    }
473
  }
474

475
  // Read the region specification.
476
  std::string region_spec;
34,867✔
477
  if (check_for_node(cell_node, "region")) {
34,867✔
478
    region_spec = get_node_value(cell_node, "region");
25,768✔
479
  }
480

481
  // Get a tokenized representation of the region specification and apply De
482
  // Morgans law
483
  Region region(region_spec, id_);
34,867✔
484
  region_ = region;
34,867✔
485

486
  // Read the translation vector.
487
  if (check_for_node(cell_node, "translation")) {
34,867✔
488
    if (fill_ == C_NONE) {
2,557!
489
      fatal_error(fmt::format("Cannot apply a translation to cell {}"
×
490
                              " because it is not filled with another universe",
491
        id_));
×
492
    }
493

494
    auto xyz {get_node_array<double>(cell_node, "translation")};
2,557✔
495
    if (xyz.size() != 3) {
2,557!
496
      fatal_error(
×
497
        fmt::format("Non-3D translation vector applied to cell {}", id_));
×
498
    }
499
    translation_ = xyz;
2,557✔
500
  }
2,557✔
501

502
  // Read the rotation transform.
503
  if (check_for_node(cell_node, "rotation")) {
34,867✔
504
    auto rot {get_node_array<double>(cell_node, "rotation")};
390✔
505
    set_rotation(rot);
390✔
506
  }
390✔
507
}
34,867✔
508

509
//==============================================================================
510

511
void CSGCell::to_hdf5_inner(hid_t group_id) const
29,095✔
512
{
513
  write_string(group_id, "geom_type", "csg", false);
29,095✔
514
  write_string(group_id, "region", region_.str(), false);
29,095✔
515
}
29,095✔
516

517
//==============================================================================
518

519
vector<int32_t>::iterator CSGCell::find_left_parenthesis(
×
520
  vector<int32_t>::iterator start, const vector<int32_t>& infix)
521
{
522
  // start search at zero
523
  int parenthesis_level = 0;
×
524
  auto it = start;
×
525
  while (it != infix.begin()) {
×
526
    // look at two tokens at a time
527
    int32_t one = *it;
×
528
    int32_t two = *(it - 1);
×
529

530
    // decrement parenthesis level if there are two adjacent surfaces
531
    if (one < OP_UNION && two < OP_UNION) {
×
532
      parenthesis_level--;
×
533
      // increment if there are two adjacent operators
534
    } else if (one >= OP_UNION && two >= OP_UNION) {
×
535
      parenthesis_level++;
×
536
    }
537

538
    // if the level gets to zero, return the position
539
    if (parenthesis_level == 0) {
×
540
      // move the iterator back one before leaving the loop
541
      // so that all tokens in the parenthesis block are included
542
      it--;
×
543
      break;
544
    }
545

546
    // continue loop, one token at a time
547
    it--;
548
  }
549
  return it;
×
550
}
551

552
//==============================================================================
553
// Region implementation
554
//==============================================================================
555

556
Region::Region(std::string region_spec, int32_t cell_id)
34,966✔
557
{
558
  // Check if region_spec is not empty.
559
  if (!region_spec.empty()) {
34,966✔
560
    // Parse all halfspaces and operators except for intersection (whitespace).
561
    for (int i = 0; i < region_spec.size();) {
155,696✔
562
      if (region_spec[i] == '(') {
129,829✔
563
        expression_.push_back(OP_LEFT_PAREN);
1,691✔
564
        i++;
1,691✔
565

566
      } else if (region_spec[i] == ')') {
128,138✔
567
        expression_.push_back(OP_RIGHT_PAREN);
1,691✔
568
        i++;
1,691✔
569

570
      } else if (region_spec[i] == '|') {
126,447✔
571
        expression_.push_back(OP_UNION);
4,058✔
572
        i++;
4,058✔
573

574
      } else if (region_spec[i] == '~') {
122,389✔
575
        expression_.push_back(OP_COMPLEMENT);
30✔
576
        i++;
30✔
577

578
      } else if (region_spec[i] == '-' || region_spec[i] == '+' ||
206,746!
579
                 std::isdigit(region_spec[i])) {
84,387✔
580
        // This is the start of a halfspace specification.  Iterate j until we
581
        // find the end, then push-back everything between i and j.
582
        int j = i + 1;
71,338✔
583
        while (j < region_spec.size() && std::isdigit(region_spec[j])) {
144,475✔
584
          j++;
73,137✔
585
        }
586
        expression_.push_back(std::stoi(region_spec.substr(i, j - i)));
142,676✔
587
        i = j;
71,338✔
588

589
      } else if (std::isspace(region_spec[i])) {
51,021!
590
        i++;
51,021✔
591

592
      } else {
593
        auto err_msg =
×
594
          fmt::format("Region specification contains invalid character, \"{}\"",
595
            region_spec[i]);
×
596
        fatal_error(err_msg);
×
597
      }
×
598
    }
599

600
    // Add in intersection operators where a missing operator is needed.
601
    int i = 0;
602
    while (i < expression_.size() - 1) {
120,221✔
603
      bool left_compat {
94,354✔
604
        (expression_[i] < OP_UNION) || (expression_[i] == OP_RIGHT_PAREN)};
94,354✔
605
      bool right_compat {(expression_[i + 1] < OP_UNION) ||
94,354✔
606
                         (expression_[i + 1] == OP_LEFT_PAREN) ||
94,354✔
607
                         (expression_[i + 1] == OP_COMPLEMENT)};
5,809✔
608
      if (left_compat && right_compat) {
94,354✔
609
        expression_.insert(expression_.begin() + i + 1, OP_INTERSECTION);
41,413✔
610
      }
611
      i++;
612
    }
613

614
    // Remove complement operators using DeMorgan's laws
615
    auto it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
25,867✔
616
    while (it != expression_.end()) {
25,897✔
617
      // Erase complement
618
      expression_.erase(it);
30✔
619

620
      // Define stop given left parenthesis or not
621
      auto stop = it;
30✔
622
      if (*it == OP_LEFT_PAREN) {
30!
623
        int depth = 1;
624
        do {
240✔
625
          stop++;
240✔
626
          if (*stop > OP_COMPLEMENT) {
240✔
627
            if (*stop == OP_RIGHT_PAREN) {
30!
628
              depth--;
30✔
629
            } else {
630
              depth++;
×
631
            }
632
          }
633
        } while (depth > 0);
240✔
634
        it++;
30✔
635
      }
636

637
      // apply DeMorgan's law to any surfaces/operators between these
638
      // positions in the RPN
639
      apply_demorgan(it, stop);
30✔
640
      // update iterator position
641
      it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
30✔
642
    }
643

644
    // Convert user IDs to surface indices.
645
    for (auto& r : expression_) {
146,058✔
646
      if (r < OP_UNION) {
120,191✔
647
        const auto& it {model::surface_map.find(abs(r))};
71,338!
648
        if (it == model::surface_map.end()) {
71,338!
649
          throw std::runtime_error {
×
650
            "Invalid surface ID " + std::to_string(abs(r)) +
×
651
            " specified in region for cell " + std::to_string(cell_id) + "."};
×
652
        }
653
        r = (r > 0) ? it->second + 1 : -(it->second + 1);
71,338✔
654
      }
655
    }
656

657
    // Check if this is a simple cell.
658
    simple_ = true;
25,867✔
659
    for (int32_t token : expression_) {
130,687✔
660
      if (token == OP_UNION) {
106,049✔
661
        simple_ = false;
1,229✔
662
        // Ensure intersections have precedence over unions
663
        enforce_precedence();
1,229✔
664
        break;
665
      }
666
    }
667

668
    // If this cell is simple, remove all the superfluous operator tokens.
669
    if (simple_) {
25,867✔
670
      for (auto it = expression_.begin(); it != expression_.end(); it++) {
122,606✔
671
        if (*it == OP_INTERSECTION || *it > OP_COMPLEMENT) {
97,968!
672
          expression_.erase(it);
36,665✔
673
          it--;
97,968✔
674
        }
675
      }
676
    }
677
    expression_.shrink_to_fit();
25,867✔
678

679
  } else {
680
    simple_ = true;
9,099✔
681
  }
682
}
34,966✔
683

684
//==============================================================================
685

686
void Region::apply_demorgan(
30✔
687
  vector<int32_t>::iterator start, vector<int32_t>::iterator stop)
688
{
689
  do {
210✔
690
    if (*start < OP_UNION) {
210✔
691
      *start *= -1;
120✔
692
    } else if (*start == OP_UNION) {
90!
693
      *start = OP_INTERSECTION;
×
694
    } else if (*start == OP_INTERSECTION) {
90!
695
      *start = OP_UNION;
90✔
696
    }
697
    start++;
210✔
698
  } while (start < stop);
210✔
699
}
30✔
700

701
//==============================================================================
702
//! Add precedence for infix regions so intersections have higher
703
//! precedence than unions using parentheses.
704
//==============================================================================
705

706
void Region::add_parentheses(int64_t start)
96✔
707
{
708
  int32_t start_token = expression_[start];
96!
709
  // Add left parenthesis and set new position to be after parenthesis
710
  if (start_token == OP_UNION) {
96!
711
    start += 2;
×
712
  }
713
  expression_.insert(expression_.begin() + start - 1, OP_LEFT_PAREN);
96✔
714

715
  // Add right parenthesis
716
  // While the start iterator is within the bounds of infix
717
  while (start + 1 < expression_.size()) {
430✔
718
    start++;
408✔
719

720
    // If the current token is an operator and is different than the start token
721
    if (expression_[start] >= OP_UNION && expression_[start] != start_token) {
408✔
722
      // Skip wrapped regions but save iterator position to check precedence and
723
      // add right parenthesis, right parenthesis position depends on the
724
      // operator, when the operator is a union then do not include the operator
725
      // in the region, when the operator is an intersection then include the
726
      // operator and next surface
727
      if (expression_[start] == OP_LEFT_PAREN) {
85✔
728
        int depth = 1;
729
        do {
44✔
730
          start++;
44✔
731
          if (expression_[start] > OP_COMPLEMENT) {
44✔
732
            if (expression_[start] == OP_RIGHT_PAREN) {
11!
733
              depth--;
11✔
734
            } else {
735
              depth++;
×
736
            }
737
          }
738
        } while (depth > 0);
44✔
739
      } else {
740
        if (start_token == OP_UNION) {
74!
741
          --start;
×
742
        }
743
        expression_.insert(expression_.begin() + start, OP_RIGHT_PAREN);
74✔
744
        return;
74✔
745
      }
746
    }
747
  }
748
  // If we get here a right parenthesis hasn't been placed
749
  expression_.push_back(OP_RIGHT_PAREN);
22✔
750
}
751

752
//==============================================================================
753
//! Add parentheses to enforce operator precedence in region expressions
754
//!
755
//! This function ensures that intersection operators have higher precedence
756
//! than union operators by adding parentheses where needed. For example:
757
//!   "1 2 | 3" becomes "(1 2) | 3"
758
//!   "1 | 2 3" becomes "1 | (2 3)"
759
//!
760
//! The algorithm uses stacks to track the current operator type and its
761
//! position at each parenthesis depth level. When it encounters a different
762
//! operator at the same depth, it adds parentheses to group the
763
//! higher-precedence operations.
764
//==============================================================================
765

766
void Region::enforce_precedence()
1,229✔
767
{
768
  // Stack tracking the operator type at each depth (0 = no operator seen yet)
769
  vector<int32_t> op_stack = {0};
1,229✔
770

771
  // Stack tracking where the operator sequence started at each depth
772
  vector<std::size_t> pos_stack = {0};
1,229✔
773

774
  for (int64_t i = 0; i < expression_.size(); ++i) {
24,469✔
775
    int32_t token = expression_[i];
23,240✔
776

777
    if (token == OP_LEFT_PAREN) {
23,240✔
778
      // Entering a new parenthesis level - push new tracking state
779
      op_stack.push_back(0);
1,887✔
780
      pos_stack.push_back(0);
1,887✔
781
      continue;
1,887✔
782
    } else if (token == OP_RIGHT_PAREN) {
21,353✔
783
      // Exiting a parenthesis level - pop tracking state (keep at least one)
784
      if (op_stack.size() > 1) {
1,846!
785
        op_stack.pop_back();
1,846✔
786
        pos_stack.pop_back();
1,846✔
787
      }
788
      continue;
1,846✔
789
    }
790

791
    if (token == OP_UNION || token == OP_INTERSECTION) {
19,507✔
792
      if (op_stack.back() == 0) {
9,139✔
793
        // First operator at this depth - record it and its position
794
        op_stack.back() = token;
3,160✔
795
        pos_stack.back() = i;
3,160✔
796
      } else if (token != op_stack.back()) {
5,979✔
797
        // Encountered a different operator at the same depth - need to add
798
        // parentheses to enforce precedence. Intersection has higher
799
        // precedence, so we parenthesize the intersection terms.
800
        if (op_stack.back() == OP_INTERSECTION) {
96✔
801
          add_parentheses(pos_stack.back());
48✔
802
        } else {
803
          add_parentheses(i);
48✔
804
        }
805

806
        // Restart the scan since we modified the expression
807
        i = -1; // Will be incremented to 0 by the for loop
96✔
808
        op_stack = {0};
96✔
809
        pos_stack = {0};
96✔
810
      }
811
    }
812
  }
813
}
1,229✔
814

815
//==============================================================================
816
//! Convert infix region specification to Reverse Polish Notation (RPN)
817
//!
818
//! This function uses the shunting-yard algorithm.
819
//==============================================================================
820

821
vector<int32_t> Region::generate_postfix(int32_t cell_id) const
44✔
822
{
823
  vector<int32_t> rpn;
44✔
824
  vector<int32_t> stack;
44✔
825

826
  for (int32_t token : expression_) {
990✔
827
    if (token < OP_UNION) {
946✔
828
      // If token is not an operator, add it to output
829
      rpn.push_back(token);
396✔
830
    } else if (token < OP_RIGHT_PAREN) {
550✔
831
      // Regular operators union, intersection, complement
832
      while (stack.size() > 0) {
561✔
833
        int32_t op = stack.back();
462✔
834

835
        if (op < OP_RIGHT_PAREN && ((token == OP_COMPLEMENT && token < op) ||
462!
836
                                     (token != OP_COMPLEMENT && token <= op))) {
209!
837
          // While there is an operator, op, on top of the stack, if the token
838
          // is left-associative and its precedence is less than or equal to
839
          // that of op or if the token is right-associative and its precedence
840
          // is less than that of op, move op to the output queue and push the
841
          // token on to the stack. Note that only complement is
842
          // right-associative.
843
          rpn.push_back(op);
209✔
844
          stack.pop_back();
209✔
845
        } else {
846
          break;
847
        }
848
      }
849

850
      stack.push_back(token);
352✔
851

852
    } else if (token == OP_LEFT_PAREN) {
198✔
853
      // If the token is a left parenthesis, push it onto the stack
854
      stack.push_back(token);
99✔
855

856
    } else {
857
      // If the token is a right parenthesis, move operators from the stack to
858
      // the output queue until reaching the left parenthesis.
859
      for (auto it = stack.rbegin(); *it != OP_LEFT_PAREN; it++) {
198✔
860
        // If we run out of operators without finding a left parenthesis, it
861
        // means there are mismatched parentheses.
862
        if (it == stack.rend()) {
99!
863
          fatal_error(fmt::format(
×
864
            "Mismatched parentheses in region specification for cell {}",
865
            cell_id));
866
        }
867
        rpn.push_back(stack.back());
99✔
868
        stack.pop_back();
99✔
869
      }
870

871
      // Pop the left parenthesis.
872
      stack.pop_back();
946✔
873
    }
874
  }
875

876
  while (stack.size() > 0) {
44✔
877
    int32_t op = stack.back();
44!
878

879
    // If the operator is a parenthesis it is mismatched.
880
    if (op >= OP_RIGHT_PAREN) {
44!
881
      fatal_error(fmt::format(
×
882
        "Mismatched parentheses in region specification for cell {}", cell_id));
883
    }
884

885
    rpn.push_back(stack.back());
44✔
886
    stack.pop_back();
88✔
887
  }
888

889
  return rpn;
44✔
890
}
44✔
891

892
//==============================================================================
893

894
std::string Region::str() const
29,194✔
895
{
896
  std::stringstream region_spec {};
29,194✔
897
  if (!expression_.empty()) {
29,194✔
898
    for (int32_t token : expression_) {
89,484✔
899
      if (token == OP_LEFT_PAREN) {
68,674✔
900
        region_spec << " (";
1,515✔
901
      } else if (token == OP_RIGHT_PAREN) {
67,159✔
902
        region_spec << " )";
1,515✔
903
      } else if (token == OP_COMPLEMENT) {
65,644!
904
        region_spec << " ~";
×
905
      } else if (token == OP_INTERSECTION) {
65,644✔
906
      } else if (token == OP_UNION) {
61,742✔
907
        region_spec << " |";
3,604✔
908
      } else {
909
        // Note the off-by-one indexing
910
        auto surf_id = model::surfaces[abs(token) - 1]->id_;
58,138✔
911
        region_spec << " " << ((token > 0) ? surf_id : -surf_id);
58,138✔
912
      }
913
    }
914
  }
915
  return region_spec.str();
58,388✔
916
}
29,194✔
917

918
//==============================================================================
919

920
std::pair<double, int32_t> Region::distance(
2,147,483,647✔
921
  Position r, Direction u, int32_t on_surface) const
922
{
923
  if (simple_) {
2,147,483,647✔
924
    return distance_simple(r, u, on_surface);
2,147,483,647✔
925
  } else {
926
    return distance_complex(r, u, on_surface);
18,930,470✔
927
  }
928
}
929

930
//==============================================================================
931

932
std::pair<double, int32_t> Region::distance_simple(
2,147,483,647✔
933
  Position r, Direction u, int32_t on_surface) const
934
{
935
  double min_dist {INFTY};
2,147,483,647✔
936
  int32_t i_surf {std::numeric_limits<int32_t>::max()};
2,147,483,647✔
937

938
  for (int32_t token : expression_) {
2,147,483,647✔
939
    // Ignore this token if it corresponds to an operator rather than a region.
940
    if (token >= OP_UNION)
2,147,483,647!
UNCOV
941
      continue;
×
942

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

948
    // Check if this distance is the new minimum.
949
    if (d < min_dist) {
2,147,483,647✔
950
      if (min_dist - d >= FP_PRECISION * min_dist) {
2,147,483,647!
951
        min_dist = d;
2,147,483,647✔
952
        i_surf = -token;
2,147,483,647✔
953
      }
954
    }
955
  }
956

957
  return {min_dist, i_surf};
2,147,483,647✔
958
}
959

960
//==============================================================================
961

962
std::pair<double, int32_t> Region::distance_complex(
18,930,470✔
963
  Position r, Direction u, int32_t on_surface) const
964
{
965
  double min_dist {INFTY};
18,930,470✔
966
  int32_t i_surf {std::numeric_limits<int32_t>::max()};
18,930,470✔
967

968
  for (int32_t token : expression_) {
865,135,318✔
969
    // Ignore this token if it corresponds to an operator rather than a region.
970
    if (token >= OP_UNION)
846,204,848✔
971
      continue;
513,720,070✔
972

973
    // Calculate the distance to this surface.
974
    // Note the off-by-one indexing
975
    bool coincident {std::abs(token) == std::abs(on_surface)};
332,484,778✔
976
    double d {model::surfaces[abs(token) - 1]->distance(r, u, coincident)};
332,484,778✔
977

978
    // Check if this distance is the new minimum.
979
    if (d < min_dist) {
332,484,778✔
980
      if (min_dist - d >= FP_PRECISION * min_dist) {
56,649,737!
981
        if (!contains_complex(r + (d + TINY_BIT) * u, u, on_surface)) {
56,649,737✔
982
          min_dist = d;
35,918,172✔
983
          i_surf = -token;
35,918,172✔
984
        }
985
      }
986
    }
987
  }
988
  return {min_dist, i_surf};
18,930,470✔
989
}
990

991
//==============================================================================
992

993
bool Region::contains(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
994
{
995
  if (simple_) {
2,147,483,647✔
996
    return contains_simple(r, u, on_surface);
2,147,483,647✔
997
  } else {
998
    return contains_complex(r, u, on_surface);
13,912,406✔
999
  }
1000
}
1001

1002
//==============================================================================
1003

1004
bool Region::contains_simple(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
1005
{
1006
  for (int32_t token : expression_) {
2,147,483,647✔
1007
    // Assume that no tokens are operators. Evaluate the sense of particle with
1008
    // respect to the surface and see if the token matches the sense. If the
1009
    // particle's surface attribute is set and matches the token, that
1010
    // overrides the determination based on sense().
1011
    if (token == on_surface) {
2,147,483,647✔
1012
    } else if (-token == on_surface) {
2,147,483,647✔
1013
      return false;
1014
    } else {
1015
      // Note the off-by-one indexing
1016
      bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
2,147,483,647✔
1017
      if (sense != (token > 0)) {
2,147,483,647✔
1018
        return false;
1019
      }
1020
    }
1021
  }
1022
  return true;
1023
}
1024

1025
//==============================================================================
1026

1027
bool Region::contains_complex(Position r, Direction u, int32_t on_surface) const
70,562,143✔
1028
{
1029
  bool in_cell = true;
70,562,143✔
1030
  int total_depth = 0;
70,562,143✔
1031

1032
  // For each token
1033
  for (auto it = expression_.begin(); it != expression_.end(); it++) {
1,195,612,578✔
1034
    int32_t token = *it;
1,149,630,765✔
1035

1036
    // If the token is a surface evaluate the sense
1037
    // If the token is a union or intersection check to
1038
    // short circuit
1039
    if (token < OP_UNION) {
1,149,630,765✔
1040
      if (token == on_surface) {
428,644,888✔
1041
        in_cell = true;
1042
      } else if (-token == on_surface) {
403,332,672✔
1043
        in_cell = false;
1044
      } else {
1045
        // Note the off-by-one indexing
1046
        bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
399,533,600✔
1047
        in_cell = (sense == (token > 0));
399,533,600✔
1048
      }
1049
    } else if ((token == OP_UNION && in_cell == true) ||
720,985,877✔
1050
               (token == OP_INTERSECTION && in_cell == false)) {
351,882,939✔
1051
      // If the total depth is zero return
1052
      if (total_depth == 0) {
139,207,917✔
1053
        return in_cell;
24,580,330✔
1054
      }
1055

1056
      total_depth--;
114,627,587✔
1057

1058
      // While the iterator is within the bounds of the vector
1059
      int depth = 1;
114,627,587✔
1060
      do {
1,875,444,002✔
1061
        // Get next token
1062
        it++;
1,875,444,002✔
1063
        int32_t next_token = *it;
1,875,444,002✔
1064

1065
        // If the token is an a parenthesis
1066
        if (next_token > OP_COMPLEMENT) {
1,875,444,002✔
1067
          // Adjust depth accordingly
1068
          if (next_token == OP_RIGHT_PAREN) {
537,927,133✔
1069
            depth--;
326,277,360✔
1070
          } else {
1071
            depth++;
211,649,773✔
1072
          }
1073
        }
1074
      } while (depth > 0);
1,875,444,002✔
1075
    } else if (token == OP_LEFT_PAREN) {
581,777,960✔
1076
      total_depth++;
169,161,401✔
1077
    } else if (token == OP_RIGHT_PAREN) {
412,616,559✔
1078
      total_depth--;
54,533,814✔
1079
    }
1080
  }
1081
  return in_cell;
1082
}
1083

1084
//==============================================================================
1085

1086
BoundingBox Region::bounding_box(int32_t cell_id) const
88✔
1087
{
1088
  if (simple_) {
88✔
1089
    return bounding_box_simple();
44✔
1090
  } else {
1091
    auto postfix = generate_postfix(cell_id);
44✔
1092
    return bounding_box_complex(postfix);
88✔
1093
  }
44✔
1094
}
1095

1096
//==============================================================================
1097

1098
BoundingBox Region::bounding_box_simple() const
44✔
1099
{
1100
  BoundingBox bbox;
44✔
1101
  for (int32_t token : expression_) {
176✔
1102
    bbox &= model::surfaces[abs(token) - 1]->bounding_box(token > 0);
132✔
1103
  }
1104
  return bbox;
44✔
1105
}
1106

1107
//==============================================================================
1108

1109
BoundingBox Region::bounding_box_complex(vector<int32_t> postfix) const
44✔
1110
{
1111
  vector<BoundingBox> stack(postfix.size());
44✔
1112
  int i_stack = -1;
44✔
1113

1114
  for (auto& token : postfix) {
792✔
1115
    if (token == OP_UNION) {
748✔
1116
      stack[i_stack - 1] = stack[i_stack - 1] | stack[i_stack];
154✔
1117
      i_stack--;
154✔
1118
    } else if (token == OP_INTERSECTION) {
594✔
1119
      stack[i_stack - 1] = stack[i_stack - 1] & stack[i_stack];
198✔
1120
      i_stack--;
198✔
1121
    } else {
1122
      i_stack++;
396✔
1123
      stack[i_stack] = model::surfaces[abs(token) - 1]->bounding_box(token > 0);
396✔
1124
    }
1125
  }
1126

1127
  assert(i_stack == 0);
44!
1128
  return stack.front();
44✔
1129
}
44✔
1130

1131
//==============================================================================
1132

1133
vector<int32_t> Region::surfaces() const
5,270✔
1134
{
1135
  if (simple_) {
5,270✔
1136
    return expression_;
5,250✔
1137
  }
1138

1139
  vector<int32_t> surfaces = expression_;
20✔
1140

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

1144
  while (it != surfaces.end()) {
60✔
1145
    surfaces.erase(it);
40✔
1146

1147
    it = std::find_if(surfaces.begin(), surfaces.end(),
40✔
1148
      [&](const auto& value) { return value >= OP_UNION; });
80!
1149
  }
1150

1151
  return surfaces;
20✔
1152
}
5,270✔
1153

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

1158
void read_cells(pugi::xml_node node)
8,342✔
1159
{
1160
  // Count the number of cells.
1161
  int n_cells = 0;
8,342✔
1162
  for (pugi::xml_node cell_node : node.children("cell")) {
43,209✔
1163
    n_cells++;
34,867✔
1164
  }
1165

1166
  // Loop over XML cell elements and populate the array.
1167
  model::cells.reserve(n_cells);
8,342✔
1168
  for (pugi::xml_node cell_node : node.children("cell")) {
43,209✔
1169
    model::cells.push_back(make_unique<CSGCell>(cell_node));
34,867✔
1170
  }
1171

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

1184
  read_dagmc_universes(node);
8,342✔
1185

1186
  populate_universes();
8,340✔
1187

1188
  // Allocate the cell overlap count if necessary.
1189
  if (settings::check_overlaps) {
8,340✔
1190
    model::overlap_check_count.resize(model::cells.size(), 0);
119✔
1191
  }
1192

1193
  if (model::cells.size() == 0) {
8,340!
1194
    fatal_error("No cells were found in the geometry.xml file");
×
1195
  }
1196
}
8,340✔
1197

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

1204
  // Populate the Universe vector and map.
1205
  for (int index_cell = 0; index_cell < model::cells.size(); index_cell++) {
43,425✔
1206
    int32_t uid = model::cells[index_cell]->universe_;
35,083✔
1207
    auto it = model::universe_map.find(uid);
35,083✔
1208
    if (it == model::universe_map.end()) {
35,083✔
1209
      model::universes.push_back(make_unique<Universe>());
40,216✔
1210
      model::universes.back()->id_ = uid;
20,108✔
1211
      model::universes.back()->cells_.push_back(index_cell);
20,108✔
1212
      model::universe_map[uid] = model::universes.size() - 1;
20,108✔
1213
    } else {
1214
#ifdef OPENMC_DAGMC_ENABLED
1215
      // Skip implicit complement cells for now
1216
      Universe* univ = model::universes[it->second].get();
2,014!
1217
      DAGUniverse* dag_univ = dynamic_cast<DAGUniverse*>(univ);
2,014!
1218
      if (dag_univ && (dag_univ->implicit_complement_idx() == index_cell)) {
2,014✔
1219
        implicit_comp_cells[it->second] = index_cell;
46✔
1220
        continue;
46✔
1221
      }
1222
#endif
1223

1224
      model::universes[it->second]->cells_.push_back(index_cell);
14,929✔
1225
    }
1226
  }
1227

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

1235
  model::universes.shrink_to_fit();
8,342✔
1236
}
8,342✔
1237

1238
//==============================================================================
1239
// C-API functions
1240
//==============================================================================
1241

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

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

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

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

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

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

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

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

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

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

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

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

1384
  BoundingBox bbox;
55✔
1385

1386
  const auto& c = model::cells[index];
55✔
1387
  bbox = c->bounding_box();
55✔
1388

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

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

1399
  return 0;
55✔
1400
}
1401

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

1410
  *name = model::cells[index]->name().data();
374✔
1411

1412
  return 0;
374✔
1413
}
1414

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

1423
  model::cells[index]->set_name(name);
22✔
1424

1425
  return 0;
11✔
1426
}
1427

1428
//==============================================================================
1429
//! Define a containing (parent) cell
1430
//==============================================================================
1431

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

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

1447
  int64_t cell_index;
1448
  int64_t lattice_index;
1449
};
1450

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

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

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

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

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

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

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

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

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

1517
  // Accessors
1518
  vector<ParentCell>& parent_cells() { return parent_cells_; }
136✔
1519
  const vector<ParentCell>& parent_cells() const { return parent_cells_; }
1520

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

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

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

1536
  return find_parent_cells(instance, dummy_particle);
×
1537
}
×
1538

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

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

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

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

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

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

1589
  while (true) {
211✔
1590
    const auto& univ = model::universes[univ_idx];
211✔
1591
    prev_univ_idx = univ_idx;
211✔
1592

1593
    // search for a cell that is filled w/ this universe
1594
    for (const auto& cell : model::cells) {
1,429✔
1595
      // if this is a material-filled cell, move on
1596
      if (cell->type_ == Fill::MATERIAL)
1,323✔
1597
        continue;
782✔
1598

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

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

1616
        // start search for universe
1617
        auto lat_it = lattice_univs.begin();
225✔
1618
        while (true) {
495✔
1619
          // find the next lattice cell with this universe
1620
          lat_it = std::find(lat_it, lattice_univs.end(), univ_idx);
360✔
1621
          if (lat_it == lattice_univs.end())
360✔
1622
            break;
1623

1624
          int lattice_idx = lat_it - lattice_univs.begin();
240✔
1625

1626
          // move iterator forward one to avoid finding the same entry
1627
          lat_it++;
240✔
1628
          if (stack.visited(
480✔
1629
                univ_idx, {model::cell_map[cell->id_], lattice_idx}))
240✔
1630
            continue;
135✔
1631

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

1643
    // if we're at the top of the geometry and the instance matches, we're done
1644
    if (univ_idx == model::root_universe &&
253!
1645
        stack.compute_instance(this->distribcell_index_) == instance)
211✔
1646
      break;
1647

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

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

1661
  } // end while
1662

1663
  // reverse the stack so the highest cell comes first
1664
  std::reverse(stack.parent_cells().begin(), stack.parent_cells().end());
136✔
1665
  return stack.parent_cells();
272✔
1666
}
136✔
1667

1668
std::unordered_map<int32_t, vector<int32_t>> Cell::get_contained_cells(
181✔
1669
  int32_t instance, Position* hint) const
1670
{
1671
  std::unordered_map<int32_t, vector<int32_t>> contained_cells;
181✔
1672

1673
  // if this is a material-filled cell it has no contained cells
1674
  if (this->type_ == Fill::MATERIAL)
181✔
1675
    return contained_cells;
1676

1677
  // find the pathway through the geometry to this cell
1678
  vector<ParentCell> parent_cells;
136!
1679

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

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

1690
  return contained_cells;
136✔
1691
}
181✔
1692

1693
//! Get all cells within this cell
1694
void Cell::get_contained_cells_inner(
134,178✔
1695
  std::unordered_map<int32_t, vector<int32_t>>& contained_cells,
1696
  vector<ParentCell>& parent_cells) const
1697
{
1698

1699
  // filled by material, determine instance based on parent cells
1700
  if (type_ == Fill::MATERIAL) {
134,178✔
1701
    int instance = 0;
133,532✔
1702
    if (this->distribcell_index_ >= 0) {
133,532!
1703
      for (auto& parent_cell : parent_cells) {
400,594✔
1704
        auto& cell = model::cells[parent_cell.cell_index];
267,062✔
1705
        if (cell->type_ == Fill::UNIVERSE) {
267,062✔
1706
          instance += cell->offset_[distribcell_index_];
132,032✔
1707
        } else if (cell->type_ == Fill::LATTICE) {
135,030!
1708
          auto& lattice = model::lattices[cell->fill_];
135,030✔
1709
          instance += lattice->offset(
135,030✔
1710
            this->distribcell_index_, parent_cell.lattice_index);
135,030✔
1711
        }
1712
      }
1713
    }
1714
    // add entry to contained cells
1715
    contained_cells[model::cell_map[id_]].push_back(instance);
133,532✔
1716
    // filled with universe, add the containing cell to the parent cells
1717
    // and recurse
1718
  } else if (type_ == Fill::UNIVERSE) {
646✔
1719
    parent_cells.push_back({model::cell_map[id_], -1});
526✔
1720
    auto& univ = model::universes[fill_];
526✔
1721
    for (auto cell_index : univ->cells_) {
3,033✔
1722
      auto& cell = model::cells[cell_index];
2,507✔
1723
      cell->get_contained_cells_inner(contained_cells, parent_cells);
2,507✔
1724
    }
1725
    parent_cells.pop_back();
526✔
1726
    // filled with a lattice, visit each universe in the lattice
1727
    // with a recursive call to collect the cell instances
1728
  } else if (type_ == Fill::LATTICE) {
120!
1729
    auto& lattice = model::lattices[fill_];
120✔
1730
    for (auto i = lattice->begin(); i != lattice->end(); ++i) {
131,340✔
1731
      auto& univ = model::universes[*i];
131,220✔
1732
      parent_cells.push_back({model::cell_map[id_], i.indx_});
131,220✔
1733
      for (auto cell_index : univ->cells_) {
262,755✔
1734
        auto& cell = model::cells[cell_index];
131,535✔
1735
        cell->get_contained_cells_inner(contained_cells, parent_cells);
131,535✔
1736
      }
1737
      parent_cells.pop_back();
131,220✔
1738
    }
1739
  }
1740
}
134,178✔
1741

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

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

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

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

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

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

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

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

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

1873
extern "C" int cells_size()
99✔
1874
{
1875
  return model::cells.size();
99✔
1876
}
1877

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