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

openmc-dev / openmc / 17649795262

11 Sep 2025 03:40PM UTC coverage: 85.186% (-0.02%) from 85.206%
17649795262

Pull #3546

github

web-flow
Merge c539b6c70 into c7175289e
Pull Request #3546: Add distributed cell density multipliers

161 of 202 new or added lines in 13 files covered. (79.7%)

162 existing lines in 7 files now uncovered.

53089 of 62321 relevant lines covered (85.19%)

38539429.19 hits per line

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

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

48
void Cell::set_rotation(const vector<double>& rot)
452✔
49
{
50
  if (fill_ == C_NONE) {
452✔
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) {
452✔
57
    fatal_error(fmt::format("Non-3D rotation vector applied to cell {}", id_));
×
58
  }
59

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

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

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

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

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

121
  if (instance >= 0) {
144✔
122
    double rho =
123
      rho_mult_.size() == 1 ? rho_mult_.at(0) : rho_mult_.at(instance);
144✔
124
    return rho * model::materials[mat_index]->density_gpcc();
144✔
125
  } else {
NEW
126
    return rho_mult_[0] * model::materials[mat_index]->density_gpcc();
×
127
  }
128
}
129

130
void Cell::set_temperature(double T, int32_t instance, bool set_contained)
512✔
131
{
132
  if (settings::temperature_method == TemperatureMethod::INTERPOLATION) {
512✔
133
    if (T < (data::temperature_min - settings::temperature_tolerance)) {
×
UNCOV
134
      throw std::runtime_error {
×
135
        fmt::format("Temperature of {} K is below minimum temperature at "
×
136
                    "which data is available of {} K.",
UNCOV
137
          T, data::temperature_min)};
×
UNCOV
138
    } else if (T > (data::temperature_max + settings::temperature_tolerance)) {
×
UNCOV
139
      throw std::runtime_error {
×
UNCOV
140
        fmt::format("Temperature of {} K is above maximum temperature at "
×
141
                    "which data is available of {} K.",
UNCOV
142
          T, data::temperature_max)};
×
143
    }
144
  }
145

146
  if (type_ == Fill::MATERIAL) {
512✔
147
    if (instance >= 0) {
480✔
148
      // If temperature vector is not big enough, resize it first
149
      if (sqrtkT_.size() != n_instances())
396✔
150
        sqrtkT_.resize(n_instances(), sqrtkT_[0]);
48✔
151

152
      // Set temperature for the corresponding instance
153
      sqrtkT_.at(instance) = std::sqrt(K_BOLTZMANN * T);
396✔
154
    } else {
155
      // Set temperature for all instances
156
      for (auto& T_ : sqrtkT_) {
168✔
157
        T_ = std::sqrt(K_BOLTZMANN * T);
84✔
158
      }
159
    }
160
  } else {
161
    if (!set_contained) {
32✔
UNCOV
162
      throw std::runtime_error {
×
UNCOV
163
        fmt::format("Attempted to set the temperature of cell {} "
×
164
                    "which is not filled by a material.",
UNCOV
165
          id_)};
×
166
    }
167

168
    auto contained_cells = this->get_contained_cells(instance);
32✔
169
    for (const auto& entry : contained_cells) {
128✔
170
      auto& cell = model::cells[entry.first];
96✔
171
      assert(cell->type_ == Fill::MATERIAL);
78✔
172
      auto& instances = entry.second;
96✔
173
      for (auto instance : instances) {
336✔
174
        cell->set_temperature(T, instance);
240✔
175
      }
176
    }
177
  }
32✔
178
}
512✔
179

180
void Cell::set_density(double rho, int32_t instance, bool set_contained)
292✔
181
{
182
  if (type_ != Fill::MATERIAL && !set_contained) {
292✔
NEW
183
    fatal_error(
×
NEW
184
      fmt::format("Attempted to set the density multiplier of cell {} "
×
185
                  "which is not filled by a material.",
NEW
186
        id_));
×
187
  }
188

189
  if (type_ == Fill::MATERIAL) {
292✔
190
    const int32_t mat_index = material(instance);
276✔
191
    if (mat_index == MATERIAL_VOID)
276✔
NEW
192
      return;
×
193

194
    if (instance >= 0) {
276✔
195
      // If density multiplier vector is not big enough, resize it first
196
      if (rho_mult_.size() != n_instances())
204✔
197
        rho_mult_.resize(n_instances(), rho_mult_[0]);
48✔
198

199
      // Set density multiplier for the corresponding instance
200
      rho_mult_.at(instance) =
204✔
201
        rho / model::materials[mat_index]->density_gpcc();
204✔
202
    } else {
203
      // Set density multiplier for all instances
204
      for (auto& Rho_ : rho_mult_) {
144✔
205
        Rho_ = rho / model::materials[mat_index]->density_gpcc();
72✔
206
      }
207
    }
208
  } else {
209
    auto contained_cells = this->get_contained_cells(instance);
16✔
210
    for (const auto& entry : contained_cells) {
64✔
211
      auto& cell = model::cells[entry.first];
48✔
212
      assert(cell->type_ == Fill::MATERIAL);
39✔
213
      auto& instances = entry.second;
48✔
214
      for (auto instance : instances) {
96✔
215
        cell->set_density(rho, instance);
48✔
216
      }
217
    }
218
  }
16✔
219
}
220

221
void Cell::export_properties_hdf5(hid_t group) const
168✔
222
{
223
  // Create a group for this cell.
224
  auto cell_group = create_group(group, fmt::format("cell {}", id_));
336✔
225

226
  // Write temperature in [K] for one or more cell instances
227
  vector<double> temps;
168✔
228
  for (auto sqrtkT_val : sqrtkT_)
312✔
229
    temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
144✔
230
  write_dataset(cell_group, "temperature", temps);
168✔
231

232
  // Write density for one or more cell instances
233
  if (type_ == Fill::MATERIAL && material_.size() > 0) {
168✔
234
    vector<double> rho;
144✔
235
    for (int32_t i = 0; i < rho_mult_.size(); ++i)
288✔
236
      rho.push_back(density(i));
144✔
237

238
    write_dataset(cell_group, "density", rho);
144✔
239
  }
144✔
240

241
  close_group(cell_group);
168✔
242
}
168✔
243

244
void Cell::import_properties_hdf5(hid_t group)
192✔
245
{
246
  auto cell_group = open_group(group, fmt::format("cell {}", id_));
384✔
247

248
  // Read temperatures from file
249
  vector<double> temps;
192✔
250
  read_dataset(cell_group, "temperature", temps);
192✔
251

252
  // Ensure number of temperatures makes sense
253
  auto n_temps = temps.size();
192✔
254
  if (n_temps > 1 && n_temps != n_instances()) {
192✔
NEW
UNCOV
255
    fatal_error(fmt::format(
×
256
      "Number of temperatures for cell {} doesn't match number of instances",
UNCOV
257
      id_));
×
258
  }
259

260
  // Modify temperatures for the cell
261
  sqrtkT_.clear();
192✔
262
  sqrtkT_.resize(temps.size());
192✔
263
  for (int64_t i = 0; i < temps.size(); ++i) {
336✔
264
    this->set_temperature(temps[i], i);
144✔
265
  }
266

267
  // Read densities
268
  if (object_exists(cell_group, "density")) {
192✔
269
    vector<double> rho;
144✔
270
    read_dataset(cell_group, "density", rho);
144✔
271

272
    // Ensure number of densities makes sense
273
    auto n_rho = rho.size();
144✔
274
    if (n_rho > 1 && n_rho != n_instances()) {
144✔
NEW
275
      fatal_error(fmt::format("Number of densities for cell {} "
×
276
                              "doesn't match number of instances",
NEW
277
        id_));
×
278
    }
279

280
    // Set densities.
281
    for (int32_t i = 0; i < n_rho; ++i)
288✔
282
      set_density(rho[i], i);
144✔
283
  }
144✔
284

285
  close_group(cell_group);
192✔
286
}
192✔
287

