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

openmc-dev / openmc / 22670832759

04 Mar 2026 01:11PM UTC coverage: 81.478% (-0.08%) from 81.556%
22670832759

Pull #3847

github

web-flow
Merge 9853f1f4b into 0ab46dfa3
Pull Request #3847: Implementation of forced collision variance reduction scheme

17565 of 25304 branches covered (69.42%)

Branch coverage included in aggregate %.

78 of 87 new or added lines in 5 files covered. (89.66%)

66 existing lines in 10 files now uncovered.

57934 of 67358 relevant lines covered (86.01%)

61109854.28 hits per line

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

73.03
/src/cell.cpp
1

2
#include "openmc/cell.h"
3

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

13
#include <fmt/core.h>
14

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

28
namespace openmc {
29

30
//==============================================================================
31
// Global variables
32
//==============================================================================
33

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

38
} // namespace model
39

40
//==============================================================================
41
// Cell implementation
42
//==============================================================================
43

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

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

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

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

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

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

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

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

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

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

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

140
  if (type_ == Fill::MATERIAL) {
475✔
141
    if (instance >= 0) {
445✔
142
      // If temperature vector is not big enough, resize it first
143
      if (sqrtkT_.size() != n_instances())
368✔
144
        sqrtkT_.resize(n_instances(), sqrtkT_[0]);
45✔
145

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

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

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

183
  if (type_ == Fill::MATERIAL) {
280✔
184
    const int32_t mat_index = material(instance);
265!
185
    if (mat_index == MATERIAL_VOID)
265!
186
      return;
187

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

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

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

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

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

232
    write_dataset(cell_group, "density", density);
132✔
233
  }
132✔
234

235
  close_group(cell_group);
154✔
236
}
154✔
237

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

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

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

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

261
  // Read densities
262
  if (object_exists(cell_group, "density")) {
176✔
263
    vector<double> density;
132✔
264
    read_dataset(cell_group, "density", density);
132✔
265

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

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

280
  close_group(cell_group);
176✔
281
}
176✔
282

283
void Cell::to_hdf5(hid_t cell_group) const
28,533✔
284
{
285

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

289
  if (!name_.empty()) {
28,533✔
290
    write_string(group, "name", name_, false);
6,122✔
291
  }
292

293
  write_dataset(group, "universe", model::universes[universe_]->id_);
28,533✔
294

295
  to_hdf5_inner(group);
28,533✔
296

297
  // Write fill information.
298
  if (type_ == Fill::MATERIAL) {
28,533✔
299
    write_dataset(group, "fill_type", "material");
23,129✔
300
    std::vector<int32_t> mat_ids;
23,129✔
301
    for (auto i_mat : material_) {
47,551✔
302
      if (i_mat != MATERIAL_VOID) {
24,422✔
303
        mat_ids.push_back(model::materials[i_mat]->id_);
16,019✔
304
      } else {
305
        mat_ids.push_back(MATERIAL_VOID);
8,403✔
306
      }
307
    }
308
    if (mat_ids.size() == 1) {
23,129✔
309
      write_dataset(group, "material", mat_ids[0]);
22,938✔
310
    } else {
311
      write_dataset(group, "material", mat_ids);
191✔
312
    }
313

314
    std::vector<double> temps;
23,129✔
315
    for (auto sqrtkT_val : sqrtkT_)
48,433✔
316
      temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
25,304✔
317
    write_dataset(group, "temperature", temps);
23,129✔
318

319
    write_dataset(group, "density_mult", density_mult_);
23,129✔
320

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

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

341
  close_group(group);
28,533✔
342
}
28,533✔
343

344
//==============================================================================
345
// CSGCell implementation
346
//==============================================================================
347

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

356
  if (check_for_node(cell_node, "name")) {
34,062✔
357
    name_ = get_node_value(cell_node, "name");
8,009✔
358
  }
359

360
  if (check_for_node(cell_node, "universe")) {
34,062✔
361
    universe_ = std::stoi(get_node_value(cell_node, "universe"));
65,854✔
362
  } else {
363
    universe_ = 0;
1,135✔
364
  }
365

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

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

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

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

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

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

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

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

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

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

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

476
  if (check_for_node(cell_node, "forced_collision")) {
34,062✔
477
    forced_collision_ = get_node_array<bool>(cell_node, "forced_collision");
11✔
478
    forced_collision_.shrink_to_fit();
11✔
479
    for (auto flag : forced_collision_) {
44!
480
      if (flag)
11!
481
        simulation::forced_collision = true;
11✔
482
    }
483
  }
484

485
  // Read the region specification.
486
  std::string region_spec;
34,062✔
487
  if (check_for_node(cell_node, "region")) {
34,062✔
488
    region_spec = get_node_value(cell_node, "region");
25,083✔
489
  }
490

491
  // Get a tokenized representation of the region specification and apply De
492
  // Morgans law
493
  Region region(region_spec, id_);
34,062✔
494
  region_ = region;
34,062✔
495

496
  // Read the translation vector.
497
  if (check_for_node(cell_node, "translation")) {
34,062✔
498
    if (fill_ == C_NONE) {
2,557!
499
      fatal_error(fmt::format("Cannot apply a translation to cell {}"
×
500
                              " because it is not filled with another universe",
501
        id_));
×
502
    }
503

504
    auto xyz {get_node_array<double>(cell_node, "translation")};
2,557✔
505
    if (xyz.size() != 3) {
2,557!
506
      fatal_error(
×
507
        fmt::format("Non-3D translation vector applied to cell {}", id_));
×
508
    }
509
    translation_ = xyz;
2,557✔
510
  }
2,557✔
511

512
  // Read the rotation transform.
513
  if (check_for_node(cell_node, "rotation")) {
34,062✔
514
    auto rot {get_node_array<double>(cell_node, "rotation")};
390✔
515
    set_rotation(rot);
390✔
516
  }
390✔
517
}
34,062✔
518

519
//==============================================================================
520

521
void CSGCell::to_hdf5_inner(hid_t group_id) const
28,369✔
522
{
523
  write_string(group_id, "geom_type", "csg", false);
28,369✔
524
  write_string(group_id, "region", region_.str(), false);
28,369✔
525
}
28,369✔
526

527
//==============================================================================
528

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

540
    // decrement parenthesis level if there are two adjacent surfaces
541
    if (one < OP_UNION && two < OP_UNION) {
×
542
      parenthesis_level--;
×
543
      // increment if there are two adjacent operators
544
    } else if (one >= OP_UNION && two >= OP_UNION) {
×
545
      parenthesis_level++;
×
546
    }
547

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

556
    // continue loop, one token at a time
557
    it--;
558
  }
