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

openmc-dev / openmc / 22286686330

22 Feb 2026 10:26PM UTC coverage: 81.639% (-0.05%) from 81.69%
22286686330

Pull #3808

github

web-flow
Merge c7a8090cd into 83a30f686
Pull Request #3808: Add properties to settings w/ documentation, c++ loading of filename, and python round-trip test

16819 of 23482 branches covered (71.63%)

Branch coverage included in aggregate %.

83 of 100 new or added lines in 8 files covered. (83.0%)

724 existing lines in 14 files now uncovered.

56872 of 66782 relevant lines covered (85.16%)

43142858.74 hits per line

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

75.25
/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
4,479✔
44
{
45
  return model::universes[universe_]->n_instances_;
4,479✔
46
}
47

48
void Cell::set_rotation(const vector<double>& rot)
339✔
49
{
50
  if (fill_ == C_NONE) {
339!
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) {
339!
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();
339✔
62
  rotation_.reserve(rot.size() == 9 ? 9 : 12);
339!
63
  if (rot.size() == 3) {
339!
64
    double phi = -rot[0] * PI / 180.0;
339✔
65
    double theta = -rot[1] * PI / 180.0;
339✔
66
    double psi = -rot[2] * PI / 180.0;
339✔
67
    rotation_.push_back(std::cos(theta) * std::cos(psi));
339✔
68
    rotation_.push_back(-std::cos(phi) * std::sin(psi) +
×
69
                        std::sin(phi) * std::sin(theta) * std::cos(psi));
339✔
70
    rotation_.push_back(std::sin(phi) * std::sin(psi) +
×
71
                        std::cos(phi) * std::sin(theta) * std::cos(psi));
339✔
72
    rotation_.push_back(std::cos(theta) * std::sin(psi));
339✔
73
    rotation_.push_back(std::cos(phi) * std::cos(psi) +
×
74
                        std::sin(phi) * std::sin(theta) * std::sin(psi));
339✔
75
    rotation_.push_back(-std::sin(phi) * std::cos(psi) +
×
76
                        std::cos(phi) * std::sin(theta) * std::sin(psi));
339✔
77
    rotation_.push_back(-std::sin(theta));
339✔
78
    rotation_.push_back(std::sin(phi) * std::cos(theta));
339✔
79
    rotation_.push_back(std::cos(phi) * std::cos(theta));
339✔
80

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

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

96
  if (instance >= 0) {
72✔
97
    double sqrtkT = sqrtkT_.size() == 1 ? sqrtkT_.at(0) : sqrtkT_.at(instance);
9!
98
    return sqrtkT * sqrtkT / K_BOLTZMANN;
9✔
99
  } else {
100
    return sqrtkT_[0] * sqrtkT_[0] / K_BOLTZMANN;
63✔
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);
2,147,483,647✔
109
  } else {
110
    return density_mult_[0];
63✔
111
  }
112
}
113

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

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

123
void Cell::set_temperature(double T, int32_t instance, bool set_contained)
384✔
124
{
125
  if (settings::temperature_method == TemperatureMethod::INTERPOLATION) {
384!
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) {
384✔
140
    if (instance >= 0) {
360✔
141
      // If temperature vector is not big enough, resize it first
142
      if (sqrtkT_.size() != n_instances())
297✔
143
        sqrtkT_.resize(n_instances(), sqrtkT_[0]);
36✔
144

145
      // Set temperature for the corresponding instance
146
      sqrtkT_.at(instance) = std::sqrt(K_BOLTZMANN * T);
297✔
147
    } else {
148
      // Set temperature for all instances
149
      for (auto& T_ : sqrtkT_) {
126✔
150
        T_ = std::sqrt(K_BOLTZMANN * T);
63✔
151
      }
152
    }
153
  } else {
154
    if (!set_contained) {
24!
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);
24✔
162
    for (const auto& entry : contained_cells) {
96✔
163
      auto& cell = model::cells[entry.first];
72✔
164
      assert(cell->type_ == Fill::MATERIAL);
54!
165
      auto& instances = entry.second;
72✔
166
      for (auto instance : instances) {
252✔
167
        cell->set_temperature(T, instance);
180✔
168
      }
169
    }
170
  }
24✔
171
}
384✔
172

173
void Cell::set_density(double density, int32_t instance, bool set_contained)
228✔
174
{
175
  if (type_ != Fill::MATERIAL && !set_contained) {
228!
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) {
228✔
183
    const int32_t mat_index = material(instance);
216✔
184
    if (mat_index == MATERIAL_VOID)
216!
185
      return;
×
186

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

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

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

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

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

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

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

237
void Cell::import_properties_hdf5(hid_t group,
144✔
238
  bool read_temperatures_from_properties, bool read_densities_from_properties)
239
{
240
  auto cell_group = open_group(group, fmt::format("cell {}", id_));
288✔
241

242
  if (read_temperatures_from_properties) {
144!
243
    // Read temperatures from file
244
    vector<double> temps;
144✔
245
    read_dataset(cell_group, "temperature", temps);
144✔
246

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

255
    // Modify temperatures for the cell
256
    sqrtkT_.clear();
144✔
257
    sqrtkT_.resize(temps.size());
144✔
258
    for (int64_t i = 0; i < temps.size(); ++i) {
252✔
259
      this->set_temperature(temps[i], i);
108✔
260
    }
261
  }
144✔
262

263
  if (read_densities_from_properties) {
144!
264
    // Read densities
265
    if (object_exists(cell_group, "density")) {
144✔
266
      vector<double> density;
108✔
267
      read_dataset(cell_group, "density", density);
108✔
268

269
      // Ensure number of densities makes sense
270
      auto n_density = density.size();
108✔
271
      if (n_density > 1 && n_density != n_instances()) {
108!
NEW
272
        fatal_error(fmt::format("Number of densities for cell {} "
×
273
                                "doesn't match number of instances",
NEW
274
          id_));
×
275
      }
276

277
      // Set densities.
278
      for (int32_t i = 0; i < n_density; ++i) {
216✔
279
        this->set_density(density[i], i);
108✔
280
      }
281
    }
108✔
282
  }
283

284
  close_group(cell_group);
144✔
285
}
144✔
286

287
void Cell::to_hdf5(hid_t cell_group) const
22,145✔
288
{
289

290
  // Create a group for this cell.
291
  auto group = create_group(cell_group, fmt::format("cell {}", id_));
44,290✔
292

293
  if (!name_.empty()) {
22,145✔
294
    write_string(group, "name", name_, false);
4,998✔
295
  }
296

297
  write_dataset(group, "universe", model::universes[universe_]->id_);
22,145✔
298

299
  to_hdf5_inner(group);
22,145✔
300

301
  // Write fill information.
302
  if (type_ == Fill::MATERIAL) {
22,145✔
303
    write_dataset(group, "fill_type", "material");
17,745✔
304
    std::vector<int32_t> mat_ids;
17,745✔
305
    for (auto i_mat : material_) {
36,558✔
306
      if (i_mat != MATERIAL_VOID) {
18,813✔
307
        mat_ids.push_back(model::materials[i_mat]->id_);
12,984✔
308
      } else {
309
        mat_ids.push_back(MATERIAL_VOID);
5,829✔
310
      }
311
    }
312
    if (mat_ids.size() == 1) {
17,745✔
313
      write_dataset(group, "material", mat_ids[0]);
17,589✔
314
    } else {
315
      write_dataset(group, "material", mat_ids);
156✔
316
    }
317

318
    std::vector<double> temps;
17,745✔
319
    for (auto sqrtkT_val : sqrtkT_)
37,287✔
320
      temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
19,542✔
321
    write_dataset(group, "temperature", temps);
17,745✔
322

323
    write_dataset(group, "density_mult", density_mult_);
17,745✔
324

325
  } else if (type_ == Fill::UNIVERSE) {
22,145✔
326
    write_dataset(group, "fill_type", "universe");
3,211✔
327
    write_dataset(group, "fill", model::universes[fill_]->id_);
3,211✔
328
    if (translation_ != Position(0, 0, 0)) {
3,211✔
329
      write_dataset(group, "translation", translation_);
1,503✔
330
    }
331
    if (!rotation_.empty()) {
3,211✔
332
      if (rotation_.size() == 12) {
216!
333
        std::array<double, 3> rot {rotation_[9], rotation_[10], rotation_[11]};
216✔
334
        write_dataset(group, "rotation", rot);
216✔
335
      } else {
336
        write_dataset(group, "rotation", rotation_);
×
337
      }
338
    }
339

340
  } else if (type_ == Fill::LATTICE) {
1,189!
341
    write_dataset(group, "fill_type", "lattice");
1,189✔
342
    write_dataset(group, "lattice", model::lattices[fill_]->id_);
1,189✔
343
  }
344

345
  close_group(group);
22,145✔
346
}
22,145✔
347

348
//==============================================================================
349
// CSGCell implementation
350
//==============================================================================
351

352
CSGCell::CSGCell(pugi::xml_node cell_node)
26,437✔
353
{
354
  if (check_for_node(cell_node, "id")) {
26,437!
355
    id_ = std::stoi(get_node_value(cell_node, "id"));
26,437✔
356
  } else {
357
    fatal_error("Must specify id of cell in geometry XML file.");
×
358
  }
359

360
  if (check_for_node(cell_node, "name")) {
26,437✔
361
    name_ = get_node_value(cell_node, "name");
6,417✔
362
  }
363

364
  if (check_for_node(cell_node, "universe")) {
26,437✔
365
    universe_ = std::stoi(get_node_value(cell_node, "universe"));
25,528✔
366
  } else {
367
    universe_ = 0;
909✔
368
  }
369

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

383
  if (fill_present) {
26,437✔
384
    fill_ = std::stoi(get_node_value(cell_node, "fill"));
5,660✔
385
    if (fill_ == universe_) {
5,660!
386
      fatal_error(fmt::format("Cell {} is filled with the same universe that "
×
387
                              "it is contained in.",
388
        id_));
×
389
    }
390
  } else {
391
    fill_ = C_NONE;
20,777✔
392
  }
393

394
  // Read the material element.  There can be zero materials (filled with a
395
  // universe), more than one material (distribmats), and some materials may
396
  // be "void".
397
  if (material_present) {
26,437✔
398
    vector<std::string> mats {
399
      get_node_array<std::string>(cell_node, "material", true)};
20,777✔
400
    if (mats.size() > 0) {
20,777!
401
      material_.reserve(mats.size());
20,777✔
402
      for (std::string mat : mats) {
42,649✔
403
        if (mat.compare("void") == 0) {
21,872✔
404
          material_.push_back(MATERIAL_VOID);
6,129✔
405
        } else {
406
          material_.push_back(std::stoi(mat));
15,743✔
407
        }
408
      }
21,872✔
409
    } else {
410
      fatal_error(fmt::format(
×
411
        "An empty material element was specified for cell {}", id_));
×
412
    }
413
  }
20,777✔
414

415
  // Read the temperature element which may be distributed like materials.
416
  if (check_for_node(cell_node, "temperature")) {
26,437✔
417
    sqrtkT_ = get_node_array<double>(cell_node, "temperature");
309✔
418
    sqrtkT_.shrink_to_fit();
309✔
419

420
    // Make sure this is a material-filled cell.
421
    if (material_.size() == 0) {
309!
422
      fatal_error(fmt::format(
×
423
        "Cell {} was specified with a temperature but no material. Temperature"
424
        "specification is only valid for cells filled with a material.",
425
        id_));
×
426
    }
427

428
    // Make sure all temperatures are non-negative.
429
    for (auto T : sqrtkT_) {
1,482✔
430
      if (T < 0) {
1,173!
431
        fatal_error(fmt::format(
×
432
          "Cell {} was specified with a negative temperature", id_));
×
433
      }
434
    }
435

436
    // Convert to sqrt(k*T).
437
    for (auto& T : sqrtkT_) {
1,482✔
438
      T = std::sqrt(K_BOLTZMANN * T);
1,173✔
439
    }
440
  }
441

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

451
    // Make sure this is a material-filled cell.
452
    if (material_.size() == 0) {
60!
453
      fatal_error(fmt::format(
×
454
        "Cell {} was specified with a density but no material. Density"
455
        "specification is only valid for cells filled with a material.",
456
        id_));
×
457
    }
458

459
    // Make sure this is a non-void material.
460
    for (auto mat_id : material_) {
120✔
461
      if (mat_id == MATERIAL_VOID) {
60!
462
        fatal_error(fmt::format(
×
463
          "Cell {} was specified with a density, but contains a void "
464
          "material. Density specification is only valid for cells "
465
          "filled with a non-void material.",
466
          id_));
×
467
      }
468
    }
469

470
    // Make sure all densities are non-negative and greater than zero.
471
    for (auto rho : density_mult_) {
984✔
472
      if (rho <= 0) {
924!
473
        fatal_error(fmt::format(
×
474
          "Cell {} was specified with a density less than or equal to zero",
475
          id_));
×
476
      }
477
    }
478
  }
479

480
  // Read the region specification.
481
  std::string region_spec;
26,437✔
482
  if (check_for_node(cell_node, "region")) {
26,437✔
483
    region_spec = get_node_value(cell_node, "region");
20,046✔
484
  }
485

486
  // Get a tokenized representation of the region specification and apply De
487
  // Morgans law
488
  Region region(region_spec, id_);
26,437✔
489
  region_ = region;
26,437✔
490

491
  // Read the translation vector.
492
  if (check_for_node(cell_node, "translation")) {
26,437✔
493
    if (fill_ == C_NONE) {
2,046!
494
      fatal_error(fmt::format("Cannot apply a translation to cell {}"
×
495
                              " because it is not filled with another universe",
496
        id_));
×
497
    }
498

499
    auto xyz {get_node_array<double>(cell_node, "translation")};
2,046✔
500
    if (xyz.size() != 3) {
2,046!
501
      fatal_error(
×
502
        fmt::format("Non-3D translation vector applied to cell {}", id_));
×
503
    }
504
    translation_ = xyz;
2,046✔
505
  }
2,046✔
506

507
  // Read the rotation transform.
508
  if (check_for_node(cell_node, "rotation")) {
26,437✔
509
    auto rot {get_node_array<double>(cell_node, "rotation")};
312✔
510
    set_rotation(rot);
312✔
511
  }
312✔
512
}
26,437✔
513

514
//==============================================================================
515

516
void CSGCell::to_hdf5_inner(hid_t group_id) const
22,145✔
517
{
518
  write_string(group_id, "geom_type", "csg", false);
22,145✔
519
  write_string(group_id, "region", region_.str(), false);
22,145✔
520
}
22,145✔
521

522
//==============================================================================
523

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

535
    // decrement parenthesis level if there are two adjacent surfaces
536
    if (one < OP_UNION && two < OP_UNION) {
×
537
      parenthesis_level--;
×
538
      // increment if there are two adjacent operators
539
    } else if (one >= OP_UNION && two >= OP_UNION) {
×
540
      parenthesis_level++;
×
541
    }
542

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

551
    // continue loop, one token at a time
552
    it--;
×
553
  }
554
  return it;
×
555
}
556