288
void Cell::to_hdf5(hid_t cell_group) const
26,871✔
289
{
290

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

294
  if (!name_.empty()) {
26,871✔
295
    write_string(group, "name", name_, false);
4,943✔
296
  }
297

298
  write_dataset(group, "universe", model::universes[universe_]->id_);
26,871✔
299

300
  to_hdf5_inner(group);
26,871✔
301

302
  // Write fill information.
303
  if (type_ == Fill::MATERIAL) {
26,871✔
304
    write_dataset(group, "fill_type", "material");
21,844✔
305
    std::vector<int32_t> mat_ids;
21,844✔
306
    for (auto i_mat : material_) {
45,322✔
307
      if (i_mat != MATERIAL_VOID) {
23,478✔
308
        mat_ids.push_back(model::materials[i_mat]->id_);
15,470✔
309
      } else {
310
        mat_ids.push_back(MATERIAL_VOID);
8,008✔
311
      }
312
    }
313
    if (mat_ids.size() == 1) {
21,844✔
314
      write_dataset(group, "material", mat_ids[0]);
21,606✔
315
    } else {
316
      write_dataset(group, "material", mat_ids);
238✔
317
    }
318

319
    std::vector<double> temps;
21,844✔
320
    for (auto sqrtkT_val : sqrtkT_)
45,427✔
321
      temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
23,583✔
322
    write_dataset(group, "temperature", temps);
21,844✔
323

324
    write_dataset(group, "density_mult", rho_mult_);
21,844✔
325

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

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

346
  close_group(group);
26,871✔
347
}
26,871✔
348

349
//==============================================================================
350
// CSGCell implementation
351
//==============================================================================
352

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

361
  if (check_for_node(cell_node, "name")) {
33,189✔
362
    name_ = get_node_value(cell_node, "name");
6,962✔
363
  }
364

365
  if (check_for_node(cell_node, "universe")) {
33,189✔
366
    universe_ = std::stoi(get_node_value(cell_node, "universe"));
31,964✔
367
  } else {
368
    universe_ = 0;
1,225✔
369
  }
370

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

384
  if (fill_present) {
33,189✔
385
    fill_ = std::stoi(get_node_value(cell_node, "fill"));
6,972✔
386
    if (fill_ == universe_) {
6,972✔
UNCOV
387
      fatal_error(fmt::format("Cell {} is filled with the same universe that "
×
388
                              "it is contained in.",
UNCOV
389
        id_));
×
390
    }
391
  } else {
392
    fill_ = C_NONE;
26,217✔
393
  }
394

395
  // Read the material element.  There can be zero materials (filled with a
396
  // universe), more than one material (distribmats), and some materials may
397
  // be "void".
398
  if (material_present) {
33,189✔
399
    vector<std::string> mats {
400
      get_node_array<std::string>(cell_node, "material", true)};
26,217✔
401
    if (mats.size() > 0) {
26,217✔
402
      material_.reserve(mats.size());
26,217✔
403
      for (std::string mat : mats) {
54,086✔
404
        if (mat.compare("void") == 0) {
27,869✔
405
          material_.push_back(MATERIAL_VOID);
8,495✔
406
        } else {
407
          material_.push_back(std::stoi(mat));
19,374✔
408
        }
409
      }
27,869✔
410
    } else {
UNCOV
411
      fatal_error(fmt::format(
×
UNCOV
412
        "An empty material element was specified for cell {}", id_));
×
413
    }
414
  }
26,217✔
415

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

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

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

437
    // Convert to sqrt(k*T).
438
    for (auto& T : sqrtkT_) {
678✔
439
      T = std::sqrt(K_BOLTZMANN * T);
363✔
440
    }
441
  }
442

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

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

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

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

482
  // Read the region specification.
483
  std::string region_spec;
33,189✔
484
  if (check_for_node(cell_node, "region")) {
33,189✔
485
    region_spec = get_node_value(cell_node, "region");
24,390✔
486
  }
487

488
  // Get a tokenized representation of the region specification and apply De
489
  // Morgans law
490
  Region region(region_spec, id_);
33,189✔
491
  region_ = region;
33,189✔
492

493
  // Read the translation vector.
494
  if (check_for_node(cell_node, "translation")) {
33,189✔
495
    if (fill_ == C_NONE) {
2,753✔
496
      fatal_error(fmt::format("Cannot apply a translation to cell {}"
×
497
                              " because it is not filled with another universe",
UNCOV
498
        id_));
×
499
    }
500

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

509
  // Read the rotation transform.
510
  if (check_for_node(cell_node, "rotation")) {
33,189✔
511
    auto rot {get_node_array<double>(cell_node, "rotation")};
416✔
512
    set_rotation(rot);
416✔
513
  }
416✔
514
}
33,189✔
515

516
//==============================================================================
517

518
void CSGCell::to_hdf5_inner(hid_t group_id) const
26,596✔
519
{
520
  write_string(group_id, "geom_type", "csg", false);
26,596✔
521
  write_string(group_id, "region", region_.str(), false);
26,596✔
522
}
26,596✔
523

524
//==============================================================================
525

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

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

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

553
    // continue loop, one token at a time
UNCOV
554
    it--;
×
555
  }
UNCOV
556
  return it;
×
557
}
558

559
//==============================================================================
560
// Region implementation
561
//==============================================================================
562