559
  return it;
×
560
}
561

562
//==============================================================================
563
// Region implementation
564
//==============================================================================
565

566
Region::Region(std::string region_spec, int32_t cell_id)
34,161✔
567
{
568
  // Check if region_spec is not empty.
569
  if (!region_spec.empty()) {
34,161✔
570
    // Parse all halfspaces and operators except for intersection (whitespace).
571
    for (int i = 0; i < region_spec.size();) {
147,874✔
572
      if (region_spec[i] == '(') {
122,692✔
573
        expression_.push_back(OP_LEFT_PAREN);
1,290✔
574
        i++;
1,290✔
575

576
      } else if (region_spec[i] == ')') {
121,402✔
577
        expression_.push_back(OP_RIGHT_PAREN);
1,290✔
578
        i++;
1,290✔
579

580
      } else if (region_spec[i] == '|') {
120,112✔
581
        expression_.push_back(OP_UNION);
3,823✔
582
        i++;
3,823✔
583

584
      } else if (region_spec[i] == '~') {
116,289✔
585
        expression_.push_back(OP_COMPLEMENT);
30✔
586
        i++;
30✔
587

588
      } else if (region_spec[i] == '-' || region_spec[i] == '+' ||
196,210!
589
                 std::isdigit(region_spec[i])) {
79,951✔
590
        // This is the start of a halfspace specification.  Iterate j until we
591
        // find the end, then push-back everything between i and j.
592
        int j = i + 1;
68,063✔
593
        while (j < region_spec.size() && std::isdigit(region_spec[j])) {
137,631✔
594
          j++;
69,568✔
595
        }
596
        expression_.push_back(std::stoi(region_spec.substr(i, j - i)));
136,126✔
597
        i = j;
68,063✔
598

599
      } else if (std::isspace(region_spec[i])) {
48,196!
600
        i++;
48,196✔
601

602
      } else {
603
        auto err_msg =
×
604
          fmt::format("Region specification contains invalid character, \"{}\"",
605
            region_spec[i]);
×
606
        fatal_error(err_msg);
×
607
      }
×
608
    }
609

610
    // Add in intersection operators where a missing operator is needed.
611
    int i = 0;
612
    while (i < expression_.size() - 1) {
113,554✔
613
      bool left_compat {
88,372✔
614
        (expression_[i] < OP_UNION) || (expression_[i] == OP_RIGHT_PAREN)};
88,372✔
615
      bool right_compat {(expression_[i + 1] < OP_UNION) ||
88,372✔
616
                         (expression_[i + 1] == OP_LEFT_PAREN) ||
88,372✔
617
                         (expression_[i + 1] == OP_COMPLEMENT)};
5,173✔
618
      if (left_compat && right_compat) {
88,372✔
619
        expression_.insert(expression_.begin() + i + 1, OP_INTERSECTION);
39,058✔
620
      }
621
      i++;
622
    }
623

624
    // Remove complement operators using DeMorgan's laws
625
    auto it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
25,182✔
626
    while (it != expression_.end()) {
25,212✔
627
      // Erase complement
628
      expression_.erase(it);
30✔
629

630
      // Define stop given left parenthesis or not
631
      auto stop = it;
30✔
632
      if (*it == OP_LEFT_PAREN) {
30!
633
        int depth = 1;
634
        do {
240✔
635
          stop++;
240✔
636
          if (*stop > OP_COMPLEMENT) {
240✔
637
            if (*stop == OP_RIGHT_PAREN) {
30!
638
              depth--;
30✔
639
            } else {
640
              depth++;
×
641
            }
642
          }
643
        } while (depth > 0);
240✔
644
        it++;
30✔
645
      }
646

647
      // apply DeMorgan's law to any surfaces/operators between these
648
      // positions in the RPN
649
      apply_demorgan(it, stop);
30✔
650
      // update iterator position
651
      it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
30✔
652
    }
653

654
    // Convert user IDs to surface indices.
655
    for (auto& r : expression_) {
138,706✔
656
      if (r < OP_UNION) {
113,524✔
657
        const auto& it {model::surface_map.find(abs(r))};
68,063!
658
        if (it == model::surface_map.end()) {
68,063!
659
          throw std::runtime_error {
×
660
            "Invalid surface ID " + std::to_string(abs(r)) +
×
661
            " specified in region for cell " + std::to_string(cell_id) + "."};
×
662
        }
663
        r = (r > 0) ? it->second + 1 : -(it->second + 1);
68,063✔
664
      }
665
    }
666

667
    // Check if this is a simple cell.
668
    simple_ = true;
25,182✔
669
    for (int32_t token : expression_) {
125,676✔
670
      if (token == OP_UNION) {
101,682✔
671
        simple_ = false;
1,188✔
672
        // Ensure intersections have precedence over unions
673
        enforce_precedence();
1,188✔
674
        break;
675
      }
676
    }
677

678
    // If this cell is simple, remove all the superfluous operator tokens.
679
    if (simple_) {
25,182✔
680
      for (auto it = expression_.begin(); it != expression_.end(); it++) {
118,130✔
681
        if (*it == OP_INTERSECTION || *it > OP_COMPLEMENT) {
94,136!
682
          expression_.erase(it);
35,071✔
683
          it--;
94,136✔
684
        }
685
      }
686
    }
687
    expression_.shrink_to_fit();
25,182✔
688

689
  } else {
690
    simple_ = true;
8,979✔
691
  }
692
}
34,161✔
693

694
//==============================================================================
695

696
void Region::apply_demorgan(
30✔
697
  vector<int32_t>::iterator start, vector<int32_t>::iterator stop)
698
{
699
  do {
210✔
700
    if (*start < OP_UNION) {
210✔
701
      *start *= -1;
120✔
702
    } else if (*start == OP_UNION) {
90!
703
      *start = OP_INTERSECTION;
×
704
    } else if (*start == OP_INTERSECTION) {
90!
705
      *start = OP_UNION;
90✔
706
    }
707
    start++;
210✔
708
  } while (start < stop);
210✔
709
}
30✔
710