557
//==============================================================================
558
// Region implementation
559
//==============================================================================
560

561
Region::Region(std::string region_spec, int32_t cell_id)
26,518✔
562
{
563
  // Check if region_spec is not empty.
564
  if (!region_spec.empty()) {
26,518✔
565
    // Parse all halfspaces and operators except for intersection (whitespace).
566
    for (int i = 0; i < region_spec.size();) {
116,932✔
567
      if (region_spec[i] == '(') {
96,805✔
568
        expression_.push_back(OP_LEFT_PAREN);
1,000✔
569
        i++;
1,000✔
570

571
      } else if (region_spec[i] == ')') {
95,805✔
572
        expression_.push_back(OP_RIGHT_PAREN);
1,000✔
573
        i++;
1,000✔
574

575
      } else if (region_spec[i] == '|') {
94,805✔
576
        expression_.push_back(OP_UNION);
2,968✔
577
        i++;
2,968✔
578

579
      } else if (region_spec[i] == '~') {
91,837✔
580
        expression_.push_back(OP_COMPLEMENT);
24✔
581
        i++;
24✔
582

583
      } else if (region_spec[i] == '-' || region_spec[i] == '+' ||
154,849!
584
                 std::isdigit(region_spec[i])) {
63,036✔
585
        // This is the start of a halfspace specification.  Iterate j until we
586
        // find the end, then push-back everything between i and j.
587
        int j = i + 1;
53,889✔
588
        while (j < region_spec.size() && std::isdigit(region_spec[j])) {
109,601✔
589
          j++;
55,712✔
590
        }
591
        expression_.push_back(std::stoi(region_spec.substr(i, j - i)));
53,889✔
592
        i = j;
53,889✔
593

594
      } else if (std::isspace(region_spec[i])) {
37,924!
595
        i++;
37,924✔
596

597
      } else {
598
        auto err_msg =
599
          fmt::format("Region specification contains invalid character, \"{}\"",
600
            region_spec[i]);
×
601
        fatal_error(err_msg);
×
602
      }
×
603
    }
604

605
    // Add in intersection operators where a missing operator is needed.
606
    int i = 0;
20,127✔
607
    while (i < expression_.size() - 1) {
89,675✔
608
      bool left_compat {
609
        (expression_[i] < OP_UNION) || (expression_[i] == OP_RIGHT_PAREN)};
69,548✔
610
      bool right_compat {(expression_[i + 1] < OP_UNION) ||
69,548✔
611
                         (expression_[i + 1] == OP_LEFT_PAREN) ||
73,564✔
612
                         (expression_[i + 1] == OP_COMPLEMENT)};
4,016✔
613
      if (left_compat && right_compat) {
69,548✔
614
        expression_.insert(expression_.begin() + i + 1, OP_INTERSECTION);
30,794✔
615
      }
616
      i++;
69,548✔
617
    }
618

619
    // Remove complement operators using DeMorgan's laws
620
    auto it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
20,127✔
621
    while (it != expression_.end()) {
20,151✔
622
      // Erase complement
623
      expression_.erase(it);
24✔
624

625
      // Define stop given left parenthesis or not
626
      auto stop = it;
24✔
627
      if (*it == OP_LEFT_PAREN) {
24!
628
        int depth = 1;
24✔
629
        do {
630
          stop++;
192✔
631
          if (*stop > OP_COMPLEMENT) {
192✔
632
            if (*stop == OP_RIGHT_PAREN) {
24!
633
              depth--;
24✔
634
            } else {
635
              depth++;
×
636
            }
637
          }
638
        } while (depth > 0);
192✔
639
        it++;
24✔
640
      }
641

642
      // apply DeMorgan's law to any surfaces/operators between these
643
      // positions in the RPN
644
      apply_demorgan(it, stop);
24✔
645
      // update iterator position
646
      it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
24✔
647
    }
648

649
    // Convert user IDs to surface indices.
650
    for (auto& r : expression_) {
109,778✔
651
      if (r < OP_UNION) {
89,651✔
652
        const auto& it {model::surface_map.find(abs(r))};
53,889✔
653
        if (it == model::surface_map.end()) {
53,889!
654
          throw std::runtime_error {
×
655
            "Invalid surface ID " + std::to_string(abs(r)) +
×
656
            " specified in region for cell " + std::to_string(cell_id) + "."};
×
657
        }
658
        r = (r > 0) ? it->second + 1 : -(it->second + 1);
53,889✔
659
      }
660
    }
661

662
    // Check if this is a simple cell.
663
    simple_ = true;
20,127✔
664
    for (int32_t token : expression_) {
99,809✔
665
      if (token == OP_UNION) {
80,617✔
666
        simple_ = false;
935✔
667
        // Ensure intersections have precedence over unions
668
        enforce_precedence();
935✔
669
        break;
935✔
670
      }
671
    }
672

673
    // If this cell is simple, remove all the superfluous operator tokens.
674
    if (simple_) {
20,127✔
675
      for (auto it = expression_.begin(); it != expression_.end(); it++) {
93,816✔
676
        if (*it == OP_INTERSECTION || *it > OP_COMPLEMENT) {
74,624!
677
          expression_.erase(it);
27,716✔
678
          it--;
27,716✔
679
        }
680
      }
681
    }
682
    expression_.shrink_to_fit();
20,127✔
683

684
  } else {
685
    simple_ = true;
6,391✔
686
  }
687
}
26,518✔
688