563
Region::Region(std::string region_spec, int32_t cell_id)
33,189✔
564
{
565
  // Check if region_spec is not empty.
566
  if (!region_spec.empty()) {
33,189✔
567
    // Parse all halfspaces and operators except for intersection (whitespace).
568
    for (int i = 0; i < region_spec.size();) {
149,998✔
569
      if (region_spec[i] == '(') {
125,608✔
570
        expression_.push_back(OP_LEFT_PAREN);
2,270✔
571
        i++;
2,270✔
572

573
      } else if (region_spec[i] == ')') {
123,338✔
574
        expression_.push_back(OP_RIGHT_PAREN);
2,270✔
575
        i++;
2,270✔
576

577
      } else if (region_spec[i] == '|') {
121,068✔
578
        expression_.push_back(OP_UNION);
4,729✔
579
        i++;
4,729✔
580

581
      } else if (region_spec[i] == '~') {
116,339✔
582
        expression_.push_back(OP_COMPLEMENT);
32✔
583
        i++;
32✔
584

585
      } else if (region_spec[i] == '-' || region_spec[i] == '+' ||
196,718✔
586
                 std::isdigit(region_spec[i])) {
80,411✔
587
        // This is the start of a halfspace specification.  Iterate j until we
588
        // find the end, then push-back everything between i and j.
589
        int j = i + 1;
67,189✔
590
        while (j < region_spec.size() && std::isdigit(region_spec[j])) {
136,162✔
591
          j++;
68,973✔
592
        }
593
        expression_.push_back(std::stoi(region_spec.substr(i, j - i)));
67,189✔
594
        i = j;
67,189✔
595

596
      } else if (std::isspace(region_spec[i])) {
49,118✔
597
        i++;
49,118✔
598

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

607
    // Add in intersection operators where a missing operator is needed.
608
    int i = 0;
24,390✔
609
    while (i < expression_.size() - 1) {
114,560✔
610
      bool left_compat {
611
        (expression_[i] < OP_UNION) || (expression_[i] == OP_RIGHT_PAREN)};
90,170✔
612
      bool right_compat {(expression_[i + 1] < OP_UNION) ||
90,170✔
613
                         (expression_[i + 1] == OP_LEFT_PAREN) ||
97,233✔
614
                         (expression_[i + 1] == OP_COMPLEMENT)};
7,063✔
615
      if (left_compat && right_compat) {
90,170✔
616
        expression_.insert(expression_.begin() + i + 1, OP_INTERSECTION);
38,070✔
617
      }
618
      i++;
90,170✔
619
    }
620

621
    // Remove complement operators using DeMorgan's laws
622
    auto it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
24,390✔
623
    while (it != expression_.end()) {
24,422✔
624
      // Erase complement
625
      expression_.erase(it);
32✔
626

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

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

651
    // Convert user IDs to surface indices.
652
    for (auto& r : expression_) {
138,918✔
653
      if (r < OP_UNION) {
114,528✔
654
        const auto& it {model::surface_map.find(abs(r))};
67,189✔
655
        if (it == model::surface_map.end()) {
67,189✔
UNCOV
656
          throw std::runtime_error {
×
UNCOV
657
            "Invalid surface ID " + std::to_string(abs(r)) +
×
UNCOV
658
            " specified in region for cell " + std::to_string(cell_id) + "."};
×
659
        }
660
        r = (r > 0) ? it->second + 1 : -(it->second + 1);
67,189✔
661
      }
662
    }
663

664
    // Check if this is a simple cell.
665
    simple_ = true;
24,390✔
666
    for (int32_t token : expression_) {
119,890✔
667
      if (token == OP_UNION) {
96,891✔
668
        simple_ = false;
1,391✔
669
        // Ensure intersections have precedence over unions
670
        add_precedence();
1,391✔
671
        break;
1,391✔
672
      }
673
    }
674

675
    // If this cell is simple, remove all the superfluous operator tokens.
676
    if (simple_) {
24,390✔
677
      for (auto it = expression_.begin(); it != expression_.end(); it++) {
111,094✔
678
        if (*it == OP_INTERSECTION || *it > OP_COMPLEMENT) {
88,095✔
679
          expression_.erase(it);
32,548✔
680
          it--;
32,548✔
681
        }
682
      }
683
    }
684
    expression_.shrink_to_fit();
24,390✔
685

686
  } else {
687
    simple_ = true;
8,799✔
688
  }
689
}
33,189✔
690

691
//==============================================================================
692

693
void Region::apply_demorgan(
224✔
694
  vector<int32_t>::iterator start, vector<int32_t>::iterator stop)
695
{
696
  do {
697
    if (*start < OP_UNION) {
224✔
698
      *start *= -1;
128✔
699
    } else if (*start == OP_UNION) {
96✔
UNCOV
700
      *start = OP_INTERSECTION;
×
701
    } else if (*start == OP_INTERSECTION) {
96✔
702
      *start = OP_UNION;
96✔
703
    }
704
    start++;
224✔
705
  } while (start < stop);
224✔
706
}
32✔
707

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

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

722
  // Keep track of return iterator distance. If we don't encounter a left
723
  // parenthesis, we return an iterator corresponding to wherever the right
724
  // parenthesis is inserted. If a left parenthesis is encountered, an iterator
725
  // corresponding to the left parenthesis is returned. Also note that we keep
726
  // track of a *distance* instead of an iterator because the underlying memory
727
  // allocation may change.
728
  std::size_t return_it_dist = 0;
32✔
729

730
  // Add right parenthesis
731
  // While the start iterator is within the bounds of infix
732
  while (start + 1 < expression_.size()) {
224✔
733
    start++;
224✔
734

735
    // If the current token is an operator and is different than the start token
736
    if (expression_[start] >= OP_UNION && expression_[start] != start_token) {
224✔
737
      // Skip wrapped regions but save iterator position to check precedence and
738
      // add right parenthesis, right parenthesis position depends on the
739
      // operator, when the operator is a union then do not include the operator
740
      // in the region, when the operator is an intersection then include the
741
      // operator and next surface
742
      if (expression_[start] == OP_LEFT_PAREN) {
32✔
UNCOV
743
        return_it_dist = start;
×
744
        int depth = 1;
×
745
        do {
UNCOV
746
          start++;
×
747
          if (expression_[start] > OP_COMPLEMENT) {
×
UNCOV
748
            if (expression_[start] == OP_RIGHT_PAREN) {
×
UNCOV
749
              depth--;
×
750
            } else {
UNCOV
751
              depth++;
×
752
            }
753
          }
754
        } while (depth > 0);
×
755
      } else {
756
        if (start_token == OP_UNION) {
32✔
UNCOV
757
          --start;
×
758
        }
759
        expression_.insert(expression_.begin() + start, OP_RIGHT_PAREN);
32✔
760
        if (return_it_dist > 0) {
32✔
UNCOV
761
          return return_it_dist;
×
762
        } else {
763
          return start - 1;
32✔
764
        }
765
      }
766
    }
767
  }
768
  // If we get here a right parenthesis hasn't been placed,
769
  // return iterator
UNCOV
770
  expression_.push_back(OP_RIGHT_PAREN);
×
UNCOV
771
  if (return_it_dist > 0) {
×
UNCOV
772
    return return_it_dist;
×
773
  } else {
UNCOV
774
    return start - 1;
×
775
  }
776
}
777

778
//==============================================================================
779

780
void Region::add_precedence()
1,391✔
781
{
782
  int32_t current_op = 0;
1,391✔
783
  std::size_t current_dist = 0;
1,391✔
784

785
  for (int64_t i = 0; i < expression_.size(); i++) {
27,792✔
786
    int32_t token = expression_[i];
26,401✔
787

788
    if (token == OP_UNION || token == OP_INTERSECTION) {
26,401✔
789
      if (current_op == 0) {
10,235✔
790
        // Set the current operator if is hasn't been set
791
        current_op = token;
3,741✔
792
        current_dist = i;
3,741✔
793
      } else if (token != current_op) {
6,494✔
794
        // If the current operator doesn't match the token, add parenthesis to
795
        // assert precedence
796
        if (current_op == OP_INTERSECTION) {
32✔
797
          i = add_parentheses(current_dist);
16✔
798
        } else {
799
          i = add_parentheses(i);
16✔
800
        }
801
        current_op = 0;
32✔
802
        current_dist = 0;
32✔
803
      }
804
    } else if (token > OP_COMPLEMENT) {
16,166✔
805
      // If the token is a parenthesis reset the current operator
806
      current_op = 0;
4,572✔
807
      current_dist = 0;
4,572✔
808
    }
809
  }
810
}
1,391✔
811

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

818
vector<int32_t> Region::generate_postfix(int32_t cell_id) const
152✔
819
{
820
  vector<int32_t> rpn;
152✔
821
  vector<int32_t> stack;
152✔
822

823
  for (int32_t token : expression_) {
3,420✔
824
    if (token < OP_UNION) {
3,268✔
825
      // If token is not an operator, add it to output
826
      rpn.push_back(token);
1,368✔
827
    } else if (token < OP_RIGHT_PAREN) {
1,900✔
828
      // Regular operators union, intersection, complement
829
      while (stack.size() > 0) {
1,938✔
830
        int32_t op = stack.back();
1,596✔
831

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

847
      stack.push_back(token);
1,216✔
848

849
    } else if (token == OP_LEFT_PAREN) {
684✔
850
      // If the token is a left parenthesis, push it onto the stack
851
      stack.push_back(token);
342✔
852

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

868
      // Pop the left parenthesis.
869
      stack.pop_back();
342✔
870
    }
871
  }
872

873
  while (stack.size() > 0) {
304✔
874
    int32_t op = stack.back();
152✔
875

876
    // If the operator is a parenthesis it is mismatched.
877
    if (op >= OP_RIGHT_PAREN) {
152✔
UNCOV
878
      fatal_error(fmt::format(
×
879
        "Mismatched parentheses in region specification for cell {}", cell_id));
880
    }
881

882
    rpn.push_back(stack.back());
152✔
883
    stack.pop_back();
152✔
884
  }
885

886
  return rpn;
304✔
887
}
152✔
888

889
//==============================================================================
890

891
std::string Region::str() const
26,596✔
892
{
893
  std::stringstream region_spec {};
26,596✔
894
  if (!expression_.empty()) {
26,596✔
895
    for (int32_t token : expression_) {
85,723✔
896
      if (token == OP_LEFT_PAREN) {
67,145✔
897
        region_spec << " (";
2,156✔
898
      } else if (token == OP_RIGHT_PAREN) {
64,989✔
899
        region_spec << " )";
2,156✔
900
      } else if (token == OP_COMPLEMENT) {
62,833✔
UNCOV
901
        region_spec << " ~";
×
902
      } else if (token == OP_INTERSECTION) {
62,833✔
903
      } else if (token == OP_UNION) {
57,815✔
904
        region_spec << " |";
4,515✔
905
      } else {
906
        // Note the off-by-one indexing
907
        auto surf_id = model::surfaces[abs(token) - 1]->id_;
53,300✔
908
        region_spec << " " << ((token > 0) ? surf_id : -surf_id);
53,300✔
909
      }
910
    }
911
  }
912
  return region_spec.str();
53,192✔
913
}
26,596✔
914

915
//==============================================================================
916

917
std::pair<double, int32_t> Region::distance(
2,147,483,647✔
918
  Position r, Direction u, int32_t on_surface) const
919
{
920
  double min_dist {INFTY};
2,147,483,647✔
921
  int32_t i_surf {std::numeric_limits<int32_t>::max()};
2,147,483,647✔
922

923
  for (int32_t token : expression_) {
2,147,483,647✔
924
    // Ignore this token if it corresponds to an operator rather than a region.
925
    if (token >= OP_UNION)
2,147,483,647✔
926
      continue;
169,611,236✔
927

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

933
    // Check if this distance is the new minimum.
934
    if (d < min_dist) {
2,147,483,647✔
935
      if (min_dist - d >= FP_PRECISION * min_dist) {
2,147,483,647✔
936
        min_dist = d;
2,147,483,647✔
937
        i_surf = -token;
2,147,483,647✔
938
      }
939
    }
940
  }
941

942
  return {min_dist, i_surf};
2,147,483,647✔
943
}
944

945
//==============================================================================
946

947
bool Region::contains(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
948
{
949
  if (simple_) {
2,147,483,647✔
950
    return contains_simple(r, u, on_surface);
2,147,483,647✔
951
  } else {
952
    return contains_complex(r, u, on_surface);
6,716,601✔
953
  }
954
}
955

956
//==============================================================================
957

958
bool Region::contains_simple(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
959
{
960
  for (int32_t token : expression_) {
2,147,483,647✔
961
    // Assume that no tokens are operators. Evaluate the sense of particle with
962
    // respect to the surface and see if the token matches the sense. If the
963
    // particle's surface attribute is set and matches the token, that
964
    // overrides the determination based on sense().
965
    if (token == on_surface) {
2,147,483,647✔
966
    } else if (-token == on_surface) {
2,147,483,647✔
967
      return false;
975,826,115✔
968
    } else {
969
      // Note the off-by-one indexing
970
      bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
2,147,483,647✔
971
      if (sense != (token > 0)) {
2,147,483,647✔
972
        return false;
828,338,540✔
973
      }
974
    }
975
  }
976
  return true;
2,147,483,647✔
977
}
978

979
//==============================================================================
980

981
bool Region::contains_complex(Position r, Direction u, int32_t on_surface) const
6,716,601✔
982
{
983
  bool in_cell = true;
6,716,601✔
984
  int total_depth = 0;
6,716,601✔
985

986
  // For each token
987
  for (auto it = expression_.begin(); it != expression_.end(); it++) {
106,381,386✔
988
    int32_t token = *it;
101,546,152✔
989

990
    // If the token is a surface evaluate the sense
991
    // If the token is a union or intersection check to
992
    // short circuit
993
    if (token < OP_UNION) {
101,546,152✔
994
      if (token == on_surface) {
44,386,262✔
995
        in_cell = true;
3,307,317✔
996
      } else if (-token == on_surface) {
41,078,945✔
997
        in_cell = false;
671,057✔
998
      } else {
999
        // Note the off-by-one indexing
1000
        bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
40,407,888✔
1001
        in_cell = (sense == (token > 0));
40,407,888✔
1002
      }
1003
    } else if ((token == OP_UNION && in_cell == true) ||
57,159,890✔
1004
               (token == OP_INTERSECTION && in_cell == false)) {
27,904,324✔
1005
      // If the total depth is zero return
1006
      if (total_depth == 0) {
6,252,087✔
1007
        return in_cell;
1,881,367✔
1008
      }
1009

1010
      total_depth--;
4,370,720✔
1011

1012
      // While the iterator is within the bounds of the vector
1013
      int depth = 1;
4,370,720✔
1014
      do {
1015
        // Get next token
1016
        it++;
27,609,220✔
1017
        int32_t next_token = *it;
27,609,220✔
1018

1019
        // If the token is an a parenthesis
1020
        if (next_token > OP_COMPLEMENT) {
27,609,220✔
1021
          // Adjust depth accordingly
1022
          if (next_token == OP_RIGHT_PAREN) {
4,557,164✔
1023
            depth--;
4,463,942✔
1024
          } else {
1025
            depth++;
93,222✔
1026
          }
1027
        }
1028
      } while (depth > 0);
27,609,220✔
1029
    } else if (token == OP_LEFT_PAREN) {
55,278,523✔
1030
      total_depth++;
8,804,431✔
1031
    } else if (token == OP_RIGHT_PAREN) {
42,103,372✔
1032
      total_depth--;
4,433,711✔
1033
    }
1034
  }
1035
  return in_cell;
4,835,234✔
1036
}
1037

1038
//==============================================================================
1039

1040
BoundingBox Region::bounding_box(int32_t cell_id) const
226✔
1041
{
1042
  if (simple_) {
226✔
1043
    return bounding_box_simple();
74✔
1044
  } else {
1045
    auto postfix = generate_postfix(cell_id);
152✔
1046
    return bounding_box_complex(postfix);
152✔
1047
  }
152✔
1048
}
1049

1050
//==============================================================================
1051

1052
BoundingBox Region::bounding_box_simple() const
74✔
1053
{
1054
  BoundingBox bbox;
74✔
1055
  for (int32_t token : expression_) {
322✔
1056
    bbox &= model::surfaces[abs(token) - 1]->bounding_box(token > 0);
248✔
1057
  }
1058
  return bbox;
74✔
1059
}
1060

1061
//==============================================================================
1062

1063
BoundingBox Region::bounding_box_complex(vector<int32_t> postfix) const
152✔
1064
{
1065
  vector<BoundingBox> stack(postfix.size());
152✔
1066
  int i_stack = -1;
152✔
1067

1068
  for (auto& token : postfix) {
2,736✔
1069
    if (token == OP_UNION) {
2,584✔
1070
      stack[i_stack - 1] = stack[i_stack - 1] | stack[i_stack];
532✔
1071
      i_stack--;
532✔
1072
    } else if (token == OP_INTERSECTION) {
2,052✔
1073
      stack[i_stack - 1] = stack[i_stack - 1] & stack[i_stack];
684✔
1074
      i_stack--;
684✔
1075
    } else {
1076
      i_stack++;
1,368✔
1077
      stack[i_stack] = model::surfaces[abs(token) - 1]->bounding_box(token > 0);
1,368✔
1078
    }
1079
  }
1080

1081
  assert(i_stack == 0);
124✔
1082
  return stack.front();
304✔
1083
}
152✔
1084

1085
//==============================================================================
1086

1087
vector<int32_t> Region::surfaces() const
5,389✔
1088
{
1089
  if (simple_) {
5,389✔
1090
    return expression_;
5,389✔
1091
  }
1092

UNCOV
1093
  vector<int32_t> surfaces = expression_;
×
1094

1095
  auto it = std::find_if(surfaces.begin(), surfaces.end(),
×
UNCOV
1096
    [&](const auto& value) { return value >= OP_UNION; });
×
1097

1098
  while (it != surfaces.end()) {
×
UNCOV
1099
    surfaces.erase(it);
×
1100

UNCOV
1101
    it = std::find_if(surfaces.begin(), surfaces.end(),
×
UNCOV
1102
      [&](const auto& value) { return value >= OP_UNION; });
×
1103
  }
1104

UNCOV
1105
  return surfaces;
×
1106
}
1107

1108
//==============================================================================
1109
// Non-method functions
1110
//==============================================================================
1111

1112
void read_cells(pugi::xml_node node)
7,495✔
1113
{
1114
  // Count the number of cells.
1115
  int n_cells = 0;
7,495✔
1116
  for (pugi::xml_node cell_node : node.children("cell")) {
40,684✔
1117
    n_cells++;
33,189✔
1118
  }
1119

1120
  // Loop over XML cell elements and populate the array.
1121
  model::cells.reserve(n_cells);
7,495✔
1122
  for (pugi::xml_node cell_node : node.children("cell")) {
40,684✔
1123
    model::cells.push_back(make_unique<CSGCell>(cell_node));
33,189✔
1124
  }
1125

1126
  // Fill the cell map.
1127
  for (int i = 0; i < model::cells.size(); i++) {
40,684✔
1128
    int32_t id = model::cells[i]->id_;
33,189✔
1129
    auto search = model::cell_map.find(id);
33,189✔
1130
    if (search == model::cell_map.end()) {
33,189✔
1131
      model::cell_map[id] = i;
33,189✔
1132
    } else {
UNCOV
1133
      fatal_error(
×
UNCOV
1134
        fmt::format("Two or more cells use the same unique ID: {}", id));
×
1135
    }
1136
  }
1137

1138
  read_dagmc_universes(node);
7,495✔
1139

1140
  populate_universes();
7,493✔
1141

1142
  // Allocate the cell overlap count if necessary.
1143
  if (settings::check_overlaps) {
7,493✔
1144
    model::overlap_check_count.resize(model::cells.size(), 0);
276✔
1145
  }
1146

1147
  if (model::cells.size() == 0) {
7,493✔
UNCOV
1148
    fatal_error("No cells were found in the geometry.xml file");
×
1149
  }
1150
}
7,493✔
1151

1152
void populate_universes()
7,495✔
1153
{
1154
  // Used to map universe index to the index of an implicit complement cell for
1155
  // DAGMC universes
1156
  std::unordered_map<int, int> implicit_comp_cells;
7,495✔
1157

1158
  // Populate the Universe vector and map.
1159
  for (int index_cell = 0; index_cell < model::cells.size(); index_cell++) {
41,022✔
1160
    int32_t uid = model::cells[index_cell]->universe_;
33,527✔
1161
    auto it = model::universe_map.find(uid);
33,527✔
1162
    if (it == model::universe_map.end()) {
33,527✔
1163
      model::universes.push_back(make_unique<Universe>());
18,818✔
1164
      model::universes.back()->id_ = uid;
18,818✔
1165
      model::universes.back()->cells_.push_back(index_cell);
18,818✔
1166
      model::universe_map[uid] = model::universes.size() - 1;
18,818✔
1167
    } else {
1168
#ifdef OPENMC_DAGMC_ENABLED
1169
      // Skip implicit complement cells for now
1170
      Universe* univ = model::universes[it->second].get();
1,984✔
1171
      DAGUniverse* dag_univ = dynamic_cast<DAGUniverse*>(univ);
1,984✔
1172
      if (dag_univ && (dag_univ->implicit_complement_idx() == index_cell)) {
1,984✔
1173
        implicit_comp_cells[it->second] = index_cell;
69✔
1174
        continue;
69✔
1175
      }
1176
#endif
1177

1178
      model::universes[it->second]->cells_.push_back(index_cell);
14,640✔
1179
    }
1180
  }
1181

1182
  // Add DAGUniverse implicit complement cells last
1183
  for (const auto& it : implicit_comp_cells) {
7,564✔
1184
    int index_univ = it.first;
69✔
1185
    int index_cell = it.second;
69✔
1186
    model::universes[index_univ]->cells_.push_back(index_cell);
69✔
1187
  }
1188

1189
  model::universes.shrink_to_fit();
7,495✔
1190
}
7,495✔
1191

1192
//==============================================================================
1193
// C-API functions
1194
//==============================================================================
1195

1196
extern "C" int openmc_cell_get_fill(
416✔
1197
  int32_t index, int* type, int32_t** indices, int32_t* n)
1198
{
1199
  if (index >= 0 && index < model::cells.size()) {
416✔
1200
    Cell& c {*model::cells[index]};
416✔
1201
    *type = static_cast<int>(c.type_);
416✔
1202
    if (c.type_ == Fill::MATERIAL) {
416✔
1203
      *indices = c.material_.data();
416✔
1204
      *n = c.material_.size();
416✔
1205
    } else {
UNCOV
1206
      *indices = &c.fill_;
×
UNCOV
1207
      *n = 1;
×
1208
    }
1209
  } else {
UNCOV
1210
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1211
    return OPENMC_E_OUT_OF_BOUNDS;
×
1212
  }
1213
  return 0;
416✔
1214
}
1215

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

1249
extern "C" int openmc_cell_set_temperature(
96✔
1250
  int32_t index, double T, const int32_t* instance, bool set_contained)
1251
{
1252
  if (index < 0 || index >= model::cells.size()) {
96✔
1253
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
1254
    return OPENMC_E_OUT_OF_BOUNDS;
×
1255
  }
1256

1257
  int32_t instance_index = instance ? *instance : -1;
96✔
1258
  try {
1259
    model::cells[index]->set_temperature(T, instance_index, set_contained);
96✔
1260
  } catch (const std::exception& e) {
×
1261
    set_errmsg(e.what());
×
1262
    return OPENMC_E_UNASSIGNED;
×
1263
  }
×
1264
  return 0;
96✔
1265
}
1266

1267
extern "C" int openmc_cell_set_density(
84✔
1268
  int32_t index, double rho, const int32_t* instance, bool set_contained)
1269
{
1270
  if (index < 0 || index >= model::cells.size()) {
84✔
NEW
1271
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
NEW
1272
    return OPENMC_E_OUT_OF_BOUNDS;
×
1273
  }
1274

1275
  int32_t instance_index = instance ? *instance : -1;
84✔
1276
  try {
1277
    model::cells[index]->set_density(rho, instance_index, set_contained);
84✔
NEW
UNCOV
1278
  } catch (const std::exception& e) {
×
NEW
UNCOV
1279
    set_errmsg(e.what());
×
NEW
UNCOV
1280
    return OPENMC_E_UNASSIGNED;
×
NEW
UNCOV
1281
  }
×
1282
  return 0;
84✔
1283
}
1284

1285
extern "C" int openmc_cell_get_temperature(
105✔
1286
  int32_t index, const int32_t* instance, double* T)
1287
{
1288
  if (index < 0 || index >= model::cells.size()) {
105✔
1289
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
1290
    return OPENMC_E_OUT_OF_BOUNDS;
×
1291
  }
1292

1293
  int32_t instance_index = instance ? *instance : -1;
105✔
1294
  try {
1295
    *T = model::cells[index]->temperature(instance_index);
105✔
1296
  } catch (const std::exception& e) {
×
1297
    set_errmsg(e.what());
×
1298
    return OPENMC_E_UNASSIGNED;
×
1299
  }
×
1300
  return 0;
105✔
1301
}
1302

1303
extern "C" int openmc_cell_get_density(
72✔
1304
  int32_t index, const int32_t* instance, double* rho)
1305
{
1306
  if (index < 0 || index >= model::cells.size()) {
72✔
NEW
1307
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
NEW
1308
    return OPENMC_E_OUT_OF_BOUNDS;
×
1309
  }
1310

1311
  int32_t instance_index = instance ? *instance : -1;
72✔
1312
  try {
1313
    if (model::cells[index]->type_ != Fill::MATERIAL) {
72✔
NEW
1314
      fatal_error(
×
NEW
1315
        fmt::format("Cell {}, instance {} is not filled with a material.",
×
NEW
1316
          model::cells[index]->id_, instance_index));
×
1317
    }
1318

1319
    int32_t mat_index = model::cells[index]->material(instance_index);
72✔
1320
    if (mat_index == MATERIAL_VOID) {
72✔
NEW
1321
      *rho = 0.0;
×
1322
    } else {
1323
      *rho = model::cells[index]->density_mult(instance_index) *
144✔
1324
             model::materials[mat_index]->density_gpcc();
72✔
1325
    }
NEW
UNCOV
1326
  } catch (const std::exception& e) {
×
NEW
UNCOV
1327
    set_errmsg(e.what());
×
NEW
UNCOV
1328
    return OPENMC_E_UNASSIGNED;
×
NEW
UNCOV
1329
  }
×
1330
  return 0;
72✔
1331
}
1332

1333
//! Get the bounding box of a cell
1334
extern "C" int openmc_cell_bounding_box(
190✔
1335
  const int32_t index, double* llc, double* urc)
1336
{
1337

1338
  BoundingBox bbox;
190✔
1339

1340
  const auto& c = model::cells[index];
190✔
1341
  bbox = c->bounding_box();
190✔
1342

1343
  // set lower left corner values
1344
  llc[0] = bbox.xmin;
190✔
1345
  llc[1] = bbox.ymin;
190✔
1346
  llc[2] = bbox.zmin;
190✔
1347

1348
  // set upper right corner values
1349
  urc[0] = bbox.xmax;
190✔
1350
  urc[1] = bbox.ymax;
190✔
1351
  urc[2] = bbox.zmax;
190✔
1352

1353
  return 0;
190✔
1354
}
1355

1356
//! Get the name of a cell
1357
extern "C" int openmc_cell_get_name(int32_t index, const char** name)
24✔
1358
{
1359
  if (index < 0 || index >= model::cells.size()) {
24✔
UNCOV
1360
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1361
    return OPENMC_E_OUT_OF_BOUNDS;
×
1362
  }
1363

1364
  *name = model::cells[index]->name().data();
24✔
1365

1366
  return 0;
24✔
1367
}
1368

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

1377
  model::cells[index]->set_name(name);
12✔
1378

1379
  return 0;
12✔
1380
}
1381

1382
//==============================================================================
1383
//! Define a containing (parent) cell
1384
//==============================================================================
1385

1386
//! Used to locate a universe fill in the geometry
1387
struct ParentCell {
1388
  bool operator==(const ParentCell& other) const
144✔
1389
  {
1390
    return cell_index == other.cell_index &&
288✔
1391
           lattice_index == other.lattice_index;
288✔
1392
  }
1393

1394
  bool operator<(const ParentCell& other) const
1395
  {
1396
    return cell_index < other.cell_index ||
1397
           (cell_index == other.cell_index &&
1398
             lattice_index < other.lattice_index);
1399
  }
1400

1401
  int64_t cell_index;
1402
  int64_t lattice_index;
1403
};
1404

1405
//! Structure used to insert ParentCell into hashed STL data structures
1406
struct ParentCellHash {
1407
  std::size_t operator()(const ParentCell& p) const
673✔
1408
  {
1409
    return 4096 * p.cell_index + p.lattice_index;
673✔
1410
  }
1411
};
1412

1413
//! Used to manage a traversal stack when locating parent cells of a cell
1414
//! instance in the model
1415
struct ParentCellStack {
1416

1417
  //! push method that adds to the parent_cells visited cells for this search
1418
  //! universe
1419
  void push(int32_t search_universe, const ParentCell& pc)
112✔
1420
  {
1421
    parent_cells_.push_back(pc);
112✔
1422
    // add parent cell to the set of cells we've visited for this search
1423
    // universe
1424
    visited_cells_[search_universe].insert(pc);
112✔
1425
  }
112✔
1426

1427
  //! removes the last parent_cell and clears the visited cells for the popped
1428
  //! cell's universe
1429
  void pop()
80✔
1430
  {
1431
    visited_cells_[this->current_univ()].clear();
80✔
1432
    parent_cells_.pop_back();
80✔
1433
  }
80✔
1434

1435
  //! checks whether or not the parent cell has been visited already for this
1436
  //! search universe
1437
  bool visited(int32_t search_universe, const ParentCell& parent_cell)
561✔
1438
  {
1439
    return visited_cells_[search_universe].count(parent_cell) != 0;
561✔
1440
  }
1441

1442
  //! return the next universe to search for a parent cell
1443
  int32_t current_univ() const
80✔
1444
  {
1445
    return model::cells[parent_cells_.back().cell_index]->universe_;
80✔
1446
  }
1447

1448
  //! indicates whether nor not parent cells are present on the stack
1449
  bool empty() const { return parent_cells_.empty(); }
80✔
1450

1451
  //! compute an instance for the provided distribcell index
1452
  int32_t compute_instance(int32_t distribcell_index) const
193✔
1453
  {
1454
    if (distribcell_index == C_NONE)
193✔
1455
      return 0;
65✔
1456

1457
    int32_t instance = 0;
128✔
1458
    for (const auto& parent_cell : this->parent_cells_) {
240✔
1459
      auto& cell = model::cells[parent_cell.cell_index];
112✔
1460
      if (cell->type_ == Fill::UNIVERSE) {
112✔
UNCOV
1461
        instance += cell->offset_[distribcell_index];
×
1462
      } else if (cell->type_ == Fill::LATTICE) {
112✔
1463
        auto& lattice = model::lattices[cell->fill_];
112✔
1464
        instance +=
112✔
1465
          lattice->offset(distribcell_index, parent_cell.lattice_index);
112✔
1466
      }
1467
    }
1468
    return instance;
128✔
1469
  }
1470

1471
  // Accessors
1472
  vector<ParentCell>& parent_cells() { return parent_cells_; }
339✔
1473
  const vector<ParentCell>& parent_cells() const { return parent_cells_; }
1474

1475
  // Data Members
1476
  vector<ParentCell> parent_cells_;
1477
  std::unordered_map<int32_t, std::unordered_set<ParentCell, ParentCellHash>>
1478
    visited_cells_;
1479
};
1480

1481
vector<ParentCell> Cell::find_parent_cells(
×
1482
  int32_t instance, const Position& r) const
1483
{
1484

1485
  // create a temporary particle
1486
  GeometryState dummy_particle {};
×
UNCOV
1487
  dummy_particle.r() = r;
×
UNCOV
1488
  dummy_particle.u() = {0., 0., 1.};
×
1489

1490
  return find_parent_cells(instance, dummy_particle);
×
1491
}
1492

UNCOV
1493
vector<ParentCell> Cell::find_parent_cells(
×
1494
  int32_t instance, GeometryState& p) const
1495
{
1496
  // look up the particle's location
1497
  exhaustive_find_cell(p);
×
1498
  const auto& coords = p.coord();
×
1499

1500
  // build a parent cell stack from the particle coordinates
1501
  ParentCellStack stack;
×
1502
  bool cell_found = false;
×
1503
  for (auto it = coords.begin(); it != coords.end(); it++) {
×
UNCOV
1504
    const auto& coord = *it;
×
UNCOV
1505
    const auto& cell = model::cells[coord.cell()];
×
1506
    // if the cell at this level matches the current cell, stop adding to the
1507
    // stack
1508
    if (coord.cell() == model::cell_map[this->id_]) {
×
1509
      cell_found = true;
×
1510
      break;
×
1511
    }
1512

1513
    // if filled with a lattice, get the lattice index from the next
1514
    // level in the coordinates to push to the stack
UNCOV
1515
    int lattice_idx = C_NONE;
×
UNCOV
1516
    if (cell->type_ == Fill::LATTICE) {
×
UNCOV
1517
      const auto& next_coord = *(it + 1);
×
UNCOV
1518
      lattice_idx = model::lattices[next_coord.lattice()]->get_flat_index(
×
1519
        next_coord.lattice_index());
1520
    }
1521
    stack.push(coord.universe(), {coord.cell(), lattice_idx});
×
1522
  }
1523

1524
  // if this loop finished because the cell was found and
1525
  // the instance matches the one requested in the call
1526
  // we have the correct path and can return the stack
UNCOV
1527
  if (cell_found &&
×
UNCOV
1528
      stack.compute_instance(this->distribcell_index_) == instance) {
×
UNCOV
1529
    return stack.parent_cells();
×
1530
  }
1531

1532
  // fall back on an exhaustive search for the cell's parents
UNCOV
1533
  return exhaustive_find_parent_cells(instance);
×
1534
}
1535

1536
vector<ParentCell> Cell::exhaustive_find_parent_cells(int32_t instance) const
113✔
1537
{
1538
  ParentCellStack stack;
113✔
1539
  // start with this cell's universe
1540
  int32_t prev_univ_idx;
1541
  int32_t univ_idx = this->universe_;
113✔
1542

1543
  while (true) {
1544
    const auto& univ = model::universes[univ_idx];
193✔
1545
    prev_univ_idx = univ_idx;
193✔
1546

1547
    // search for a cell that is filled w/ this universe
1548
    for (const auto& cell : model::cells) {
1,236✔
1549
      // if this is a material-filled cell, move on
1550
      if (cell->type_ == Fill::MATERIAL)
1,155✔
1551
        continue;
642✔
1552

1553
      if (cell->type_ == Fill::UNIVERSE) {
513✔
1554
        // if this is in the set of cells previously visited for this universe,
1555
        // move on
1556
        if (stack.visited(univ_idx, {model::cell_map[cell->id_], C_NONE}))
305✔
UNCOV
1557
          continue;
×
1558

1559
        // if this cell contains the universe we're searching for, add it to the
1560
        // stack
1561
        if (cell->fill_ == univ_idx) {
305✔
UNCOV
1562
          stack.push(univ_idx, {model::cell_map[cell->id_], C_NONE});
×
UNCOV
1563
          univ_idx = cell->universe_;
×
1564
        }
1565
      } else if (cell->type_ == Fill::LATTICE) {
208✔
1566
        // retrieve the lattice and lattice universes
1567
        const auto& lattice = model::lattices[cell->fill_];
208✔
1568
        const auto& lattice_univs = lattice->universes_;
208✔
1569

1570
        // start search for universe
1571
        auto lat_it = lattice_univs.begin();
208✔
1572
        while (true) {
1573
          // find the next lattice cell with this universe
1574
          lat_it = std::find(lat_it, lattice_univs.end(), univ_idx);
352✔
1575
          if (lat_it == lattice_univs.end())
352✔
1576
            break;
96✔
1577

1578
          int lattice_idx = lat_it - lattice_univs.begin();
256✔
1579

1580
          // move iterator forward one to avoid finding the same entry
1581
          lat_it++;
256✔
1582
          if (stack.visited(
256✔
1583
                univ_idx, {model::cell_map[cell->id_], lattice_idx}))
256✔
1584
            continue;
144✔
1585

1586
          // add this cell and lattice index to the stack and exit loop
1587
          stack.push(univ_idx, {model::cell_map[cell->id_], lattice_idx});
112✔
1588
          univ_idx = cell->universe_;
112✔
1589
          break;
112✔
1590
        }
144✔
1591
      }
1592
      // if we've updated the universe, break
1593
      if (prev_univ_idx != univ_idx)
513✔
1594
        break;
112✔
1595
    } // end cell loop search for universe
1596

1597
    // if we're at the top of the geometry and the instance matches, we're done
1598
    if (univ_idx == model::root_universe &&
386✔
1599
        stack.compute_instance(this->distribcell_index_) == instance)
193✔
1600
      break;
113✔
1601

1602
    // if there is no match on the original cell's universe, report an error
1603
    if (univ_idx == this->universe_) {
80✔
UNCOV
1604
      fatal_error(
×
UNCOV
1605
        fmt::format("Could not find the parent cells for cell {}, instance {}.",
×
UNCOV
1606
          this->id_, instance));
×
1607
    }
1608

1609
    // if we don't find a suitable update, adjust the stack and continue
1610
    if (univ_idx == model::root_universe || univ_idx == prev_univ_idx) {
80✔
1611
      stack.pop();
80✔
1612
      univ_idx = stack.empty() ? this->universe_ : stack.current_univ();
80✔
1613
    }
1614

1615
  } // end while
80✔
1616

1617
  // reverse the stack so the highest cell comes first
1618
  std::reverse(stack.parent_cells().begin(), stack.parent_cells().end());
113✔
1619
  return stack.parent_cells();
226✔
1620
}
113✔
1621

1622
std::unordered_map<int32_t, vector<int32_t>> Cell::get_contained_cells(
161✔
1623
  int32_t instance, Position* hint) const
1624
{
1625
  std::unordered_map<int32_t, vector<int32_t>> contained_cells;
161✔
1626

1627
  // if this is a material-filled cell it has no contained cells
1628
  if (this->type_ == Fill::MATERIAL)
161✔
1629
    return contained_cells;
48✔
1630

1631
  // find the pathway through the geometry to this cell
1632
  vector<ParentCell> parent_cells;
113✔
1633

1634
  // if a positional hint is provided, attempt to do a fast lookup
1635
  // of the parent cells
1636
  parent_cells = hint ? find_parent_cells(instance, *hint)
226✔
1637
                      : exhaustive_find_parent_cells(instance);
113✔
1638

1639
  // if this cell is filled w/ a material, it contains no other cells
1640
  if (type_ != Fill::MATERIAL) {
113✔
1641
    this->get_contained_cells_inner(contained_cells, parent_cells);
113✔
1642
  }
1643

1644
  return contained_cells;
113✔
1645
}
113✔
1646

1647
//! Get all cells within this cell
1648
void Cell::get_contained_cells_inner(
87,763✔
1649
  std::unordered_map<int32_t, vector<int32_t>>& contained_cells,
1650
  vector<ParentCell>& parent_cells) const
1651
{
1652

1653
  // filled by material, determine instance based on parent cells
1654
  if (type_ == Fill::MATERIAL) {
87,763✔
1655
    int instance = 0;
87,138✔
1656
    if (this->distribcell_index_ >= 0) {
87,138✔
1657
      for (auto& parent_cell : parent_cells) {
261,412✔
1658
        auto& cell = model::cells[parent_cell.cell_index];
174,274✔
1659
        if (cell->type_ == Fill::UNIVERSE) {
174,274✔
1660
          instance += cell->offset_[distribcell_index_];
85,538✔
1661
        } else if (cell->type_ == Fill::LATTICE) {
88,736✔
1662
          auto& lattice = model::lattices[cell->fill_];
88,736✔
1663
          instance += lattice->offset(
177,472✔
1664
            this->distribcell_index_, parent_cell.lattice_index);
88,736✔
1665
        }
1666
      }
1667
    }
1668
    // add entry to contained cells
1669
    contained_cells[model::cell_map[id_]].push_back(instance);
87,138✔
1670
    // filled with universe, add the containing cell to the parent cells
1671
    // and recurse
1672
  } else if (type_ == Fill::UNIVERSE) {
625✔
1673
    parent_cells.push_back({model::cell_map[id_], -1});
529✔
1674
    auto& univ = model::universes[fill_];
529✔
1675
    for (auto cell_index : univ->cells_) {
3,171✔
1676
      auto& cell = model::cells[cell_index];
2,642✔
1677
      cell->get_contained_cells_inner(contained_cells, parent_cells);
2,642✔
1678
    }
1679
    parent_cells.pop_back();
529✔
1680
    // filled with a lattice, visit each universe in the lattice
1681
    // with a recursive call to collect the cell instances
1682
  } else if (type_ == Fill::LATTICE) {
96✔
1683
    auto& lattice = model::lattices[fill_];
96✔
1684
    for (auto i = lattice->begin(); i != lattice->end(); ++i) {
84,768✔
1685
      auto& univ = model::universes[*i];
84,672✔
1686
      parent_cells.push_back({model::cell_map[id_], i.indx_});
84,672✔
1687
      for (auto cell_index : univ->cells_) {
169,680✔
1688
        auto& cell = model::cells[cell_index];
85,008✔
1689
        cell->get_contained_cells_inner(contained_cells, parent_cells);
85,008✔
1690
      }
1691
      parent_cells.pop_back();
84,672✔
1692
    }
1693
  }
1694
}
87,763✔
1695

1696
//! Return the index in the cells array of a cell with a given ID
1697
extern "C" int openmc_get_cell_index(int32_t id, int32_t* index)
756✔
1698
{
1699
  auto it = model::cell_map.find(id);
756✔
1700
  if (it != model::cell_map.end()) {
756✔
1701
    *index = it->second;
744✔
1702
    return 0;
744✔
1703
  } else {
1704
    set_errmsg("No cell exists with ID=" + std::to_string(id) + ".");
12✔
1705
    return OPENMC_E_INVALID_ID;
12✔
1706
  }
1707
}
1708

1709
//! Return the ID of a cell
1710
extern "C" int openmc_cell_get_id(int32_t index, int32_t* id)
601,856✔
1711
{
1712
  if (index >= 0 && index < model::cells.size()) {
601,856✔
1713
    *id = model::cells[index]->id_;
601,856✔
1714
    return 0;
601,856✔
1715
  } else {
UNCOV
1716
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1717
    return OPENMC_E_OUT_OF_BOUNDS;
×
1718
  }
1719
}
1720

1721
//! Set the ID of a cell
1722
extern "C" int openmc_cell_set_id(int32_t index, int32_t id)
24✔
1723
{
1724
  if (index >= 0 && index < model::cells.size()) {
24✔
1725
    model::cells[index]->id_ = id;
24✔
1726
    model::cell_map[id] = index;
24✔
1727
    return 0;
24✔
1728
  } else {
UNCOV
1729
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1730
    return OPENMC_E_OUT_OF_BOUNDS;
×
1731
  }
1732
}
1733

1734
//! Return the translation vector of a cell
1735
extern "C" int openmc_cell_get_translation(int32_t index, double xyz[])
60✔
1736
{
1737
  if (index >= 0 && index < model::cells.size()) {
60✔
1738
    auto& cell = model::cells[index];
60✔
1739
    xyz[0] = cell->translation_.x;
60✔
1740
    xyz[1] = cell->translation_.y;
60✔
1741
    xyz[2] = cell->translation_.z;
60✔
1742
    return 0;
60✔
1743
  } else {
UNCOV
1744
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1745
    return OPENMC_E_OUT_OF_BOUNDS;
×
1746
  }
1747
}
1748

1749
//! Set the translation vector of a cell
1750
extern "C" int openmc_cell_set_translation(int32_t index, const double xyz[])
36✔
1751
{
1752
  if (index >= 0 && index < model::cells.size()) {
36✔
1753
    if (model::cells[index]->fill_ == C_NONE) {
36✔
1754
      set_errmsg(fmt::format("Cannot apply a translation to cell {}"
12✔
1755
                             " because it is not filled with another universe",
1756
        index));
1757
      return OPENMC_E_GEOMETRY;
12✔
1758
    }
1759
    model::cells[index]->translation_ = Position(xyz);
24✔
1760
    return 0;
24✔
1761
  } else {
UNCOV
1762
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1763
    return OPENMC_E_OUT_OF_BOUNDS;
×
1764
  }
1765
}
1766

1767
//! Return the rotation matrix of a cell
1768
extern "C" int openmc_cell_get_rotation(int32_t index, double rot[], size_t* n)
60✔
1769
{
1770
  if (index >= 0 && index < model::cells.size()) {
60✔
1771
    auto& cell = model::cells[index];
60✔
1772
    *n = cell->rotation_.size();
60✔
1773
    std::memcpy(rot, cell->rotation_.data(), *n * sizeof(cell->rotation_[0]));
60✔
1774
    return 0;
60✔
1775
  } else {
UNCOV
1776
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1777
    return OPENMC_E_OUT_OF_BOUNDS;
×
1778
  }
1779
}
1780

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

1801
//! Get the number of instances of the requested cell
1802
extern "C" int openmc_cell_get_num_instances(
12✔
1803
  int32_t index, int32_t* num_instances)
1804
{
1805
  if (index < 0 || index >= model::cells.size()) {
12✔
UNCOV
1806
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1807
    return OPENMC_E_OUT_OF_BOUNDS;
×
1808
  }
1809
  *num_instances = model::cells[index]->n_instances();
12✔
1810
  return 0;
12✔
1811
}
1812

1813
//! Extend the cells array by n elements
1814
extern "C" int openmc_extend_cells(
24✔
1815
  int32_t n, int32_t* index_start, int32_t* index_end)
1816
{
1817
  if (index_start)
24✔
1818
    *index_start = model::cells.size();
24✔
1819
  if (index_end)
24✔
UNCOV
1820
    *index_end = model::cells.size() + n - 1;
×
1821
  for (int32_t i = 0; i < n; i++) {
48✔
1822
    model::cells.push_back(make_unique<CSGCell>());
24✔
1823
  }
1824
  return 0;
24✔
1825
}
1826

1827
extern "C" int cells_size()
48✔
1828
{
1829
  return model::cells.size();
48✔
1830
}
1831

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