711
//==============================================================================
712
//! Add precedence for infix regions so intersections have higher
713
//! precedence than unions using parentheses.
714
//==============================================================================
715

716
void Region::add_parentheses(int64_t start)
96✔
717
{
718
  int32_t start_token = expression_[start];
96!
719
  // Add left parenthesis and set new position to be after parenthesis
720
  if (start_token == OP_UNION) {
96!
721
    start += 2;
×
722
  }
723
  expression_.insert(expression_.begin() + start - 1, OP_LEFT_PAREN);
96✔
724

725
  // Add right parenthesis
726
  // While the start iterator is within the bounds of infix
727
  while (start + 1 < expression_.size()) {
430✔
728
    start++;
408✔
729

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

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

776
void Region::enforce_precedence()
1,188✔
777
{
778
  // Stack tracking the operator type at each depth (0 = no operator seen yet)
779
  vector<int32_t> op_stack = {0};
1,188✔
780

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

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

787
    if (token == OP_LEFT_PAREN) {
20,405✔
788
      // Entering a new parenthesis level - push new tracking state
789
      op_stack.push_back(0);
1,486✔
790
      pos_stack.push_back(0);
1,486✔
791
      continue;
1,486✔
792
    } else if (token == OP_RIGHT_PAREN) {
18,919✔
793
      // Exiting a parenthesis level - pop tracking state (keep at least one)
794
      if (op_stack.size() > 1) {
1,445!
795
        op_stack.pop_back();
1,445✔
796
        pos_stack.pop_back();
1,445✔
797
      }
798
      continue;
1,445✔
799
    }
800

801
    if (token == OP_UNION || token == OP_INTERSECTION) {
17,474✔
802
      if (op_stack.back() == 0) {
8,143✔
803
        // First operator at this depth - record it and its position
804
        op_stack.back() = token;
2,718✔
805
        pos_stack.back() = i;
2,718✔
806
      } else if (token != op_stack.back()) {
5,425✔
807
        // Encountered a different operator at the same depth - need to add
808
        // parentheses to enforce precedence. Intersection has higher
809
        // precedence, so we parenthesize the intersection terms.
810
        if (op_stack.back() == OP_INTERSECTION) {
96✔
811
          add_parentheses(pos_stack.back());
48✔
812
        } else {
813
          add_parentheses(i);
48✔
814
        }
815

816
        // Restart the scan since we modified the expression
817
        i = -1; // Will be incremented to 0 by the for loop
96✔
818
        op_stack = {0};
96✔
819
        pos_stack = {0};
96✔
820
      }
821
    }
822
  }
823
}
1,188✔
824

825
//==============================================================================
826
//! Convert infix region specification to Reverse Polish Notation (RPN)
827
//!
828
//! This function uses the shunting-yard algorithm.
829
//==============================================================================
830

831
vector<int32_t> Region::generate_postfix(int32_t cell_id) const
44✔
832
{
833
  vector<int32_t> rpn;
44✔
834
  vector<int32_t> stack;
44✔
835

836
  for (int32_t token : expression_) {
990✔
837
    if (token < OP_UNION) {
946✔
838
      // If token is not an operator, add it to output
839
      rpn.push_back(token);
396✔
840
    } else if (token < OP_RIGHT_PAREN) {
550✔
841
      // Regular operators union, intersection, complement
842
      while (stack.size() > 0) {
561✔
843
        int32_t op = stack.back();
462✔
844

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

860
      stack.push_back(token);
352✔
861

862
    } else if (token == OP_LEFT_PAREN) {
198✔
863
      // If the token is a left parenthesis, push it onto the stack
864
      stack.push_back(token);
99✔
865

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

881
      // Pop the left parenthesis.
882
      stack.pop_back();
946✔
883
    }
884
  }
885

886
  while (stack.size() > 0) {
44✔
887
    int32_t op = stack.back();
44!
888

889
    // If the operator is a parenthesis it is mismatched.
890
    if (op >= OP_RIGHT_PAREN) {
44!
891
      fatal_error(fmt::format(
×
892
        "Mismatched parentheses in region specification for cell {}", cell_id));
893
    }
894

895
    rpn.push_back(stack.back());
44✔
896
    stack.pop_back();
88✔
897
  }
898

899
  return rpn;
44✔
900
}
44✔
901

902
//==============================================================================
903

904
std::string Region::str() const
28,468✔
905
{
906
  std::stringstream region_spec {};
28,468✔
907
  if (!expression_.empty()) {
28,468✔
908
    for (int32_t token : expression_) {
84,644✔
909
      if (token == OP_LEFT_PAREN) {
64,472✔
910
        region_spec << " (";
1,218✔
911
      } else if (token == OP_RIGHT_PAREN) {
63,254✔
912
        region_spec << " )";
1,218✔
913
      } else if (token == OP_COMPLEMENT) {
62,036!
914
        region_spec << " ~";
×
915
      } else if (token == OP_INTERSECTION) {
62,036✔
916
      } else if (token == OP_UNION) {
58,695✔
917
        region_spec << " |";
3,417✔
918
      } else {
919
        // Note the off-by-one indexing
920
        auto surf_id = model::surfaces[abs(token) - 1]->id_;
55,278✔
921
        region_spec << " " << ((token > 0) ? surf_id : -surf_id);
55,278✔
922
      }
923
    }
924
  }
925
  return region_spec.str();
56,936✔
926
}
28,468✔
927

928
//==============================================================================
929

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

UNCOV
936
  for (int32_t token : expression_) {
×
937
    // Ignore this token if it corresponds to an operator rather than a region.
UNCOV
938
    if (token >= OP_UNION)
×
939
      continue;
174,174,736✔
940

941
    // Calculate the distance to this surface.
942
    // Note the off-by-one indexing
UNCOV
943
    bool coincident {std::abs(token) == std::abs(on_surface)};
×
UNCOV
944
    double d {model::surfaces[abs(token) - 1]->distance(r, u, coincident)};
×
945

946
    // Check if this distance is the new minimum.
UNCOV
947
    if (d < min_dist) {
×
UNCOV
948
      if (min_dist - d >= FP_PRECISION * min_dist) {
×
UNCOV
949
        min_dist = d;
×
UNCOV
950
        i_surf = -token;
×
951
      }
952
    }
953
  }
954

955
  return {min_dist, i_surf};
2,147,483,647✔
956
}
957

958
//==============================================================================
959

960
bool Region::contains(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
961
{
UNCOV
962
  if (simple_) {
×
UNCOV
963
    return contains_simple(r, u, on_surface);
×
964
  } else {
965
    return contains_complex(r, u, on_surface);
7,416,259✔
966
  }
967
}
968

969
//==============================================================================
970

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

992
//==============================================================================
993

994
bool Region::contains_complex(Position r, Direction u, int32_t on_surface) const
7,416,259✔
995
{
996
  bool in_cell = true;
7,416,259✔
997
  int total_depth = 0;
7,416,259✔
998

999
  // For each token
1000
  for (auto it = expression_.begin(); it != expression_.end(); it++) {
116,395,972✔
1001
    int32_t token = *it;
110,927,395✔
1002

1003
    // If the token is a surface evaluate the sense
1004
    // If the token is a union or intersection check to
1005
    // short circuit
1006
    if (token < OP_UNION) {
110,927,395✔
1007
      if (token == on_surface) {
48,486,894✔
1008
        in_cell = true;
1009
      } else if (-token == on_surface) {
44,826,530✔
1010
        in_cell = false;
1011
      } else {
1012
        // Note the off-by-one indexing
1013
        bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
44,115,422✔
1014
        in_cell = (sense == (token > 0));
44,115,422✔
1015
      }
1016
    } else if ((token == OP_UNION && in_cell == true) ||
62,440,501✔
1017
               (token == OP_INTERSECTION && in_cell == false)) {
30,060,460✔
1018
      // If the total depth is zero return
1019
      if (total_depth == 0) {
6,680,064✔
1020
        return in_cell;
1,947,682✔
1021
      }
1022

1023
      total_depth--;
4,732,382✔
1024

1025
      // While the iterator is within the bounds of the vector
1026
      int depth = 1;
4,732,382✔
1027
      do {
29,551,858✔
1028
        // Get next token
1029
        it++;
29,551,858✔
1030
        int32_t next_token = *it;
29,551,858✔
1031

1032
        // If the token is an a parenthesis
1033
        if (next_token > OP_COMPLEMENT) {
29,551,858✔
1034
          // Adjust depth accordingly
1035
          if (next_token == OP_RIGHT_PAREN) {
4,923,842✔
1036
            depth--;
4,828,112✔
1037
          } else {
1038
            depth++;
95,730✔
1039
          }
1040
        }
1041
      } while (depth > 0);
29,551,858✔
1042
    } else if (token == OP_LEFT_PAREN) {
55,760,437✔
1043
      total_depth++;
9,711,092✔
1044
    } else if (token == OP_RIGHT_PAREN) {
46,049,345✔
1045
      total_depth--;
4,978,710✔
1046
    }
1047
  }
1048
  return in_cell;
1049
}
1050

1051
//==============================================================================
1052

1053
BoundingBox Region::bounding_box(int32_t cell_id) const
88✔
1054
{
1055
  if (simple_) {
88✔
1056
    return bounding_box_simple();
44✔
1057
  } else {
1058
    auto postfix = generate_postfix(cell_id);
44✔
1059
    return bounding_box_complex(postfix);
88✔
1060
  }
44✔
1061
}
1062

1063
//==============================================================================
1064

1065
BoundingBox Region::bounding_box_simple() const
44✔
1066
{
1067
  BoundingBox bbox;
44✔
1068
  for (int32_t token : expression_) {
176✔
1069
    bbox &= model::surfaces[abs(token) - 1]->bounding_box(token > 0);
132✔
1070
  }
1071
  return bbox;
44✔
1072
}
1073

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

1076
BoundingBox Region::bounding_box_complex(vector<int32_t> postfix) const
44✔
1077
{
1078
  vector<BoundingBox> stack(postfix.size());
44✔
1079
  int i_stack = -1;
44✔
1080

1081
  for (auto& token : postfix) {
792✔
1082
    if (token == OP_UNION) {
748✔
1083
      stack[i_stack - 1] = stack[i_stack - 1] | stack[i_stack];
154✔
1084
      i_stack--;
154✔
1085
    } else if (token == OP_INTERSECTION) {
594✔
1086
      stack[i_stack - 1] = stack[i_stack - 1] & stack[i_stack];
198✔
1087
      i_stack--;
198✔
1088
    } else {
1089
      i_stack++;
396✔
1090
      stack[i_stack] = model::surfaces[abs(token) - 1]->bounding_box(token > 0);
396✔
1091
    }
1092
  }
1093

1094
  assert(i_stack == 0);
44!
1095
  return stack.front();
44✔
1096
}
44✔
1097

1098
//==============================================================================
1099

1100
vector<int32_t> Region::surfaces() const
5,270✔
1101
{
1102
  if (simple_) {
5,270✔
1103
    return expression_;
5,250✔
1104
  }
1105

1106
  vector<int32_t> surfaces = expression_;
20✔
1107

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

1111
  while (it != surfaces.end()) {
60✔
1112
    surfaces.erase(it);
40✔
1113

1114
    it = std::find_if(surfaces.begin(), surfaces.end(),
40✔
1115
      [&](const auto& value) { return value >= OP_UNION; });
80!
1116
  }
1117

1118
  return surfaces;
20✔
1119
}
5,270✔
1120

1121
//==============================================================================
1122
// Non-method functions
1123
//==============================================================================
1124

1125
void read_cells(pugi::xml_node node)
8,154✔
1126
{
1127
  simulation::forced_collision = false;
8,154✔
1128

1129
  // Count the number of cells.
1130
  int n_cells = 0;
8,154✔
1131
  for (pugi::xml_node cell_node : node.children("cell")) {
42,216✔
1132
    n_cells++;
34,062✔
1133
  }
1134

1135
  // Loop over XML cell elements and populate the array.
1136
  model::cells.reserve(n_cells);
8,154✔
1137
  for (pugi::xml_node cell_node : node.children("cell")) {
42,216✔
1138
    model::cells.push_back(make_unique<CSGCell>(cell_node));
34,062✔
1139
  }
1140

1141
  // Fill the cell map.
1142
  for (int i = 0; i < model::cells.size(); i++) {
42,216✔
1143
    int32_t id = model::cells[i]->id_;
34,062!
1144
    auto search = model::cell_map.find(id);
34,062!
1145
    if (search == model::cell_map.end()) {
34,062!
1146
      model::cell_map[id] = i;
34,062✔
1147
    } else {
1148
      fatal_error(
×
1149
        fmt::format("Two or more cells use the same unique ID: {}", id));
×
1150
    }
1151
  }
1152

1153
  read_dagmc_universes(node);
8,154✔
1154

1155
  populate_universes();
8,152✔
1156

1157
  // Allocate the cell overlap count if necessary.
1158
  if (settings::check_overlaps) {
8,152✔
1159
    model::overlap_check_count.resize(model::cells.size(), 0);
119✔
1160
  }
1161

1162
  if (model::cells.size() == 0) {
8,152!
1163
    fatal_error("No cells were found in the geometry.xml file");
×
1164
  }
1165
}
8,152✔
1166

1167
void populate_universes()
8,154✔
1168
{
1169
  // Used to map universe index to the index of an implicit complement cell for
1170
  // DAGMC universes
1171
  std::unordered_map<int, int> implicit_comp_cells;
8,154✔
1172

1173
  // Populate the Universe vector and map.
1174
  for (int index_cell = 0; index_cell < model::cells.size(); index_cell++) {
42,432✔
1175
    int32_t uid = model::cells[index_cell]->universe_;
34,278✔
1176
    auto it = model::universe_map.find(uid);
34,278✔
1177
    if (it == model::universe_map.end()) {
34,278✔
1178
      model::universes.push_back(make_unique<Universe>());
39,380✔
1179
      model::universes.back()->id_ = uid;
19,690✔
1180
      model::universes.back()->cells_.push_back(index_cell);
19,690✔
1181
      model::universe_map[uid] = model::universes.size() - 1;
19,690✔
1182
    } else {
1183
#ifdef OPENMC_DAGMC_ENABLED
1184
      // Skip implicit complement cells for now
1185
      Universe* univ = model::universes[it->second].get();
1,975!
1186
      DAGUniverse* dag_univ = dynamic_cast<DAGUniverse*>(univ);
1,975!
1187
      if (dag_univ && (dag_univ->implicit_complement_idx() == index_cell)) {
1,975✔
1188
        implicit_comp_cells[it->second] = index_cell;
46✔
1189
        continue;
46✔
1190
      }
1191
#endif
1192

1193
      model::universes[it->second]->cells_.push_back(index_cell);
14,542✔
1194
    }
1195
  }
1196

1197
  // Add DAGUniverse implicit complement cells last
1198
  for (const auto& it : implicit_comp_cells) {
8,200✔
1199
    int index_univ = it.first;
46✔
1200
    int index_cell = it.second;
46✔
1201
    model::universes[index_univ]->cells_.push_back(index_cell);
46!
1202
  }
1203

1204
  model::universes.shrink_to_fit();
8,154✔
1205
}
8,154✔
1206

1207
//==============================================================================
1208
// C-API functions
1209
//==============================================================================
1210

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

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

1264
extern "C" int openmc_cell_set_temperature(
88✔
1265
  int32_t index, double T, const int32_t* instance, bool set_contained)
1266
{
1267
  if (index < 0 || index >= model::cells.size()) {
88!
1268
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
1269
    return OPENMC_E_OUT_OF_BOUNDS;
×
1270
  }
1271

1272
  int32_t instance_index = instance ? *instance : -1;
88✔
1273
  try {
88✔
1274
    model::cells[index]->set_temperature(T, instance_index, set_contained);
88✔
1275
  } catch (const std::exception& e) {
×
1276
    set_errmsg(e.what());
×
1277
    return OPENMC_E_UNASSIGNED;
×
1278
  }
×
1279
  return 0;
1280
}
1281

1282
extern "C" int openmc_cell_set_density(
88✔
1283
  int32_t index, double density, const int32_t* instance, bool set_contained)
1284
{
1285
  if (index < 0 || index >= model::cells.size()) {
88!
1286
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
1287
    return OPENMC_E_OUT_OF_BOUNDS;
×
1288
  }
1289

1290
  int32_t instance_index = instance ? *instance : -1;
88✔
1291
  try {
88✔
1292
    model::cells[index]->set_density(density, instance_index, set_contained);
88✔
1293
  } catch (const std::exception& e) {
×
1294
    set_errmsg(e.what());
×
1295
    return OPENMC_E_UNASSIGNED;
×
1296
  }
×
1297
  return 0;
1298
}
1299

1300
extern "C" int openmc_cell_get_temperature(
91✔
1301
  int32_t index, const int32_t* instance, double* T)
1302
{
1303
  if (index < 0 || index >= model::cells.size()) {
91!
1304
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
1305
    return OPENMC_E_OUT_OF_BOUNDS;
×
1306
  }
1307

1308
  int32_t instance_index = instance ? *instance : -1;
91✔
1309
  try {
91✔
1310
    *T = model::cells[index]->temperature(instance_index);
91✔
1311
  } catch (const std::exception& e) {
×
1312
    set_errmsg(e.what());
×
1313
    return OPENMC_E_UNASSIGNED;
×
1314
  }
×
1315
  return 0;
91✔
1316
}
1317

1318
extern "C" int openmc_cell_get_density(
88✔
1319
  int32_t index, const int32_t* instance, double* density)
1320
{
1321
  if (index < 0 || index >= model::cells.size()) {
88!
1322
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
1323
    return OPENMC_E_OUT_OF_BOUNDS;
×
1324
  }
1325

1326
  int32_t instance_index = instance ? *instance : -1;
88✔
1327
  try {
88✔
1328
    if (model::cells[index]->type_ != Fill::MATERIAL) {
88!
1329
      fatal_error(
×
1330
        fmt::format("Cell {}, instance {} is not filled with a material.",
×
1331
          model::cells[index]->id_, instance_index));
×
1332
    }
1333

1334
    int32_t mat_index = model::cells[index]->material(instance_index);
88!
1335
    if (mat_index == MATERIAL_VOID) {
88!
1336
      *density = 0.0;
×
1337
    } else {
1338
      *density = model::cells[index]->density_mult(instance_index) *
88✔
1339
                 model::materials[mat_index]->density_gpcc();
176!
1340
    }
1341
  } catch (const std::exception& e) {
×
1342
    set_errmsg(e.what());
×
1343
    return OPENMC_E_UNASSIGNED;
×
1344
  }
×
1345
  return 0;
1346
}
1347

1348
//! Get the bounding box of a cell
1349
extern "C" int openmc_cell_bounding_box(
55✔
1350
  const int32_t index, double* llc, double* urc)
1351
{
1352

1353
  BoundingBox bbox;
55✔
1354

1355
  const auto& c = model::cells[index];
55✔
1356
  bbox = c->bounding_box();
55✔
1357

1358
  // set lower left corner values
1359
  llc[0] = bbox.min.x;
55✔
1360
  llc[1] = bbox.min.y;
55✔
1361
  llc[2] = bbox.min.z;
55✔
1362

1363
  // set upper right corner values
1364
  urc[0] = bbox.max.x;
55✔
1365
  urc[1] = bbox.max.y;
55✔
1366
  urc[2] = bbox.max.z;
55✔
1367

1368
  return 0;
55✔
1369
}
1370

1371
//! Get the name of a cell
1372
extern "C" int openmc_cell_get_name(int32_t index, const char** name)
22✔
1373
{
1374
  if (index < 0 || index >= model::cells.size()) {
22!
1375
    set_errmsg("Index in cells array is out of bounds.");
×
1376
    return OPENMC_E_OUT_OF_BOUNDS;
×
1377
  }
1378

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

1381
  return 0;
22✔
1382
}
1383

1384
//! Set the name of a cell
1385
extern "C" int openmc_cell_set_name(int32_t index, const char* name)
11✔
1386
{
1387
  if (index < 0 || index >= model::cells.size()) {
11!
1388
    set_errmsg("Index in cells array is out of bounds.");
×
1389
    return OPENMC_E_OUT_OF_BOUNDS;
×
1390
  }
1391

1392
  model::cells[index]->set_name(name);
22✔
1393

1394
  return 0;
11✔
1395
}
1396

1397
//==============================================================================
1398
//! Define a containing (parent) cell
1399
//==============================================================================
1400

1401
//! Used to locate a universe fill in the geometry
1402
struct ParentCell {
1403
  bool operator==(const ParentCell& other) const
135✔
1404
  {
1405
    return cell_index == other.cell_index &&
135!
1406
           lattice_index == other.lattice_index;
135!
1407
  }
1408

1409
  bool operator<(const ParentCell& other) const
1410
  {
1411
    return cell_index < other.cell_index ||
1412
           (cell_index == other.cell_index &&
1413
             lattice_index < other.lattice_index);
1414
  }
1415

1416
  int64_t cell_index;
1417
  int64_t lattice_index;
1418
};
1419

1420
//! Structure used to insert ParentCell into hashed STL data structures
1421
struct ParentCellHash {
1422
  std::size_t operator()(const ParentCell& p) const
631✔
1423
  {
1424
    return 4096 * p.cell_index + p.lattice_index;
631!
1425
  }
1426
};
1427

1428
//! Used to manage a traversal stack when locating parent cells of a cell
1429
//! instance in the model
1430
struct ParentCellStack {
106✔
1431

1432
  //! push method that adds to the parent_cells visited cells for this search
1433
  //! universe
1434
  void push(int32_t search_universe, const ParentCell& pc)
105✔
1435
  {
1436
    parent_cells_.push_back(pc);
105✔
1437
    // add parent cell to the set of cells we've visited for this search
1438
    // universe
1439
    visited_cells_[search_universe].insert(pc);
105✔
1440
  }
105✔
1441

1442
  //! removes the last parent_cell and clears the visited cells for the popped
1443
  //! cell's universe
1444
  void pop()
75✔
1445
  {
1446
    visited_cells_[this->current_univ()].clear();
75✔
1447
    parent_cells_.pop_back();
75✔
1448
  }
75✔
1449

1450
  //! checks whether or not the parent cell has been visited already for this
1451
  //! search universe
1452
  bool visited(int32_t search_universe, const ParentCell& parent_cell)
526✔
1453
  {
1454
    return visited_cells_[search_universe].count(parent_cell) != 0;
526✔
1455
  }
1456

1457
  //! return the next universe to search for a parent cell
1458
  int32_t current_univ() const
75✔
1459
  {
1460
    return model::cells[parent_cells_.back().cell_index]->universe_;
75✔
1461
  }
1462

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

1466
  //! compute an instance for the provided distribcell index
1467
  int32_t compute_instance(int32_t distribcell_index) const
181✔
1468
  {
1469
    if (distribcell_index == C_NONE)
181✔
1470
      return 0;
1471

1472
    int32_t instance = 0;
120✔
1473
    for (const auto& parent_cell : this->parent_cells_) {
225✔
1474
      auto& cell = model::cells[parent_cell.cell_index];
105!
1475
      if (cell->type_ == Fill::UNIVERSE) {
105!
1476
        instance += cell->offset_[distribcell_index];
×
1477
      } else if (cell->type_ == Fill::LATTICE) {
105!
1478
        auto& lattice = model::lattices[cell->fill_];
105✔
1479
        instance +=
105✔
1480
          lattice->offset(distribcell_index, parent_cell.lattice_index);
105✔
1481
      }
1482
    }
1483
    return instance;
1484
  }
1485

1486
  // Accessors
1487
  vector<ParentCell>& parent_cells() { return parent_cells_; }
106✔
1488
  const vector<ParentCell>& parent_cells() const { return parent_cells_; }
1489

1490
  // Data Members
1491
  vector<ParentCell> parent_cells_;
1492
  std::unordered_map<int32_t, std::unordered_set<ParentCell, ParentCellHash>>
1493
    visited_cells_;
1494
};
1495

1496
vector<ParentCell> Cell::find_parent_cells(
×
1497
  int32_t instance, const Position& r) const
1498
{
1499

1500
  // create a temporary particle
1501
  GeometryState dummy_particle {};
×
1502
  dummy_particle.r() = r;
×
1503
  dummy_particle.u() = {0., 0., 1.};
×
1504

1505
  return find_parent_cells(instance, dummy_particle);
×
1506
}
×
1507

1508
vector<ParentCell> Cell::find_parent_cells(
×
1509
  int32_t instance, GeometryState& p) const
1510
{
1511
  // look up the particle's location
1512
  exhaustive_find_cell(p);
×
1513
  const auto& coords = p.coord();
×
1514

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

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

1539
  // if this loop finished because the cell was found and
1540
  // the instance matches the one requested in the call
1541
  // we have the correct path and can return the stack
1542
  if (cell_found &&
×
1543
      stack.compute_instance(this->distribcell_index_) == instance) {
×
1544
    return stack.parent_cells();
×
1545
  }
1546

1547
  // fall back on an exhaustive search for the cell's parents
1548
  return exhaustive_find_parent_cells(instance);
×
1549
}
×
1550

1551
vector<ParentCell> Cell::exhaustive_find_parent_cells(int32_t instance) const
106✔
1552
{
1553
  ParentCellStack stack;
106✔
1554
  // start with this cell's universe
1555
  int32_t prev_univ_idx;
106✔
1556
  int32_t univ_idx = this->universe_;
106✔
1557

1558
  while (true) {
181✔
1559
    const auto& univ = model::universes[univ_idx];
181✔
1560
    prev_univ_idx = univ_idx;
181✔
1561

1562
    // search for a cell that is filled w/ this universe
1563
    for (const auto& cell : model::cells) {
1,159✔
1564
      // if this is a material-filled cell, move on
1565
      if (cell->type_ == Fill::MATERIAL)
1,083✔
1566
        continue;
602✔
1567

1568
      if (cell->type_ == Fill::UNIVERSE) {
481✔
1569
        // if this is in the set of cells previously visited for this universe,
1570
        // move on
1571
        if (stack.visited(univ_idx, {model::cell_map[cell->id_], C_NONE}))
286!
1572
          continue;
×
1573

1574
        // if this cell contains the universe we're searching for, add it to the
1575
        // stack
1576
        if (cell->fill_ == univ_idx) {
286!
1577
          stack.push(univ_idx, {model::cell_map[cell->id_], C_NONE});
×
1578
          univ_idx = cell->universe_;
×
1579
        }
1580
      } else if (cell->type_ == Fill::LATTICE) {
195!
1581
        // retrieve the lattice and lattice universes
1582
        const auto& lattice = model::lattices[cell->fill_];
195✔
1583
        const auto& lattice_univs = lattice->universes_;
195✔
1584

1585
        // start search for universe
1586
        auto lat_it = lattice_univs.begin();
195✔
1587
        while (true) {
465✔
1588
          // find the next lattice cell with this universe
1589
          lat_it = std::find(lat_it, lattice_univs.end(), univ_idx);
330✔
1590
          if (lat_it == lattice_univs.end())
330✔
1591
            break;
1592

1593
          int lattice_idx = lat_it - lattice_univs.begin();
240✔
1594

1595
          // move iterator forward one to avoid finding the same entry
1596
          lat_it++;
240✔
1597
          if (stack.visited(
480✔
1598
                univ_idx, {model::cell_map[cell->id_], lattice_idx}))
240✔
1599
            continue;
135✔
1600

1601
          // add this cell and lattice index to the stack and exit loop
1602
          stack.push(univ_idx, {model::cell_map[cell->id_], lattice_idx});
105✔
1603
          univ_idx = cell->universe_;
105✔
1604
          break;
105✔
1605
        }
135✔
1606
      }
1607
      // if we've updated the universe, break
1608
      if (prev_univ_idx != univ_idx)
481✔
1609
        break;
1610
    } // end cell loop search for universe
1611

1612
    // if we're at the top of the geometry and the instance matches, we're done
1613
    if (univ_idx == model::root_universe &&
217!
1614
        stack.compute_instance(this->distribcell_index_) == instance)
181✔
1615
      break;
1616

1617
    // if there is no match on the original cell's universe, report an error
1618
    if (univ_idx == this->universe_) {
75!
1619
      fatal_error(
×
1620
        fmt::format("Could not find the parent cells for cell {}, instance {}.",
×
1621
          this->id_, instance));
×
1622
    }
1623

1624
    // if we don't find a suitable update, adjust the stack and continue
1625
    if (univ_idx == model::root_universe || univ_idx == prev_univ_idx) {
75!
1626
      stack.pop();
75✔
1627
      univ_idx = stack.empty() ? this->universe_ : stack.current_univ();
75!
1628
    }
1629

1630
  } // end while
1631

1632
  // reverse the stack so the highest cell comes first
1633
  std::reverse(stack.parent_cells().begin(), stack.parent_cells().end());
106✔
1634
  return stack.parent_cells();
212✔
1635
}
106✔
1636

1637
std::unordered_map<int32_t, vector<int32_t>> Cell::get_contained_cells(
151✔
1638
  int32_t instance, Position* hint) const
1639
{
1640
  std::unordered_map<int32_t, vector<int32_t>> contained_cells;
151✔
1641

1642
  // if this is a material-filled cell it has no contained cells
1643
  if (this->type_ == Fill::MATERIAL)
151✔
1644
    return contained_cells;
1645

1646
  // find the pathway through the geometry to this cell
1647
  vector<ParentCell> parent_cells;
106!
1648

1649
  // if a positional hint is provided, attempt to do a fast lookup
1650
  // of the parent cells
1651
  parent_cells = hint ? find_parent_cells(instance, *hint)
106!
1652
                      : exhaustive_find_parent_cells(instance);
106✔
1653

1654
  // if this cell is filled w/ a material, it contains no other cells
1655
  if (type_ != Fill::MATERIAL) {
106!
1656
    this->get_contained_cells_inner(contained_cells, parent_cells);
106✔
1657
  }
1658

1659
  return contained_cells;
106✔
1660
}
151✔
1661

1662
//! Get all cells within this cell
1663
void Cell::get_contained_cells_inner(
82,278✔
1664
  std::unordered_map<int32_t, vector<int32_t>>& contained_cells,
1665
  vector<ParentCell>& parent_cells) const
1666
{
1667

1668
  // filled by material, determine instance based on parent cells
1669
  if (type_ == Fill::MATERIAL) {
82,278✔
1670
    int instance = 0;
81,692✔
1671
    if (this->distribcell_index_ >= 0) {
81,692!
1672
      for (auto& parent_cell : parent_cells) {
245,074✔
1673
        auto& cell = model::cells[parent_cell.cell_index];
163,382✔
1674
        if (cell->type_ == Fill::UNIVERSE) {
163,382✔
1675
          instance += cell->offset_[distribcell_index_];
80,192✔
1676
        } else if (cell->type_ == Fill::LATTICE) {
83,190!
1677
          auto& lattice = model::lattices[cell->fill_];
83,190✔
1678
          instance += lattice->offset(
83,190✔
1679
            this->distribcell_index_, parent_cell.lattice_index);
83,190✔
1680
        }
1681
      }
1682
    }
1683
    // add entry to contained cells
1684
    contained_cells[model::cell_map[id_]].push_back(instance);
81,692✔
1685
    // filled with universe, add the containing cell to the parent cells
1686
    // and recurse
1687
  } else if (type_ == Fill::UNIVERSE) {
586✔
1688
    parent_cells.push_back({model::cell_map[id_], -1});
496✔
1689
    auto& univ = model::universes[fill_];
496✔
1690
    for (auto cell_index : univ->cells_) {
2,973✔
1691
      auto& cell = model::cells[cell_index];
2,477✔
1692
      cell->get_contained_cells_inner(contained_cells, parent_cells);
2,477✔
1693
    }
1694
    parent_cells.pop_back();
496✔
1695
    // filled with a lattice, visit each universe in the lattice
1696
    // with a recursive call to collect the cell instances
1697
  } else if (type_ == Fill::LATTICE) {
90!
1698
    auto& lattice = model::lattices[fill_];
90✔
1699
    for (auto i = lattice->begin(); i != lattice->end(); ++i) {
79,470✔
1700
      auto& univ = model::universes[*i];
79,380✔
1701
      parent_cells.push_back({model::cell_map[id_], i.indx_});
79,380✔
1702
      for (auto cell_index : univ->cells_) {
159,075✔
1703
        auto& cell = model::cells[cell_index];
79,695✔
1704
        cell->get_contained_cells_inner(contained_cells, parent_cells);
79,695✔
1705
      }
1706
      parent_cells.pop_back();
79,380✔
1707
    }
1708
  }
1709
}
82,278✔
1710

1711
//! Return the index in the cells array of a cell with a given ID
1712
extern "C" int openmc_get_cell_index(int32_t id, int32_t* index)
492✔
1713
{
1714
  auto it = model::cell_map.find(id);
492✔
1715
  if (it != model::cell_map.end()) {
492✔
1716
    *index = it->second;
481✔
1717
    return 0;
481✔
1718
  } else {
1719
    set_errmsg("No cell exists with ID=" + std::to_string(id) + ".");
11✔
1720
    return OPENMC_E_INVALID_ID;
11✔
1721
  }
1722
}
1723

1724
//! Return the ID of a cell
1725
extern "C" int openmc_cell_get_id(int32_t index, int32_t* id)
600,976✔
1726
{
1727
  if (index >= 0 && index < model::cells.size()) {
600,976!
1728
    *id = model::cells[index]->id_;
600,976✔
1729
    return 0;
600,976✔
1730
  } else {
1731
    set_errmsg("Index in cells array is out of bounds.");
×
1732
    return OPENMC_E_OUT_OF_BOUNDS;
×
1733
  }
1734
}
1735

1736
//! Set the ID of a cell
1737
extern "C" int openmc_cell_set_id(int32_t index, int32_t id)
22✔
1738
{
1739
  if (index >= 0 && index < model::cells.size()) {
22!
1740
    model::cells[index]->id_ = id;
22✔
1741
    model::cell_map[id] = index;
22✔
1742
    return 0;
22✔
1743
  } else {
1744
    set_errmsg("Index in cells array is out of bounds.");
×
1745
    return OPENMC_E_OUT_OF_BOUNDS;
×
1746
  }
1747
}
1748

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

1764
//! Set the translation vector of a cell
1765
extern "C" int openmc_cell_set_translation(int32_t index, const double xyz[])
33✔
1766
{
1767
  if (index >= 0 && index < model::cells.size()) {
33!
1768
    if (model::cells[index]->fill_ == C_NONE) {
33✔
1769
      set_errmsg(fmt::format("Cannot apply a translation to cell {}"
11✔
1770
                             " because it is not filled with another universe",
1771
        index));
1772
      return OPENMC_E_GEOMETRY;
11✔
1773
    }
1774
    model::cells[index]->translation_ = Position(xyz);
22✔
1775
    return 0;
22✔
1776
  } else {
1777
    set_errmsg("Index in cells array is out of bounds.");
×
1778
    return OPENMC_E_OUT_OF_BOUNDS;
×
1779
  }
1780
}
1781

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

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

1816
//! Get the number of instances of the requested cell
1817
extern "C" int openmc_cell_get_num_instances(
11✔
1818
  int32_t index, int32_t* num_instances)
1819
{
1820
  if (index < 0 || index >= model::cells.size()) {
11!
1821
    set_errmsg("Index in cells array is out of bounds.");
×
1822
    return OPENMC_E_OUT_OF_BOUNDS;
×
1823
  }
1824
  *num_instances = model::cells[index]->n_instances();
11✔
1825
  return 0;
11✔
1826
}
1827

1828
//! Extend the cells array by n elements
1829
extern "C" int openmc_extend_cells(
22✔
1830
  int32_t n, int32_t* index_start, int32_t* index_end)
1831
{
1832
  if (index_start)
22!
1833
    *index_start = model::cells.size();
22✔
1834
  if (index_end)
22!
1835
    *index_end = model::cells.size() + n - 1;
×
1836
  for (int32_t i = 0; i < n; i++) {
44✔
1837
    model::cells.push_back(make_unique<CSGCell>());
22✔
1838
  }
1839
  return 0;
22✔
1840
}
1841

1842
extern "C" int cells_size()
44✔
1843
{
1844
  return model::cells.size();
44✔
1845
}
1846

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