689
//==============================================================================
690

691
void Region::apply_demorgan(
24✔
692
  vector<int32_t>::iterator start, vector<int32_t>::iterator stop)
693
{
694
  do {
695
    if (*start < OP_UNION) {
168✔
696
      *start *= -1;
96✔
697
    } else if (*start == OP_UNION) {
72!
698
      *start = OP_INTERSECTION;
×
699
    } else if (*start == OP_INTERSECTION) {
72!
700
      *start = OP_UNION;
72✔
701
    }
702
    start++;
168✔
703
  } while (start < stop);
168✔
704
}
24✔
705

706
//==============================================================================
707
//! Add precedence for infix regions so intersections have higher
708
//! precedence than unions using parentheses.
709
//==============================================================================
710

711
void Region::add_parentheses(int64_t start)
78✔
712
{
713
  int32_t start_token = expression_[start];
78✔
714
  // Add left parenthesis and set new position to be after parenthesis
715
  if (start_token == OP_UNION) {
78!
716
    start += 2;
×
717
  }
718
  expression_.insert(expression_.begin() + start - 1, OP_LEFT_PAREN);
78✔
719

720
  // Add right parenthesis
721
  // While the start iterator is within the bounds of infix
722
  while (start + 1 < expression_.size()) {
348✔
723
    start++;
330✔
724

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

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

771
void Region::enforce_precedence()
935✔
772
{
773
  // Stack tracking the operator type at each depth (0 = no operator seen yet)
774
  vector<int32_t> op_stack = {0};
935✔
775

776
  // Stack tracking where the operator sequence started at each depth
777
  vector<std::size_t> pos_stack = {0};
935✔
778

779
  for (int64_t i = 0; i < expression_.size(); ++i) {
16,787✔
780
    int32_t token = expression_[i];
15,852✔
781

782
    if (token == OP_LEFT_PAREN) {
15,852✔
783
      // Entering a new parenthesis level - push new tracking state
784
      op_stack.push_back(0);
1,159✔
785
      pos_stack.push_back(0);
1,159✔
786
      continue;
1,159✔
787
    } else if (token == OP_RIGHT_PAREN) {
14,693✔
788
      // Exiting a parenthesis level - pop tracking state (keep at least one)
789
      if (op_stack.size() > 1) {
1,126!
790
        op_stack.pop_back();
1,126✔
791
        pos_stack.pop_back();
1,126✔
792
      }
793
      continue;
1,126✔
794
    }
795

796
    if (token == OP_UNION || token == OP_INTERSECTION) {
13,567✔
797
      if (op_stack.back() == 0) {
6,316✔
798
        // First operator at this depth - record it and its position
799
        op_stack.back() = token;
2,130✔
800
        pos_stack.back() = i;
2,130✔
801
      } else if (token != op_stack.back()) {
4,186✔
802
        // Encountered a different operator at the same depth - need to add
803
        // parentheses to enforce precedence. Intersection has higher
804
        // precedence, so we parenthesize the intersection terms.
805
        if (op_stack.back() == OP_INTERSECTION) {
78✔
806
          add_parentheses(pos_stack.back());
39✔
807
        } else {
808
          add_parentheses(i);
39✔
809
        }
810

811
        // Restart the scan since we modified the expression
812
        i = -1; // Will be incremented to 0 by the for loop
78✔
813
        op_stack = {0};
78✔
814
        pos_stack = {0};
78✔
815
      }
816
    }
817
  }
818
}
935✔
819

820
//==============================================================================
821
//! Convert infix region specification to Reverse Polish Notation (RPN)
822
//!
823
//! This function uses the shunting-yard algorithm.
824
//==============================================================================
825

826
vector<int32_t> Region::generate_postfix(int32_t cell_id) const
36✔
827
{
828
  vector<int32_t> rpn;
36✔
829
  vector<int32_t> stack;
36✔
830

831
  for (int32_t token : expression_) {
810✔
832
    if (token < OP_UNION) {
774✔
833
      // If token is not an operator, add it to output
834
      rpn.push_back(token);
324✔
835
    } else if (token < OP_RIGHT_PAREN) {
450✔
836
      // Regular operators union, intersection, complement
837
      while (stack.size() > 0) {
459✔
838
        int32_t op = stack.back();
378✔
839

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

855
      stack.push_back(token);
288✔
856

857
    } else if (token == OP_LEFT_PAREN) {
162✔
858
      // If the token is a left parenthesis, push it onto the stack
859
      stack.push_back(token);
81✔
860

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

876
      // Pop the left parenthesis.
877
      stack.pop_back();
81✔
878
    }
879
  }
880

881
  while (stack.size() > 0) {
72✔
882
    int32_t op = stack.back();
36✔
883

884
    // If the operator is a parenthesis it is mismatched.
885
    if (op >= OP_RIGHT_PAREN) {
36!
886
      fatal_error(fmt::format(
×
887
        "Mismatched parentheses in region specification for cell {}", cell_id));
888
    }
889

890
    rpn.push_back(stack.back());
36✔
891
    stack.pop_back();
36✔
892
  }
893

894
  return rpn;
72✔
895
}
36✔
896

897
//==============================================================================
898

899
std::string Region::str() const
22,226✔
900
{
901
  std::stringstream region_spec {};
22,226✔
902
  if (!expression_.empty()) {
22,226✔
903
    for (int32_t token : expression_) {
68,046✔
904
      if (token == OP_LEFT_PAREN) {
51,698✔
905
        region_spec << " (";
974✔
906
      } else if (token == OP_RIGHT_PAREN) {
50,724✔
907
        region_spec << " )";
974✔
908
      } else if (token == OP_COMPLEMENT) {
49,750!
909
        region_spec << " ~";
×
910
      } else if (token == OP_INTERSECTION) {
49,750✔
911
      } else if (token == OP_UNION) {
47,095✔
912
        region_spec << " |";
2,723✔
913
      } else {
914
        // Note the off-by-one indexing
915
        auto surf_id = model::surfaces[abs(token) - 1]->id_;
44,372✔
916
        region_spec << " " << ((token > 0) ? surf_id : -surf_id);
44,372✔
917
      }
918
    }
919
  }
920
  return region_spec.str();
44,452✔
921
}
22,226✔
922

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

925
std::pair<double, int32_t> Region::distance(
2,147,483,647✔
926
  Position r, Direction u, int32_t on_surface) const
927
{
928
  double min_dist {INFTY};
2,147,483,647✔
929
  int32_t i_surf {std::numeric_limits<int32_t>::max()};
2,147,483,647✔
930

931
  for (int32_t token : expression_) {
2,147,483,647✔
932
    // Ignore this token if it corresponds to an operator rather than a region.
933
    if (token >= OP_UNION)
2,147,483,647✔
934
      continue;
116,975,136✔
935

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

941
    // Check if this distance is the new minimum.
942
    if (d < min_dist) {
2,147,483,647✔
943
      if (min_dist - d >= FP_PRECISION * min_dist) {
2,147,483,647!
944
        min_dist = d;
2,147,483,647✔
945
        i_surf = -token;
2,147,483,647✔
946
      }
947
    }
948
  }
949

950
  return {min_dist, i_surf};
2,147,483,647✔
951
}
952

953
//==============================================================================
954

955
bool Region::contains(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
956
{
957
  if (simple_) {
2,147,483,647✔
958
    return contains_simple(r, u, on_surface);
2,147,483,647✔
959
  } else {
960
    return contains_complex(r, u, on_surface);
5,552,091✔
961
  }
962
}
963

964
//==============================================================================
965

966
bool Region::contains_simple(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
967
{
968
  for (int32_t token : expression_) {
2,147,483,647✔
969
    // Assume that no tokens are operators. Evaluate the sense of particle with
970
    // respect to the surface and see if the token matches the sense. If the
971
    // particle's surface attribute is set and matches the token, that
972
    // overrides the determination based on sense().
973
    if (token == on_surface) {
2,147,483,647✔
974
    } else if (-token == on_surface) {
2,147,483,647✔
975
      return false;
1,021,597,472✔
976
    } else {
977
      // Note the off-by-one indexing
978
      bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
2,147,483,647✔
979
      if (sense != (token > 0)) {
2,147,483,647✔
980
        return false;
886,262,464✔
981
      }
982
    }
983
  }
984
  return true;
2,147,483,647✔
985
}
986

987
//==============================================================================
988

989
bool Region::contains_complex(Position r, Direction u, int32_t on_surface) const
5,552,091✔
990
{
991
  bool in_cell = true;
5,552,091✔
992
  int total_depth = 0;
5,552,091✔
993

994
  // For each token
995
  for (auto it = expression_.begin(); it != expression_.end(); it++) {
84,861,696✔
996
    int32_t token = *it;
80,804,792✔
997

998
    // If the token is a surface evaluate the sense
999
    // If the token is a union or intersection check to
1000
    // short circuit
1001
    if (token < OP_UNION) {
80,804,792✔
1002
      if (token == on_surface) {
35,402,740✔
1003
        in_cell = true;
2,689,203✔
1004
      } else if (-token == on_surface) {
32,713,537✔
1005
        in_cell = false;
525,228✔
1006
      } else {
1007
        // Note the off-by-one indexing
1008
        bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
32,188,309✔
1009
        in_cell = (sense == (token > 0));
32,188,309✔
1010
      }
1011
    } else if ((token == OP_UNION && in_cell == true) ||
45,402,052✔
1012
               (token == OP_INTERSECTION && in_cell == false)) {
22,022,284✔
1013
      // If the total depth is zero return
1014
      if (total_depth == 0) {
4,993,285✔
1015
        return in_cell;
1,495,187✔
1016
      }
1017

1018
      total_depth--;
3,498,098✔
1019

1020
      // While the iterator is within the bounds of the vector
1021
      int depth = 1;
3,498,098✔
1022
      do {
1023
        // Get next token
1024
        it++;
21,607,960✔
1025
        int32_t next_token = *it;
21,607,960✔
1026

1027
        // If the token is an a parenthesis
1028
        if (next_token > OP_COMPLEMENT) {
21,607,960✔
1029
          // Adjust depth accordingly
1030
          if (next_token == OP_RIGHT_PAREN) {
3,654,798✔
1031
            depth--;
3,576,448✔
1032
          } else {
1033
            depth++;
78,350✔
1034
          }
1035
        }
1036
      } while (depth > 0);
21,607,960✔
1037
    } else if (token == OP_LEFT_PAREN) {
43,906,865✔
1038
      total_depth++;
7,028,108✔
1039
    } else if (token == OP_RIGHT_PAREN) {
33,380,659✔
1040
      total_depth--;
3,530,010✔
1041
    }
1042
  }
1043
  return in_cell;
4,056,904✔
1044
}
1045

1046
//==============================================================================
1047

1048
BoundingBox Region::bounding_box(int32_t cell_id) const
72✔
1049
{
1050
  if (simple_) {
72✔
1051
    return bounding_box_simple();
36✔
1052
  } else {
1053
    auto postfix = generate_postfix(cell_id);
36✔
1054
    return bounding_box_complex(postfix);
36✔
1055
  }
36✔
1056
}
1057

1058
//==============================================================================
1059

1060
BoundingBox Region::bounding_box_simple() const
36✔
1061
{
1062
  BoundingBox bbox;
36✔
1063
  for (int32_t token : expression_) {
144✔
1064
    bbox &= model::surfaces[abs(token) - 1]->bounding_box(token > 0);
108✔
1065
  }
1066
  return bbox;
36✔
1067
}
1068

1069
//==============================================================================
1070

1071
BoundingBox Region::bounding_box_complex(vector<int32_t> postfix) const
36✔
1072
{
1073
  vector<BoundingBox> stack(postfix.size());
36✔
1074
  int i_stack = -1;
36✔
1075

1076
  for (auto& token : postfix) {
648✔
1077
    if (token == OP_UNION) {
612✔
1078
      stack[i_stack - 1] = stack[i_stack - 1] | stack[i_stack];
126✔
1079
      i_stack--;
126✔
1080
    } else if (token == OP_INTERSECTION) {
486✔
1081
      stack[i_stack - 1] = stack[i_stack - 1] & stack[i_stack];
162✔
1082
      i_stack--;
162✔
1083
    } else {
1084
      i_stack++;
324✔
1085
      stack[i_stack] = model::surfaces[abs(token) - 1]->bounding_box(token > 0);
324✔
1086
    }
1087
  }
1088

1089
  assert(i_stack == 0);
28!
1090
  return stack.front();
72✔
1091
}
36✔
1092

1093
//==============================================================================
1094

1095
vector<int32_t> Region::surfaces() const
4,224✔
1096
{
1097
  if (simple_) {
4,224✔
1098
    return expression_;
4,208✔
1099
  }
1100

1101
  vector<int32_t> surfaces = expression_;
16!
1102

1103
  auto it = std::find_if(surfaces.begin(), surfaces.end(),
16!
1104
    [&](const auto& value) { return value >= OP_UNION; });
32✔
1105

1106
  while (it != surfaces.end()) {
48✔
1107
    surfaces.erase(it);
32!
1108

1109
    it = std::find_if(surfaces.begin(), surfaces.end(),
32!
1110
      [&](const auto& value) { return value >= OP_UNION; });
96✔
1111
  }
1112

1113
  return surfaces;
16✔
1114
}
16✔
1115

1116
//==============================================================================
1117
// Non-method functions
1118
//==============================================================================
1119

1120
void read_cells(pugi::xml_node node)
6,475✔
1121
{
1122
  // Count the number of cells.
1123
  int n_cells = 0;
6,475✔
1124
  for (pugi::xml_node cell_node : node.children("cell")) {
32,912✔
1125
    n_cells++;
26,437✔
1126
  }
1127

1128
  // Loop over XML cell elements and populate the array.
1129
  model::cells.reserve(n_cells);
6,475✔
1130
  for (pugi::xml_node cell_node : node.children("cell")) {
32,912✔
1131
    model::cells.push_back(make_unique<CSGCell>(cell_node));
26,437✔
1132
  }
1133

1134
  // Fill the cell map.
1135
  for (int i = 0; i < model::cells.size(); i++) {
32,912✔
1136
    int32_t id = model::cells[i]->id_;
26,437✔
1137
    auto search = model::cell_map.find(id);
26,437✔
1138
    if (search == model::cell_map.end()) {
26,437!
1139
      model::cell_map[id] = i;
26,437✔
1140
    } else {
1141
      fatal_error(
×
1142
        fmt::format("Two or more cells use the same unique ID: {}", id));
×
1143
    }
1144
  }
1145

1146
  read_dagmc_universes(node);
6,475✔
1147

1148
  populate_universes();
6,475✔
1149

1150
  // Allocate the cell overlap count if necessary.
1151
  if (settings::check_overlaps) {
6,475✔
1152
    model::overlap_check_count.resize(model::cells.size(), 0);
97✔
1153
  }
1154

1155
  if (model::cells.size() == 0) {
6,475!
1156
    fatal_error("No cells were found in the geometry.xml file");
×
1157
  }
1158
}
6,475✔
1159

1160
void populate_universes()
6,475✔
1161
{
1162
  // Used to map universe index to the index of an implicit complement cell for
1163
  // DAGMC universes
1164
  std::unordered_map<int, int> implicit_comp_cells;
6,475✔
1165

1166
  // Populate the Universe vector and map.
1167
  for (int index_cell = 0; index_cell < model::cells.size(); index_cell++) {
32,912✔
1168
    int32_t uid = model::cells[index_cell]->universe_;
26,437✔
1169
    auto it = model::universe_map.find(uid);
26,437✔
1170
    if (it == model::universe_map.end()) {
26,437✔
1171
      model::universes.push_back(make_unique<Universe>());
14,934✔
1172
      model::universes.back()->id_ = uid;
14,934✔
1173
      model::universes.back()->cells_.push_back(index_cell);
14,934✔
1174
      model::universe_map[uid] = model::universes.size() - 1;
14,934✔
1175
    } else {
1176
#ifdef OPENMC_DAGMC_ENABLED
1177
      // Skip implicit complement cells for now
1178
      Universe* univ = model::universes[it->second].get();
1179
      DAGUniverse* dag_univ = dynamic_cast<DAGUniverse*>(univ);
1180
      if (dag_univ && (dag_univ->implicit_complement_idx() == index_cell)) {
1181
        implicit_comp_cells[it->second] = index_cell;
1182
        continue;
1183
      }
1184
#endif
1185

1186
      model::universes[it->second]->cells_.push_back(index_cell);
11,503✔
1187
    }
1188
  }
1189

1190
  // Add DAGUniverse implicit complement cells last
1191
  for (const auto& it : implicit_comp_cells) {
6,475!
1192
    int index_univ = it.first;
×
1193
    int index_cell = it.second;
×
1194
    model::universes[index_univ]->cells_.push_back(index_cell);
×
1195
  }
1196

1197
  model::universes.shrink_to_fit();
6,475✔
1198
}
6,475✔
1199

1200
//==============================================================================
1201
// C-API functions
1202
//==============================================================================
1203

1204
extern "C" int openmc_cell_get_fill(
45✔
1205
  int32_t index, int* type, int32_t** indices, int32_t* n)
1206
{
1207
  if (index >= 0 && index < model::cells.size()) {
45!
1208
    Cell& c {*model::cells[index]};
45✔
1209
    *type = static_cast<int>(c.type_);
45✔
1210
    if (c.type_ == Fill::MATERIAL) {
45!
1211
      *indices = c.material_.data();
45✔
1212
      *n = c.material_.size();
45✔
1213
    } else {
1214
      *indices = &c.fill_;
×
1215
      *n = 1;
×
1216
    }
1217
  } else {
1218
    set_errmsg("Index in cells array is out of bounds.");
×
1219
    return OPENMC_E_OUT_OF_BOUNDS;
×
1220
  }
1221
  return 0;
45✔
1222
}
1223

1224
extern "C" int openmc_cell_set_fill(
9✔
1225
  int32_t index, int type, int32_t n, const int32_t* indices)
1226
{
1227
  Fill filltype = static_cast<Fill>(type);
9✔
1228
  if (index >= 0 && index < model::cells.size()) {
9!
1229
    Cell& c {*model::cells[index]};
9✔
1230
    if (filltype == Fill::MATERIAL) {
9!
1231
      c.type_ = Fill::MATERIAL;
9✔
1232
      c.material_.clear();
9✔
1233
      for (int i = 0; i < n; i++) {
18✔
1234
        int i_mat = indices[i];
9✔
1235
        if (i_mat == MATERIAL_VOID) {
9!
1236
          c.material_.push_back(MATERIAL_VOID);
×
1237
        } else if (i_mat >= 0 && i_mat < model::materials.size()) {
9!
1238
          c.material_.push_back(i_mat);
9✔
1239
        } else {
1240
          set_errmsg("Index in materials array is out of bounds.");
×
1241
          return OPENMC_E_OUT_OF_BOUNDS;
×
1242
        }
1243
      }
1244
      c.material_.shrink_to_fit();
9✔
1245
    } else if (filltype == Fill::UNIVERSE) {
×
1246
      c.type_ = Fill::UNIVERSE;
×
1247
    } else {
1248
      c.type_ = Fill::LATTICE;
×
1249
    }
1250
  } else {
1251
    set_errmsg("Index in cells array is out of bounds.");
×
1252
    return OPENMC_E_OUT_OF_BOUNDS;
×
1253
  }
1254
  return 0;
9✔
1255
}
1256

1257
extern "C" int openmc_cell_set_temperature(
72✔
1258
  int32_t index, double T, const int32_t* instance, bool set_contained)
1259
{
1260
  if (index < 0 || index >= model::cells.size()) {
72!
1261
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
1262
    return OPENMC_E_OUT_OF_BOUNDS;
×
1263
  }
1264

1265
  int32_t instance_index = instance ? *instance : -1;
72✔
1266
  try {
1267
    model::cells[index]->set_temperature(T, instance_index, set_contained);
72✔
1268
  } catch (const std::exception& e) {
×
1269
    set_errmsg(e.what());
×
1270
    return OPENMC_E_UNASSIGNED;
×
1271
  }
×
1272
  return 0;
72✔
1273
}
1274

1275
extern "C" int openmc_cell_set_density(
72✔
1276
  int32_t index, double density, const int32_t* instance, bool set_contained)
1277
{
1278
  if (index < 0 || index >= model::cells.size()) {
72!
1279
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
1280
    return OPENMC_E_OUT_OF_BOUNDS;
×
1281
  }
1282

1283
  int32_t instance_index = instance ? *instance : -1;
72✔
1284
  try {
1285
    model::cells[index]->set_density(density, instance_index, set_contained);
72✔
1286
  } catch (const std::exception& e) {
×
1287
    set_errmsg(e.what());
×
1288
    return OPENMC_E_UNASSIGNED;
×
1289
  }
×
1290
  return 0;
72✔
1291
}
1292

1293
extern "C" int openmc_cell_get_temperature(
72✔
1294
  int32_t index, const int32_t* instance, double* T)
1295
{
1296
  if (index < 0 || index >= model::cells.size()) {
72!
1297
    strcpy(openmc_err_msg, "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;
72✔
1302
  try {
1303
    *T = model::cells[index]->temperature(instance_index);
72✔
1304
  } catch (const std::exception& e) {
×
1305
    set_errmsg(e.what());
×
1306
    return OPENMC_E_UNASSIGNED;
×
1307
  }
×
1308
  return 0;
72✔
1309
}
1310

1311
extern "C" int openmc_cell_get_density(
72✔
1312
  int32_t index, const int32_t* instance, double* density)
1313
{
1314
  if (index < 0 || index >= model::cells.size()) {
72!
1315
    strcpy(openmc_err_msg, "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;
72✔
1320
  try {
1321
    if (model::cells[index]->type_ != Fill::MATERIAL) {
72!
1322
      fatal_error(
×
1323
        fmt::format("Cell {}, instance {} is not filled with a material.",
×
1324
          model::cells[index]->id_, instance_index));
×
1325
    }
1326

1327
    int32_t mat_index = model::cells[index]->material(instance_index);
72✔
1328
    if (mat_index == MATERIAL_VOID) {
72!
1329
      *density = 0.0;
×
1330
    } else {
1331
      *density = model::cells[index]->density_mult(instance_index) *
144✔
1332
                 model::materials[mat_index]->density_gpcc();
72✔
1333
    }
1334
  } catch (const std::exception& e) {
×
1335
    set_errmsg(e.what());
×
1336
    return OPENMC_E_UNASSIGNED;
×
1337
  }
×
1338
  return 0;
72✔
1339
}
1340

1341
//! Get the bounding box of a cell
1342
extern "C" int openmc_cell_bounding_box(
45✔
1343
  const int32_t index, double* llc, double* urc)
1344
{
1345

1346
  BoundingBox bbox;
45✔
1347

1348
  const auto& c = model::cells[index];
45✔
1349
  bbox = c->bounding_box();
45✔
1350

1351
  // set lower left corner values
1352
  llc[0] = bbox.min.x;
45✔
1353
  llc[1] = bbox.min.y;
45✔
1354
  llc[2] = bbox.min.z;
45✔
1355

1356
  // set upper right corner values
1357
  urc[0] = bbox.max.x;
45✔
1358
  urc[1] = bbox.max.y;
45✔
1359
  urc[2] = bbox.max.z;
45✔
1360

1361
  return 0;
45✔
1362
}
1363

1364
//! Get the name of a cell
1365
extern "C" int openmc_cell_get_name(int32_t index, const char** name)
18✔
1366
{
1367
  if (index < 0 || index >= model::cells.size()) {
18!
1368
    set_errmsg("Index in cells array is out of bounds.");
×
1369
    return OPENMC_E_OUT_OF_BOUNDS;
×
1370
  }
1371

1372
  *name = model::cells[index]->name().data();
18✔
1373

1374
  return 0;
18✔
1375
}
1376

1377
//! Set the name of a cell
1378
extern "C" int openmc_cell_set_name(int32_t index, const char* name)
9✔
1379
{
1380
  if (index < 0 || index >= model::cells.size()) {
9!
1381
    set_errmsg("Index in cells array is out of bounds.");
×
1382
    return OPENMC_E_OUT_OF_BOUNDS;
×
1383
  }
1384

1385
  model::cells[index]->set_name(name);
9✔
1386

1387
  return 0;
9✔
1388
}
1389

1390
//==============================================================================
1391
//! Define a containing (parent) cell
1392
//==============================================================================
1393

1394
//! Used to locate a universe fill in the geometry
1395
struct ParentCell {
1396
  bool operator==(const ParentCell& other) const
108✔
1397
  {
1398
    return cell_index == other.cell_index &&
216!
1399
           lattice_index == other.lattice_index;
216!
1400
  }
1401

1402
  bool operator<(const ParentCell& other) const
1403
  {
1404
    return cell_index < other.cell_index ||
1405
           (cell_index == other.cell_index &&
1406
             lattice_index < other.lattice_index);
1407
  }
1408

1409
  int64_t cell_index;
1410
  int64_t lattice_index;
1411
};
1412

1413
//! Structure used to insert ParentCell into hashed STL data structures
1414
struct ParentCellHash {
1415
  std::size_t operator()(const ParentCell& p) const
504✔
1416
  {
1417
    return 4096 * p.cell_index + p.lattice_index;
504✔
1418
  }
1419
};
1420

1421
//! Used to manage a traversal stack when locating parent cells of a cell
1422
//! instance in the model
1423
struct ParentCellStack {
1424

1425
  //! push method that adds to the parent_cells visited cells for this search
1426
  //! universe
1427
  void push(int32_t search_universe, const ParentCell& pc)
84✔
1428
  {
1429
    parent_cells_.push_back(pc);
84✔
1430
    // add parent cell to the set of cells we've visited for this search
1431
    // universe
1432
    visited_cells_[search_universe].insert(pc);
84✔
1433
  }
84✔
1434

1435
  //! removes the last parent_cell and clears the visited cells for the popped
1436
  //! cell's universe
1437
  void pop()
60✔
1438
  {
1439
    visited_cells_[this->current_univ()].clear();
60✔
1440
    parent_cells_.pop_back();
60✔
1441
  }
60✔
1442

1443
  //! checks whether or not the parent cell has been visited already for this
1444
  //! search universe
1445
  bool visited(int32_t search_universe, const ParentCell& parent_cell)
420✔
1446
  {
1447
    return visited_cells_[search_universe].count(parent_cell) != 0;
420✔
1448
  }
1449

1450
  //! return the next universe to search for a parent cell
1451
  int32_t current_univ() const
60✔
1452
  {
1453
    return model::cells[parent_cells_.back().cell_index]->universe_;
60✔
1454
  }
1455

1456
  //! indicates whether nor not parent cells are present on the stack
1457
  bool empty() const { return parent_cells_.empty(); }
60✔
1458

1459
  //! compute an instance for the provided distribcell index
1460
  int32_t compute_instance(int32_t distribcell_index) const
144✔
1461
  {
1462
    if (distribcell_index == C_NONE)
144✔
1463
      return 0;
48✔
1464

1465
    int32_t instance = 0;
96✔
1466
    for (const auto& parent_cell : this->parent_cells_) {
180✔
1467
      auto& cell = model::cells[parent_cell.cell_index];
84✔
1468
      if (cell->type_ == Fill::UNIVERSE) {
84!
1469
        instance += cell->offset_[distribcell_index];
×
1470
      } else if (cell->type_ == Fill::LATTICE) {
84!
1471
        auto& lattice = model::lattices[cell->fill_];
84✔
1472
        instance +=
84✔
1473
          lattice->offset(distribcell_index, parent_cell.lattice_index);
84✔
1474
      }
1475
    }
1476
    return instance;
96✔
1477
  }
1478

1479
  // Accessors
1480
  vector<ParentCell>& parent_cells() { return parent_cells_; }
252✔
1481
  const vector<ParentCell>& parent_cells() const { return parent_cells_; }
1482

1483
  // Data Members
1484
  vector<ParentCell> parent_cells_;
1485
  std::unordered_map<int32_t, std::unordered_set<ParentCell, ParentCellHash>>
1486
    visited_cells_;
1487
};
1488

1489
vector<ParentCell> Cell::find_parent_cells(
×
1490
  int32_t instance, const Position& r) const
1491
{
1492

1493
  // create a temporary particle
1494
  GeometryState dummy_particle {};
×
1495
  dummy_particle.r() = r;
×
1496
  dummy_particle.u() = {0., 0., 1.};
×
1497

1498
  return find_parent_cells(instance, dummy_particle);
×
1499
}
×
1500

1501
vector<ParentCell> Cell::find_parent_cells(
×
1502
  int32_t instance, GeometryState& p) const
1503
{
1504
  // look up the particle's location
1505
  exhaustive_find_cell(p);
×
1506
  const auto& coords = p.coord();
×
1507

1508
  // build a parent cell stack from the particle coordinates
1509
  ParentCellStack stack;
×
1510
  bool cell_found = false;
×
1511
  for (auto it = coords.begin(); it != coords.end(); it++) {
×
1512
    const auto& coord = *it;
×
1513
    const auto& cell = model::cells[coord.cell()];
×
1514
    // if the cell at this level matches the current cell, stop adding to the
1515
    // stack
1516
    if (coord.cell() == model::cell_map[this->id_]) {
×
1517
      cell_found = true;
×
1518
      break;
×
1519
    }
1520

1521
    // if filled with a lattice, get the lattice index from the next
1522
    // level in the coordinates to push to the stack
1523
    int lattice_idx = C_NONE;
×
1524
    if (cell->type_ == Fill::LATTICE) {
×
1525
      const auto& next_coord = *(it + 1);
×
1526
      lattice_idx = model::lattices[next_coord.lattice()]->get_flat_index(
×
1527
        next_coord.lattice_index());
1528
    }
1529
    stack.push(coord.universe(), {coord.cell(), lattice_idx});
×
1530
  }
1531

1532
  // if this loop finished because the cell was found and
1533
  // the instance matches the one requested in the call
1534
  // we have the correct path and can return the stack
1535
  if (cell_found &&
×
1536
      stack.compute_instance(this->distribcell_index_) == instance) {
×
1537
    return stack.parent_cells();
×
1538
  }
1539

1540
  // fall back on an exhaustive search for the cell's parents
1541
  return exhaustive_find_parent_cells(instance);
×
1542
}
×
1543

1544
vector<ParentCell> Cell::exhaustive_find_parent_cells(int32_t instance) const
84✔
1545
{
1546
  ParentCellStack stack;
84✔
1547
  // start with this cell's universe
1548
  int32_t prev_univ_idx;
1549
  int32_t univ_idx = this->universe_;
84✔
1550

1551
  while (true) {
1552
    const auto& univ = model::universes[univ_idx];
144✔
1553
    prev_univ_idx = univ_idx;
144✔
1554

1555
    // search for a cell that is filled w/ this universe
1556
    for (const auto& cell : model::cells) {
924✔
1557
      // if this is a material-filled cell, move on
1558
      if (cell->type_ == Fill::MATERIAL)
864✔
1559
        continue;
480✔
1560

1561
      if (cell->type_ == Fill::UNIVERSE) {
384✔
1562
        // if this is in the set of cells previously visited for this universe,
1563
        // move on
1564
        if (stack.visited(univ_idx, {model::cell_map[cell->id_], C_NONE}))
228!
1565
          continue;
×
1566

1567
        // if this cell contains the universe we're searching for, add it to the
1568
        // stack
1569
        if (cell->fill_ == univ_idx) {
228!
1570
          stack.push(univ_idx, {model::cell_map[cell->id_], C_NONE});
×
1571
          univ_idx = cell->universe_;
×
1572
        }
1573
      } else if (cell->type_ == Fill::LATTICE) {
156!
1574
        // retrieve the lattice and lattice universes
1575
        const auto& lattice = model::lattices[cell->fill_];
156✔
1576
        const auto& lattice_univs = lattice->universes_;
156✔
1577

1578
        // start search for universe
1579
        auto lat_it = lattice_univs.begin();
156✔
1580
        while (true) {
1581
          // find the next lattice cell with this universe
1582
          lat_it = std::find(lat_it, lattice_univs.end(), univ_idx);
264✔
1583
          if (lat_it == lattice_univs.end())
264✔
1584
            break;
72✔
1585

1586
          int lattice_idx = lat_it - lattice_univs.begin();
192✔
1587

1588
          // move iterator forward one to avoid finding the same entry
1589
          lat_it++;
192✔
1590
          if (stack.visited(
192✔
1591
                univ_idx, {model::cell_map[cell->id_], lattice_idx}))
192✔
1592
            continue;
108✔
1593

1594
          // add this cell and lattice index to the stack and exit loop
1595
          stack.push(univ_idx, {model::cell_map[cell->id_], lattice_idx});
84✔
1596
          univ_idx = cell->universe_;
84✔
1597
          break;
84✔
1598
        }
108✔
1599
      }
1600
      // if we've updated the universe, break
1601
      if (prev_univ_idx != univ_idx)
384✔
1602
        break;
84✔
1603
    } // end cell loop search for universe
1604

1605
    // if we're at the top of the geometry and the instance matches, we're done
1606
    if (univ_idx == model::root_universe &&
288!
1607
        stack.compute_instance(this->distribcell_index_) == instance)
144✔
1608
      break;
84✔
1609

1610
    // if there is no match on the original cell's universe, report an error
1611
    if (univ_idx == this->universe_) {
60!
1612
      fatal_error(
×
1613
        fmt::format("Could not find the parent cells for cell {}, instance {}.",
×
1614
          this->id_, instance));
×
1615
    }
1616

1617
    // if we don't find a suitable update, adjust the stack and continue
1618
    if (univ_idx == model::root_universe || univ_idx == prev_univ_idx) {
60!
1619
      stack.pop();
60✔
1620
      univ_idx = stack.empty() ? this->universe_ : stack.current_univ();
60!
1621
    }
1622

1623
  } // end while
60✔
1624

1625
  // reverse the stack so the highest cell comes first
1626
  std::reverse(stack.parent_cells().begin(), stack.parent_cells().end());
84✔
1627
  return stack.parent_cells();
168✔
1628
}
84✔
1629

1630
std::unordered_map<int32_t, vector<int32_t>> Cell::get_contained_cells(
120✔
1631
  int32_t instance, Position* hint) const
1632
{
1633
  std::unordered_map<int32_t, vector<int32_t>> contained_cells;
120✔
1634

1635
  // if this is a material-filled cell it has no contained cells
1636
  if (this->type_ == Fill::MATERIAL)
120✔
1637
    return contained_cells;
36✔
1638

1639
  // find the pathway through the geometry to this cell
1640
  vector<ParentCell> parent_cells;
84✔
1641

1642
  // if a positional hint is provided, attempt to do a fast lookup
1643
  // of the parent cells
1644
  parent_cells = hint ? find_parent_cells(instance, *hint)
168!
1645
                      : exhaustive_find_parent_cells(instance);
84✔
1646

1647
  // if this cell is filled w/ a material, it contains no other cells
1648
  if (type_ != Fill::MATERIAL) {
84!
1649
    this->get_contained_cells_inner(contained_cells, parent_cells);
84✔
1650
  }
1651

1652
  return contained_cells;
84✔
1653
}
84✔
1654

1655
//! Get all cells within this cell
1656
void Cell::get_contained_cells_inner(
65,820✔
1657
  std::unordered_map<int32_t, vector<int32_t>>& contained_cells,
1658
  vector<ParentCell>& parent_cells) const
1659
{
1660

1661
  // filled by material, determine instance based on parent cells
1662
  if (type_ == Fill::MATERIAL) {
65,820✔
1663
    int instance = 0;
65,352✔
1664
    if (this->distribcell_index_ >= 0) {
65,352!
1665
      for (auto& parent_cell : parent_cells) {
196,056✔
1666
        auto& cell = model::cells[parent_cell.cell_index];
130,704✔
1667
        if (cell->type_ == Fill::UNIVERSE) {
130,704✔
1668
          instance += cell->offset_[distribcell_index_];
64,152✔
1669
        } else if (cell->type_ == Fill::LATTICE) {
66,552!
1670
          auto& lattice = model::lattices[cell->fill_];
66,552✔
1671
          instance += lattice->offset(
133,104✔
1672
            this->distribcell_index_, parent_cell.lattice_index);
66,552✔
1673
        }
1674
      }
1675
    }
1676
    // add entry to contained cells
1677
    contained_cells[model::cell_map[id_]].push_back(instance);
65,352✔
1678
    // filled with universe, add the containing cell to the parent cells
1679
    // and recurse
1680
  } else if (type_ == Fill::UNIVERSE) {
468✔
1681
    parent_cells.push_back({model::cell_map[id_], -1});
396✔
1682
    auto& univ = model::universes[fill_];
396✔
1683
    for (auto cell_index : univ->cells_) {
2,376✔
1684
      auto& cell = model::cells[cell_index];
1,980✔
1685
      cell->get_contained_cells_inner(contained_cells, parent_cells);
1,980✔
1686
    }
1687
    parent_cells.pop_back();
396✔
1688
    // filled with a lattice, visit each universe in the lattice
1689
    // with a recursive call to collect the cell instances
1690
  } else if (type_ == Fill::LATTICE) {
72!
1691
    auto& lattice = model::lattices[fill_];
72✔
1692
    for (auto i = lattice->begin(); i != lattice->end(); ++i) {
63,576✔
1693
      auto& univ = model::universes[*i];
63,504✔
1694
      parent_cells.push_back({model::cell_map[id_], i.indx_});
63,504✔
1695
      for (auto cell_index : univ->cells_) {
127,260✔
1696
        auto& cell = model::cells[cell_index];
63,756✔
1697
        cell->get_contained_cells_inner(contained_cells, parent_cells);
63,756✔
1698
      }
1699
      parent_cells.pop_back();
63,504✔
1700
    }
1701
  }
1702
}
65,820✔
1703

1704
//! Return the index in the cells array of a cell with a given ID
1705
extern "C" int openmc_get_cell_index(int32_t id, int32_t* index)
360✔
1706
{
1707
  auto it = model::cell_map.find(id);
360✔
1708
  if (it != model::cell_map.end()) {
360✔
1709
    *index = it->second;
351✔
1710
    return 0;
351✔
1711
  } else {
1712
    set_errmsg("No cell exists with ID=" + std::to_string(id) + ".");
9✔
1713
    return OPENMC_E_INVALID_ID;
9✔
1714
  }
1715
}
1716

1717
//! Return the ID of a cell
1718
extern "C" int openmc_cell_get_id(int32_t index, int32_t* id)
400,756✔
1719
{
1720
  if (index >= 0 && index < model::cells.size()) {
400,756!
1721
    *id = model::cells[index]->id_;
400,756✔
1722
    return 0;
400,756✔
1723
  } else {
1724
    set_errmsg("Index in cells array is out of bounds.");
×
1725
    return OPENMC_E_OUT_OF_BOUNDS;
×
1726
  }
1727
}
1728

1729
//! Set the ID of a cell
1730
extern "C" int openmc_cell_set_id(int32_t index, int32_t id)
18✔
1731
{
1732
  if (index >= 0 && index < model::cells.size()) {
18!
1733
    model::cells[index]->id_ = id;
18✔
1734
    model::cell_map[id] = index;
18✔
1735
    return 0;
18✔
1736
  } else {
1737
    set_errmsg("Index in cells array is out of bounds.");
×
1738
    return OPENMC_E_OUT_OF_BOUNDS;
×
1739
  }
1740
}
1741

1742
//! Return the translation vector of a cell
1743
extern "C" int openmc_cell_get_translation(int32_t index, double xyz[])
45✔
1744
{
1745
  if (index >= 0 && index < model::cells.size()) {
45!
1746
    auto& cell = model::cells[index];
45✔
1747
    xyz[0] = cell->translation_.x;
45✔
1748
    xyz[1] = cell->translation_.y;
45✔
1749
    xyz[2] = cell->translation_.z;
45✔
1750
    return 0;
45✔
1751
  } else {
1752
    set_errmsg("Index in cells array is out of bounds.");
×
1753
    return OPENMC_E_OUT_OF_BOUNDS;
×
1754
  }
1755
}
1756

1757
//! Set the translation vector of a cell
1758
extern "C" int openmc_cell_set_translation(int32_t index, const double xyz[])
27✔
1759
{
1760
  if (index >= 0 && index < model::cells.size()) {
27!
1761
    if (model::cells[index]->fill_ == C_NONE) {
27✔
1762
      set_errmsg(fmt::format("Cannot apply a translation to cell {}"
9✔
1763
                             " because it is not filled with another universe",
1764
        index));
1765
      return OPENMC_E_GEOMETRY;
9✔
1766
    }
1767
    model::cells[index]->translation_ = Position(xyz);
18✔
1768
    return 0;
18✔
1769
  } else {
1770
    set_errmsg("Index in cells array is out of bounds.");
×
1771
    return OPENMC_E_OUT_OF_BOUNDS;
×
1772
  }
1773
}
1774

1775
//! Return the rotation matrix of a cell
1776
extern "C" int openmc_cell_get_rotation(int32_t index, double rot[], size_t* n)
45✔
1777
{
1778
  if (index >= 0 && index < model::cells.size()) {
45!
1779
    auto& cell = model::cells[index];
45✔
1780
    *n = cell->rotation_.size();
45✔
1781
    std::memcpy(rot, cell->rotation_.data(), *n * sizeof(cell->rotation_[0]));
45✔
1782
    return 0;
45✔
1783
  } else {
1784
    set_errmsg("Index in cells array is out of bounds.");
×
1785
    return OPENMC_E_OUT_OF_BOUNDS;
×
1786
  }
1787
}
1788

1789
//! Set the flattened rotation matrix of a cell
1790
extern "C" int openmc_cell_set_rotation(
36✔
1791
  int32_t index, const double rot[], size_t rot_len)
1792
{
1793
  if (index >= 0 && index < model::cells.size()) {
36!
1794
    if (model::cells[index]->fill_ == C_NONE) {
36✔
1795
      set_errmsg(fmt::format("Cannot apply a rotation to cell {}"
9✔
1796
                             " because it is not filled with another universe",
1797
        index));
1798
      return OPENMC_E_GEOMETRY;
9✔
1799
    }
1800
    std::vector<double> vec_rot(rot, rot + rot_len);
27✔
1801
    model::cells[index]->set_rotation(vec_rot);
27✔
1802
    return 0;
27✔
1803
  } else {
27✔
1804
    set_errmsg("Index in cells array is out of bounds.");
×
1805
    return OPENMC_E_OUT_OF_BOUNDS;
×
1806
  }
1807
}
1808

1809
//! Get the number of instances of the requested cell
1810
extern "C" int openmc_cell_get_num_instances(
9✔
1811
  int32_t index, int32_t* num_instances)
1812
{
1813
  if (index < 0 || index >= model::cells.size()) {
9!
1814
    set_errmsg("Index in cells array is out of bounds.");
×
1815
    return OPENMC_E_OUT_OF_BOUNDS;
×
1816
  }
1817
  *num_instances = model::cells[index]->n_instances();
9✔
1818
  return 0;
9✔
1819
}
1820

1821
//! Extend the cells array by n elements
1822
extern "C" int openmc_extend_cells(
18✔
1823
  int32_t n, int32_t* index_start, int32_t* index_end)
1824
{
1825
  if (index_start)
18!
1826
    *index_start = model::cells.size();
18✔
1827
  if (index_end)
18!
1828
    *index_end = model::cells.size() + n - 1;
×
1829
  for (int32_t i = 0; i < n; i++) {
36✔
1830
    model::cells.push_back(make_unique<CSGCell>());
18✔
1831
  }
1832
  return 0;
18✔
1833
}
1834

1835
extern "C" int cells_size()
36✔
1836
{
1837
  return model::cells.size();
36✔
1838
}
1839

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