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

openmc-dev / openmc / 17448060282

03 Sep 2025 10:50PM UTC coverage: 85.19% (-0.02%) from 85.209%
17448060282

Pull #3546

github

web-flow
Merge d2b204976 into 591856472
Pull Request #3546: Add distributed cell density multipliers

148 of 187 new or added lines in 12 files covered. (79.14%)

159 existing lines in 8 files now uncovered.

53075 of 62302 relevant lines covered (85.19%)

40233512.28 hits per line

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

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

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

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

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

96
  if (instance >= 0) {
121✔
97
    double sqrtkT = sqrtkT_.size() == 1 ? sqrtkT_.at(0) : sqrtkT_.at(instance);
14✔
98
    return sqrtkT * sqrtkT / K_BOLTZMANN;
14✔
99
  } else {
100
    return sqrtkT_[0] * sqrtkT_[0] / K_BOLTZMANN;
107✔
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];
69✔
112
  }
113
}
114

115
void Cell::set_temperature(double T, int32_t instance, bool set_contained)
564✔
116
{
117
  if (settings::temperature_method == TemperatureMethod::INTERPOLATION) {
564✔
118
    if (T < (data::temperature_min - settings::temperature_tolerance)) {
×
119
      throw std::runtime_error {
×
120
        fmt::format("Temperature of {} K is below minimum temperature at "
×
121
                    "which data is available of {} K.",
122
          T, data::temperature_min)};
×
UNCOV
123
    } else if (T > (data::temperature_max + settings::temperature_tolerance)) {
×
UNCOV
124
      throw std::runtime_error {
×
UNCOV
125
        fmt::format("Temperature of {} K is above maximum temperature at "
×
126
                    "which data is available of {} K.",
127
          T, data::temperature_max)};
×
128
    }
129
  }
130

131
  if (type_ == Fill::MATERIAL) {
564✔
132
    if (instance >= 0) {
530✔
133
      // If temperature vector is not big enough, resize it first
134
      if (sqrtkT_.size() != n_instances())
437✔
135
        sqrtkT_.resize(n_instances(), sqrtkT_[0]);
51✔
136

137
      // Set temperature for the corresponding instance
138
      sqrtkT_.at(instance) = std::sqrt(K_BOLTZMANN * T);
437✔
139
    } else {
140
      // Set temperature for all instances
141
      for (auto& T_ : sqrtkT_) {
186✔
142
        T_ = std::sqrt(K_BOLTZMANN * T);
93✔
143
      }
144
    }
145
  } else {
146
    if (!set_contained) {
34✔
UNCOV
147
      throw std::runtime_error {
×
UNCOV
148
        fmt::format("Attempted to set the temperature of cell {} "
×
149
                    "which is not filled by a material.",
UNCOV
150
          id_)};
×
151
    }
152

153
    auto contained_cells = this->get_contained_cells(instance);
34✔
154
    for (const auto& entry : contained_cells) {
136✔
155
      auto& cell = model::cells[entry.first];
102✔
156
      assert(cell->type_ == Fill::MATERIAL);
84✔
157
      auto& instances = entry.second;
102✔
158
      for (auto instance : instances) {
357✔
159
        cell->set_temperature(T, instance);
255✔
160
      }
161
    }
162
  }
34✔
163
}
564✔
164

165
void Cell::set_density_mult(double rho, int32_t instance, bool set_contained)
162✔
166
{
167
  if (type_ != Fill::MATERIAL && !set_contained) {
162✔
NEW
UNCOV
168
    fatal_error(
×
NEW
UNCOV
169
      fmt::format("Attempted to set the density multiplier of cell {} "
×
170
                  "which is not filled by a material.",
NEW
UNCOV
171
        id_));
×
172
  }
173

174
  if (type_ == Fill::MATERIAL) {
162✔
175
    if (instance >= 0) {
145✔
176
      // If density multiplier vector is not big enough, resize it first
177
      if (rho_mult_.size() != n_instances())
65✔
178
        rho_mult_.resize(n_instances(), rho_mult_[0]);
51✔
179

180
      // Set density multiplier for the corresponding instance
181
      rho_mult_.at(instance) = rho;
65✔
182
    } else {
183
      // Set density multiplier for all instances
184
      for (auto& Rho_ : rho_mult_) {
160✔
185
        Rho_ = rho;
80✔
186
      }
187
    }
188
  } else {
189
    auto contained_cells = this->get_contained_cells(instance);
17✔
190
    for (const auto& entry : contained_cells) {
68✔
191
      auto& cell = model::cells[entry.first];
51✔
192
      assert(cell->type_ == Fill::MATERIAL);
42✔
193
      auto& instances = entry.second;
51✔
194
      for (auto instance : instances) {
102✔
195
        cell->set_density_mult(rho, instance);
51✔
196
      }
197
    }
198
  }
17✔
199
}
162✔
200

201
void Cell::export_properties_hdf5(hid_t group) const
193✔
202
{
203
  // Create a group for this cell.
204
  auto cell_group = create_group(group, fmt::format("cell {}", id_));
386✔
205

206
  // Write temperature in [K] for one or more cell instances
207
  vector<double> temps;
193✔
208
  for (auto sqrtkT_val : sqrtkT_)
358✔
209
    temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
165✔
210
  write_dataset(cell_group, "temperature", temps);
193✔
211

212
  // Write density multipliers for one or more cell instances
213
  write_dataset(cell_group, "density_mult", rho_mult_);
193✔
214

215
  close_group(cell_group);
193✔
216
}
193✔
217

218
void Cell::import_properties_hdf5(hid_t group)
224✔
219
{
220
  auto cell_group = open_group(group, fmt::format("cell {}", id_));
448✔
221

222
  // Read temperatures from file
223
  vector<double> temps;
224✔
224
  read_dataset(cell_group, "temperature", temps);
224✔
225

226
  // Ensure number of temperatures makes sense
227
  auto n_temps = temps.size();
224✔
228
  if (n_temps > 1 && n_temps != n_instances()) {
224✔
NEW
229
    fatal_error(fmt::format(
×
230
      "Number of temperatures for cell {} doesn't match number of instances",
231
      id_));
×
232
  }
233

234
  // Modify temperatures for the cell
235
  sqrtkT_.clear();
224✔
236
  sqrtkT_.resize(temps.size());
224✔
237
  for (int64_t i = 0; i < temps.size(); ++i) {
392✔
238
    this->set_temperature(temps[i], i);
168✔
239
  }
240

241
  // Read density multipliers
242
  if (object_exists(cell_group, "density_mult")) {
224✔
243
    read_dataset(cell_group, "density_mult", rho_mult_);
224✔
244

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

254
  close_group(cell_group);
224✔
255
}
224✔
256

257
void Cell::to_hdf5(hid_t cell_group) const
30,182✔
258
{
259

260
  // Create a group for this cell.
261
  auto group = create_group(cell_group, fmt::format("cell {}", id_));
60,364✔
262

263
  if (!name_.empty()) {
30,182✔
264
    write_string(group, "name", name_, false);
5,384✔
265
  }
266

267
  write_dataset(group, "universe", model::universes[universe_]->id_);
30,182✔
268

269
  to_hdf5_inner(group);
30,182✔
270

271
  // Write fill information.
272
  if (type_ == Fill::MATERIAL) {
30,182✔
273
    write_dataset(group, "fill_type", "material");
24,764✔
274
    std::vector<int32_t> mat_ids;
24,764✔
275
    for (auto i_mat : material_) {
51,229✔
276
      if (i_mat != MATERIAL_VOID) {
26,465✔
277
        mat_ids.push_back(model::materials[i_mat]->id_);
16,324✔
278
      } else {
279
        mat_ids.push_back(MATERIAL_VOID);
10,141✔
280
      }
281
    }
282
    if (mat_ids.size() == 1) {
24,764✔
283
      write_dataset(group, "material", mat_ids[0]);
24,517✔
284
    } else {
285
      write_dataset(group, "material", mat_ids);
247✔
286
    }
287

288
    std::vector<double> temps;
24,764✔
289
    for (auto sqrtkT_val : sqrtkT_)
51,355✔
290
      temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
26,591✔
291
    write_dataset(group, "temperature", temps);
24,764✔
292

293
    write_dataset(group, "density_mult", rho_mult_);
24,764✔
294

295
  } else if (type_ == Fill::UNIVERSE) {
30,182✔
296
    write_dataset(group, "fill_type", "universe");
3,872✔
297
    write_dataset(group, "fill", model::universes[fill_]->id_);
3,872✔
298
    if (translation_ != Position(0, 0, 0)) {
3,872✔
299
      write_dataset(group, "translation", translation_);
2,004✔
300
    }
301
    if (!rotation_.empty()) {
3,872✔
302
      if (rotation_.size() == 12) {
288✔
303
        std::array<double, 3> rot {rotation_[9], rotation_[10], rotation_[11]};
288✔
304
        write_dataset(group, "rotation", rot);
288✔
305
      } else {
UNCOV
306
        write_dataset(group, "rotation", rotation_);
×
307
      }
308
    }
309

310
  } else if (type_ == Fill::LATTICE) {
1,546✔
311
    write_dataset(group, "fill_type", "lattice");
1,546✔
312
    write_dataset(group, "lattice", model::lattices[fill_]->id_);
1,546✔
313
  }
314

315
  close_group(group);
30,182✔
316
}
30,182✔
317

318
//==============================================================================
319
// CSGCell implementation
320
//==============================================================================
321

322
CSGCell::CSGCell(pugi::xml_node cell_node)
36,635✔
323
{
324
  if (check_for_node(cell_node, "id")) {
36,635✔
325
    id_ = std::stoi(get_node_value(cell_node, "id"));
36,635✔
326
  } else {
UNCOV
327
    fatal_error("Must specify id of cell in geometry XML file.");
×
328
  }
329

330
  if (check_for_node(cell_node, "name")) {
36,635✔
331
    name_ = get_node_value(cell_node, "name");
7,441✔
332
  }
333

334
  if (check_for_node(cell_node, "universe")) {
36,635✔
335
    universe_ = std::stoi(get_node_value(cell_node, "universe"));
35,332✔
336
  } else {
337
    universe_ = 0;
1,303✔
338
  }
339

340
  // Make sure that either material or fill was specified, but not both.
341
  bool fill_present = check_for_node(cell_node, "fill");
36,635✔
342
  bool material_present = check_for_node(cell_node, "material");
36,635✔
343
  if (!(fill_present || material_present)) {
36,635✔
UNCOV
344
    fatal_error(
×
UNCOV
345
      fmt::format("Neither material nor fill was specified for cell {}", id_));
×
346
  }
347
  if (fill_present && material_present) {
36,635✔
UNCOV
348
    fatal_error(fmt::format("Cell {} has both a material and a fill specified; "
×
349
                            "only one can be specified per cell",
UNCOV
350
      id_));
×
351
  }
352

353
  if (fill_present) {
36,635✔
354
    fill_ = std::stoi(get_node_value(cell_node, "fill"));
7,364✔
355
    if (fill_ == universe_) {
7,364✔
UNCOV
356
      fatal_error(fmt::format("Cell {} is filled with the same universe that "
×
357
                              "it is contained in.",
UNCOV
358
        id_));
×
359
    }
360
  } else {
361
    fill_ = C_NONE;
29,271✔
362
  }
363

364
  // Read the material element.  There can be zero materials (filled with a
365
  // universe), more than one material (distribmats), and some materials may
366
  // be "void".
367
  if (material_present) {
36,635✔
368
    vector<std::string> mats {
369
      get_node_array<std::string>(cell_node, "material", true)};
29,271✔
370
    if (mats.size() > 0) {
29,271✔
371
      material_.reserve(mats.size());
29,271✔
372
      for (std::string mat : mats) {
60,270✔
373
        if (mat.compare("void") == 0) {
30,999✔
374
          material_.push_back(MATERIAL_VOID);
10,651✔
375
        } else {
376
          material_.push_back(std::stoi(mat));
20,348✔
377
        }
378
      }
30,999✔
379
    } else {
UNCOV
380
      fatal_error(fmt::format(
×
381
        "An empty material element was specified for cell {}", id_));
×
382
    }
383
  }
29,271✔
384

385
  // Read the temperature element which may be distributed like materials.
386
  if (check_for_node(cell_node, "temperature")) {
36,635✔
387
    sqrtkT_ = get_node_array<double>(cell_node, "temperature");
335✔
388
    sqrtkT_.shrink_to_fit();
335✔
389

390
    // Make sure this is a material-filled cell.
391
    if (material_.size() == 0) {
335✔
UNCOV
392
      fatal_error(fmt::format(
×
393
        "Cell {} was specified with a temperature but no material. Temperature"
394
        "specification is only valid for cells filled with a material.",
UNCOV
395
        id_));
×
396
    }
397

398
    // Make sure all temperatures are non-negative.
399
    for (auto T : sqrtkT_) {
721✔
400
      if (T < 0) {
386✔
UNCOV
401
        fatal_error(fmt::format(
×
UNCOV
402
          "Cell {} was specified with a negative temperature", id_));
×
403
      }
404
    }
405

406
    // Convert to sqrt(k*T).
407
    for (auto& T : sqrtkT_) {
721✔
408
      T = std::sqrt(K_BOLTZMANN * T);
386✔
409
    }
410
  }
411

412
  // Read the density element which can be distributed similar to temperature.
413
  // These get assigned to the density multiplier, requiring a division by
414
  // the material density.
415
  // Note: calculating the actual density multiplier is deferred until materials
416
  // are finalized. rho_mult_ contains the true density in the meantime.
417
  if (check_for_node(cell_node, "density")) {
36,635✔
418
    rho_mult_ = get_node_array<double>(cell_node, "density");
17✔
419
    rho_mult_.shrink_to_fit();
17✔
420
    xml_set_density_ = true;
17✔
421

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

430
    // Make sure this is a non-void material.
431
    for (auto mat_id : material_) {
34✔
432
      if (mat_id == MATERIAL_VOID) {
17✔
NEW
UNCOV
433
        fatal_error(fmt::format(
×
434
          "Cell {} was specified with a density, but contains a void "
435
          "material. Density specification is only valid for cells "
436
          "filled with a non-void material.",
NEW
437
          id_));
×
438
      }
439
    }
440

441
    // Make sure all densities are non-negative and greater than zero.
442
    for (auto rho : rho_mult_) {
85✔
443
      if (rho <= 0) {
68✔
NEW
444
        fatal_error(fmt::format(
×
445
          "Cell {} was specified with a density less than or equal to zero",
NEW
446
          id_));
×
447
      }
448
    }
449
  }
450

451
  // Read the region specification.
452
  std::string region_spec;
36,635✔
453
  if (check_for_node(cell_node, "region")) {
36,635✔
454
    region_spec = get_node_value(cell_node, "region");
25,660✔
455
  }
456

457
  // Get a tokenized representation of the region specification and apply De
458
  // Morgans law
459
  Region region(region_spec, id_);
36,635✔
460
  region_ = region;
36,635✔
461

462
  // Read the translation vector.
463
  if (check_for_node(cell_node, "translation")) {
36,635✔
464
    if (fill_ == C_NONE) {
2,918✔
465
      fatal_error(fmt::format("Cannot apply a translation to cell {}"
×
466
                              " because it is not filled with another universe",
467
        id_));
×
468
    }
469

470
    auto xyz {get_node_array<double>(cell_node, "translation")};
2,918✔
471
    if (xyz.size() != 3) {
2,918✔
472
      fatal_error(
×
473
        fmt::format("Non-3D translation vector applied to cell {}", id_));
×
474
    }
475
    translation_ = xyz;
2,918✔
476
  }
2,918✔
477

478
  // Read the rotation transform.
479
  if (check_for_node(cell_node, "rotation")) {
36,635✔
480
    auto rot {get_node_array<double>(cell_node, "rotation")};
442✔
481
    set_rotation(rot);
442✔
482
  }
442✔
483
}
36,635✔
484

485
//==============================================================================
486

487
void CSGCell::to_hdf5_inner(hid_t group_id) const
29,972✔
488
{
489
  write_string(group_id, "geom_type", "csg", false);
29,972✔
490
  write_string(group_id, "region", region_.str(), false);
29,972✔
491
}
29,972✔
492

493
//==============================================================================
494

UNCOV
495
vector<int32_t>::iterator CSGCell::find_left_parenthesis(
×
496
  vector<int32_t>::iterator start, const vector<int32_t>& infix)
497
{
498
  // start search at zero
UNCOV
499
  int parenthesis_level = 0;
×
UNCOV
500
  auto it = start;
×
UNCOV
501
  while (it != infix.begin()) {
×
502
    // look at two tokens at a time
UNCOV
503
    int32_t one = *it;
×
UNCOV
504
    int32_t two = *(it - 1);
×
505

506
    // decrement parenthesis level if there are two adjacent surfaces
UNCOV
507
    if (one < OP_UNION && two < OP_UNION) {
×
UNCOV
508
      parenthesis_level--;
×
509
      // increment if there are two adjacent operators
UNCOV
510
    } else if (one >= OP_UNION && two >= OP_UNION) {
×
UNCOV
511
      parenthesis_level++;
×
512
    }
513

514
    // if the level gets to zero, return the position
UNCOV
515
    if (parenthesis_level == 0) {
×
516
      // move the iterator back one before leaving the loop
517
      // so that all tokens in the parenthesis block are included
UNCOV
518
      it--;
×
519
      break;
×
520
    }
521

522
    // continue loop, one token at a time
523
    it--;
×
524
  }
525
  return it;
×
526
}
527

528
//==============================================================================
529
// Region implementation
530
//==============================================================================
531

532
Region::Region(std::string region_spec, int32_t cell_id)
36,635✔
533
{
534
  // Check if region_spec is not empty.
535
  if (!region_spec.empty()) {
36,635✔
536
    // Parse all halfspaces and operators except for intersection (whitespace).
537
    for (int i = 0; i < region_spec.size();) {
153,966✔
538
      if (region_spec[i] == '(') {
128,306✔
539
        expression_.push_back(OP_LEFT_PAREN);
2,068✔
540
        i++;
2,068✔
541

542
      } else if (region_spec[i] == ')') {
126,238✔
543
        expression_.push_back(OP_RIGHT_PAREN);
2,068✔
544
        i++;
2,068✔
545

546
      } else if (region_spec[i] == '|') {
124,170✔
547
        expression_.push_back(OP_UNION);
4,444✔
548
        i++;
4,444✔
549

550
      } else if (region_spec[i] == '~') {
119,726✔
551
        expression_.push_back(OP_COMPLEMENT);
34✔
552
        i++;
34✔
553

554
      } else if (region_spec[i] == '-' || region_spec[i] == '+' ||
202,153✔
555
                 std::isdigit(region_spec[i])) {
82,461✔
556
        // This is the start of a halfspace specification.  Iterate j until we
557
        // find the end, then push-back everything between i and j.
558
        int j = i + 1;
69,609✔
559
        while (j < region_spec.size() && std::isdigit(region_spec[j])) {
141,365✔
560
          j++;
71,756✔
561
        }
562
        expression_.push_back(std::stoi(region_spec.substr(i, j - i)));
69,609✔
563
        i = j;
69,609✔
564

565
      } else if (std::isspace(region_spec[i])) {
50,083✔
566
        i++;
50,083✔
567

568
      } else {
569
        auto err_msg =
570
          fmt::format("Region specification contains invalid character, \"{}\"",
UNCOV
571
            region_spec[i]);
×
UNCOV
572
        fatal_error(err_msg);
×
UNCOV
573
      }
×
574
    }
575

576
    // Add in intersection operators where a missing operator is needed.
577
    int i = 0;
25,660✔
578
    while (i < expression_.size() - 1) {
117,728✔
579
      bool left_compat {
580
        (expression_[i] < OP_UNION) || (expression_[i] == OP_RIGHT_PAREN)};
92,068✔
581
      bool right_compat {(expression_[i + 1] < OP_UNION) ||
92,068✔
582
                         (expression_[i + 1] == OP_LEFT_PAREN) ||
98,648✔
583
                         (expression_[i + 1] == OP_COMPLEMENT)};
6,580✔
584
      if (left_compat && right_compat) {
92,068✔
585
        expression_.insert(expression_.begin() + i + 1, OP_INTERSECTION);
39,505✔
586
      }
587
      i++;
92,068✔
588
    }
589

590
    // Remove complement operators using DeMorgan's laws
591
    auto it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
25,660✔
592
    while (it != expression_.end()) {
25,694✔
593
      // Erase complement
594
      expression_.erase(it);
34✔
595

596
      // Define stop given left parenthesis or not
597
      auto stop = it;
34✔
598
      if (*it == OP_LEFT_PAREN) {
34✔
599
        int depth = 1;
34✔
600
        do {
601
          stop++;
272✔
602
          if (*stop > OP_COMPLEMENT) {
272✔
603
            if (*stop == OP_RIGHT_PAREN) {
34✔
604
              depth--;
34✔
605
            } else {
UNCOV
606
              depth++;
×
607
            }
608
          }
609
        } while (depth > 0);
272✔
610
        it++;
34✔
611
      }
612

613
      // apply DeMorgan's law to any surfaces/operators between these
614
      // positions in the RPN
615
      apply_demorgan(it, stop);
34✔
616
      // update iterator position
617
      it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
34✔
618
    }
619

620
    // Convert user IDs to surface indices.
621
    for (auto& r : expression_) {
143,354✔
622
      if (r < OP_UNION) {
117,694✔
623
        const auto& it {model::surface_map.find(abs(r))};
69,609✔
624
        if (it == model::surface_map.end()) {
69,609✔
UNCOV
625
          throw std::runtime_error {
×
UNCOV
626
            "Invalid surface ID " + std::to_string(abs(r)) +
×
UNCOV
627
            " specified in region for cell " + std::to_string(cell_id) + "."};
×
628
        }
629
        r = (r > 0) ? it->second + 1 : -(it->second + 1);
69,609✔
630
      }
631
    }
632

633
    // Check if this is a simple cell.
634
    simple_ = true;
25,660✔
635
    for (int32_t token : expression_) {
125,644✔
636
      if (token == OP_UNION) {
101,295✔
637
        simple_ = false;
1,311✔
638
        // Ensure intersections have precedence over unions
639
        add_precedence();
1,311✔
640
        break;
1,311✔
641
      }
642
    }
643

644
    // If this cell is simple, remove all the superfluous operator tokens.
645
    if (simple_) {
25,660✔
646
      for (auto it = expression_.begin(); it != expression_.end(); it++) {
117,410✔
647
        if (*it == OP_INTERSECTION || *it > OP_COMPLEMENT) {
93,061✔
648
          expression_.erase(it);
34,356✔
649
          it--;
34,356✔
650
        }
651
      }
652
    }
653
    expression_.shrink_to_fit();
25,660✔
654

655
  } else {
656
    simple_ = true;
10,975✔
657
  }
658
}
36,635✔
659

660
//==============================================================================
661

662
void Region::apply_demorgan(
238✔
663
  vector<int32_t>::iterator start, vector<int32_t>::iterator stop)
664
{
665
  do {
666
    if (*start < OP_UNION) {
238✔
667
      *start *= -1;
136✔
668
    } else if (*start == OP_UNION) {
102✔
UNCOV
669
      *start = OP_INTERSECTION;
×
670
    } else if (*start == OP_INTERSECTION) {
102✔
671
      *start = OP_UNION;
102✔
672
    }
673
    start++;
238✔
674
  } while (start < stop);
238✔
675
}
34✔
676

677
//==============================================================================
678
//! Add precedence for infix regions so intersections have higher
679
//! precedence than unions using parentheses.
680
//==============================================================================
681

682
int64_t Region::add_parentheses(int64_t start)
34✔
683
{
684
  int32_t start_token = expression_[start];
34✔
685
  // Add left parenthesis and set new position to be after parenthesis
686
  if (start_token == OP_UNION) {
34✔
UNCOV
687
    start += 2;
×
688
  }
689
  expression_.insert(expression_.begin() + start - 1, OP_LEFT_PAREN);
34✔
690

691
  // Keep track of return iterator distance. If we don't encounter a left
692
  // parenthesis, we return an iterator corresponding to wherever the right
693
  // parenthesis is inserted. If a left parenthesis is encountered, an iterator
694
  // corresponding to the left parenthesis is returned. Also note that we keep
695
  // track of a *distance* instead of an iterator because the underlying memory
696
  // allocation may change.
697
  std::size_t return_it_dist = 0;
34✔
698

699
  // Add right parenthesis
700
  // While the start iterator is within the bounds of infix
701
  while (start + 1 < expression_.size()) {
238✔
702
    start++;
238✔
703

704
    // If the current token is an operator and is different than the start token
705
    if (expression_[start] >= OP_UNION && expression_[start] != start_token) {
238✔
706
      // Skip wrapped regions but save iterator position to check precedence and
707
      // add right parenthesis, right parenthesis position depends on the
708
      // operator, when the operator is a union then do not include the operator
709
      // in the region, when the operator is an intersection then include the
710
      // operator and next surface
711
      if (expression_[start] == OP_LEFT_PAREN) {
34✔
UNCOV
712
        return_it_dist = start;
×
UNCOV
713
        int depth = 1;
×
714
        do {
UNCOV
715
          start++;
×
UNCOV
716
          if (expression_[start] > OP_COMPLEMENT) {
×
UNCOV
717
            if (expression_[start] == OP_RIGHT_PAREN) {
×
UNCOV
718
              depth--;
×
719
            } else {
UNCOV
720
              depth++;
×
721
            }
722
          }
UNCOV
723
        } while (depth > 0);
×
724
      } else {
725
        if (start_token == OP_UNION) {
34✔
UNCOV
726
          --start;
×
727
        }
728
        expression_.insert(expression_.begin() + start, OP_RIGHT_PAREN);
34✔
729
        if (return_it_dist > 0) {
34✔
UNCOV
730
          return return_it_dist;
×
731
        } else {
732
          return start - 1;
34✔
733
        }
734
      }
735
    }
736
  }
737
  // If we get here a right parenthesis hasn't been placed,
738
  // return iterator
739
  expression_.push_back(OP_RIGHT_PAREN);
×
740
  if (return_it_dist > 0) {
×
741
    return return_it_dist;
×
742
  } else {
UNCOV
743
    return start - 1;
×
744
  }
745
}
746

747
//==============================================================================
748

749
void Region::add_precedence()
1,311✔
750
{
751
  int32_t current_op = 0;
1,311✔
752
  std::size_t current_dist = 0;
1,311✔
753

754
  for (int64_t i = 0; i < expression_.size(); i++) {
25,910✔
755
    int32_t token = expression_[i];
24,599✔
756

757
    if (token == OP_UNION || token == OP_INTERSECTION) {
24,599✔
758
      if (current_op == 0) {
9,576✔
759
        // Set the current operator if is hasn't been set
760
        current_op = token;
3,464✔
761
        current_dist = i;
3,464✔
762
      } else if (token != current_op) {
6,112✔
763
        // If the current operator doesn't match the token, add parenthesis to
764
        // assert precedence
765
        if (current_op == OP_INTERSECTION) {
34✔
766
          i = add_parentheses(current_dist);
17✔
767
        } else {
768
          i = add_parentheses(i);
17✔
769
        }
770
        current_op = 0;
34✔
771
        current_dist = 0;
34✔
772
      }
773
    } else if (token > OP_COMPLEMENT) {
15,023✔
774
      // If the token is a parenthesis reset the current operator
775
      current_op = 0;
4,170✔
776
      current_dist = 0;
4,170✔
777
    }
778
  }
779
}
1,311✔
780

781
//==============================================================================
782
//! Convert infix region specification to Reverse Polish Notation (RPN)
783
//!
784
//! This function uses the shunting-yard algorithm.
785
//==============================================================================
786

787
vector<int32_t> Region::generate_postfix(int32_t cell_id) const
132✔
788
{
789
  vector<int32_t> rpn;
132✔
790
  vector<int32_t> stack;
132✔
791

792
  for (int32_t token : expression_) {
2,970✔
793
    if (token < OP_UNION) {
2,838✔
794
      // If token is not an operator, add it to output
795
      rpn.push_back(token);
1,188✔
796
    } else if (token < OP_RIGHT_PAREN) {
1,650✔
797
      // Regular operators union, intersection, complement
798
      while (stack.size() > 0) {
1,683✔
799
        int32_t op = stack.back();
1,386✔
800

801
        if (op < OP_RIGHT_PAREN && ((token == OP_COMPLEMENT && token < op) ||
1,386✔
802
                                     (token != OP_COMPLEMENT && token <= op))) {
627✔
803
          // While there is an operator, op, on top of the stack, if the token
804
          // is left-associative and its precedence is less than or equal to
805
          // that of op or if the token is right-associative and its precedence
806
          // is less than that of op, move op to the output queue and push the
807
          // token on to the stack. Note that only complement is
808
          // right-associative.
809
          rpn.push_back(op);
627✔
810
          stack.pop_back();
627✔
811
        } else {
812
          break;
813
        }
814
      }
815

816
      stack.push_back(token);
1,056✔
817

818
    } else if (token == OP_LEFT_PAREN) {
594✔
819
      // If the token is a left parenthesis, push it onto the stack
820
      stack.push_back(token);
297✔
821

822
    } else {
823
      // If the token is a right parenthesis, move operators from the stack to
824
      // the output queue until reaching the left parenthesis.
825
      for (auto it = stack.rbegin(); *it != OP_LEFT_PAREN; it++) {
594✔
826
        // If we run out of operators without finding a left parenthesis, it
827
        // means there are mismatched parentheses.
828
        if (it == stack.rend()) {
297✔
UNCOV
829
          fatal_error(fmt::format(
×
830
            "Mismatched parentheses in region specification for cell {}",
831
            cell_id));
832
        }
833
        rpn.push_back(stack.back());
297✔
834
        stack.pop_back();
297✔
835
      }
836

837
      // Pop the left parenthesis.
838
      stack.pop_back();
297✔
839
    }
840
  }
841

842
  while (stack.size() > 0) {
264✔
843
    int32_t op = stack.back();
132✔
844

845
    // If the operator is a parenthesis it is mismatched.
846
    if (op >= OP_RIGHT_PAREN) {
132✔
UNCOV
847
      fatal_error(fmt::format(
×
848
        "Mismatched parentheses in region specification for cell {}", cell_id));
849
    }
850

851
    rpn.push_back(stack.back());
132✔
852
    stack.pop_back();
132✔
853
  }
854

855
  return rpn;
264✔
856
}
132✔
857

858
//==============================================================================
859

860
std::string Region::str() const
29,972✔
861
{
862
  std::stringstream region_spec {};
29,972✔
863
  if (!expression_.empty()) {
29,972✔
864
    for (int32_t token : expression_) {
88,084✔
865
      if (token == OP_LEFT_PAREN) {
68,305✔
866
        region_spec << " (";
1,956✔
867
      } else if (token == OP_RIGHT_PAREN) {
66,349✔
868
        region_spec << " )";
1,956✔
869
      } else if (token == OP_COMPLEMENT) {
64,393✔
UNCOV
870
        region_spec << " ~";
×
871
      } else if (token == OP_INTERSECTION) {
64,393✔
872
      } else if (token == OP_UNION) {
59,754✔
873
        region_spec << " |";
4,236✔
874
      } else {
875
        // Note the off-by-one indexing
876
        auto surf_id = model::surfaces[abs(token) - 1]->id_;
55,518✔
877
        region_spec << " " << ((token > 0) ? surf_id : -surf_id);
55,518✔
878
      }
879
    }
880
  }
881
  return region_spec.str();
59,944✔
882
}
29,972✔
883

884
//==============================================================================
885

886
std::pair<double, int32_t> Region::distance(
2,147,483,647✔
887
  Position r, Direction u, int32_t on_surface) const
888
{
889
  double min_dist {INFTY};
2,147,483,647✔
890
  int32_t i_surf {std::numeric_limits<int32_t>::max()};
2,147,483,647✔
891

892
  for (int32_t token : expression_) {
2,147,483,647✔
893
    // Ignore this token if it corresponds to an operator rather than a region.
894
    if (token >= OP_UNION)
2,147,483,647✔
895
      continue;
176,682,290✔
896

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

902
    // Check if this distance is the new minimum.
903
    if (d < min_dist) {
2,147,483,647✔
904
      if (min_dist - d >= FP_PRECISION * min_dist) {
2,147,483,647✔
905
        min_dist = d;
2,147,483,647✔
906
        i_surf = -token;
2,147,483,647✔
907
      }
908
    }
909
  }
910

911
  return {min_dist, i_surf};
2,147,483,647✔
912
}
913

914
//==============================================================================
915

916
bool Region::contains(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
917
{
918
  if (simple_) {
2,147,483,647✔
919
    return contains_simple(r, u, on_surface);
2,147,483,647✔
920
  } else {
921
    return contains_complex(r, u, on_surface);
7,115,275✔
922
  }
923
}
924

925
//==============================================================================
926

927
bool Region::contains_simple(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
928
{
929
  for (int32_t token : expression_) {
2,147,483,647✔
930
    // Assume that no tokens are operators. Evaluate the sense of particle with
931
    // respect to the surface and see if the token matches the sense. If the
932
    // particle's surface attribute is set and matches the token, that
933
    // overrides the determination based on sense().
934
    if (token == on_surface) {
2,147,483,647✔
935
    } else if (-token == on_surface) {
2,147,483,647✔
936
      return false;
1,063,893,281✔
937
    } else {
938
      // Note the off-by-one indexing
939
      bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
2,147,483,647✔
940
      if (sense != (token > 0)) {
2,147,483,647✔
941
        return false;
902,651,144✔
942
      }
943
    }
944
  }
945
  return true;
2,147,483,647✔
946
}
947

948
//==============================================================================
949

950
bool Region::contains_complex(Position r, Direction u, int32_t on_surface) const
7,115,275✔
951
{
952
  bool in_cell = true;
7,115,275✔
953
  int total_depth = 0;
7,115,275✔
954

955
  // For each token
956
  for (auto it = expression_.begin(); it != expression_.end(); it++) {
112,725,770✔
957
    int32_t token = *it;
107,578,048✔
958

959
    // If the token is a surface evaluate the sense
960
    // If the token is a union or intersection check to
961
    // short circuit
962
    if (token < OP_UNION) {
107,578,048✔
963
      if (token == on_surface) {
47,038,571✔
964
        in_cell = true;
3,483,783✔
965
      } else if (-token == on_surface) {
43,554,788✔
966
        in_cell = false;
717,488✔
967
      } else {
968
        // Note the off-by-one indexing
969
        bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
42,837,300✔
970
        in_cell = (sense == (token > 0));
42,837,300✔
971
      }
972
    } else if ((token == OP_UNION && in_cell == true) ||
60,539,477✔
973
               (token == OP_INTERSECTION && in_cell == false)) {
29,678,922✔
974
      // If the total depth is zero return
975
      if (total_depth == 0) {
6,623,271✔
976
        return in_cell;
1,967,553✔
977
      }
978

979
      total_depth--;
4,655,718✔
980

981
      // While the iterator is within the bounds of the vector
982
      int depth = 1;
4,655,718✔
983
      do {
984
        // Get next token
985
        it++;
29,392,168✔
986
        int32_t next_token = *it;
29,392,168✔
987

988
        // If the token is an a parenthesis
989
        if (next_token > OP_COMPLEMENT) {
29,392,168✔
990
          // Adjust depth accordingly
991
          if (next_token == OP_RIGHT_PAREN) {
4,856,654✔
992
            depth--;
4,756,186✔
993
          } else {
994
            depth++;
100,468✔
995
          }
996
        }
997
      } while (depth > 0);
29,392,168✔
998
    } else if (token == OP_LEFT_PAREN) {
58,571,924✔
999
      total_depth++;
9,324,314✔
1000
    } else if (token == OP_RIGHT_PAREN) {
44,591,892✔
1001
      total_depth--;
4,668,596✔
1002
    }
1003
  }
1004
  return in_cell;
5,147,722✔
1005
}
1006

1007
//==============================================================================
1008

1009
BoundingBox Region::bounding_box(int32_t cell_id) const
207✔
1010
{
1011
  if (simple_) {
207✔
1012
    return bounding_box_simple();
75✔
1013
  } else {
1014
    auto postfix = generate_postfix(cell_id);
132✔
1015
    return bounding_box_complex(postfix);
132✔
1016
  }
132✔
1017
}
1018

1019
//==============================================================================
1020

1021
BoundingBox Region::bounding_box_simple() const
75✔
1022
{
1023
  BoundingBox bbox;
75✔
1024
  for (int32_t token : expression_) {
319✔
1025
    bbox &= model::surfaces[abs(token) - 1]->bounding_box(token > 0);
244✔
1026
  }
1027
  return bbox;
75✔
1028
}
1029

1030
//==============================================================================
1031

1032
BoundingBox Region::bounding_box_complex(vector<int32_t> postfix) const
132✔
1033
{
1034
  vector<BoundingBox> stack(postfix.size());
132✔
1035
  int i_stack = -1;
132✔
1036

1037
  for (auto& token : postfix) {
2,376✔
1038
    if (token == OP_UNION) {
2,244✔
1039
      stack[i_stack - 1] = stack[i_stack - 1] | stack[i_stack];
462✔
1040
      i_stack--;
462✔
1041
    } else if (token == OP_INTERSECTION) {
1,782✔
1042
      stack[i_stack - 1] = stack[i_stack - 1] & stack[i_stack];
594✔
1043
      i_stack--;
594✔
1044
    } else {
1045
      i_stack++;
1,188✔
1046
      stack[i_stack] = model::surfaces[abs(token) - 1]->bounding_box(token > 0);
1,188✔
1047
    }
1048
  }
1049

1050
  assert(i_stack == 0);
104✔
1051
  return stack.front();
264✔
1052
}
132✔
1053

1054
//==============================================================================
1055

1056
vector<int32_t> Region::surfaces() const
5,721✔
1057
{
1058
  if (simple_) {
5,721✔
1059
    return expression_;
5,721✔
1060
  }
1061

UNCOV
1062
  vector<int32_t> surfaces = expression_;
×
1063

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

UNCOV
1067
  while (it != surfaces.end()) {
×
UNCOV
1068
    surfaces.erase(it);
×
1069

UNCOV
1070
    it = std::find_if(surfaces.begin(), surfaces.end(),
×
UNCOV
1071
      [&](const auto& value) { return value >= OP_UNION; });
×
1072
  }
1073

UNCOV
1074
  return surfaces;
×
1075
}
1076

1077
//==============================================================================
1078
// Non-method functions
1079
//==============================================================================
1080

1081
void read_cells(pugi::xml_node node)
7,882✔
1082
{
1083
  // Count the number of cells.
1084
  int n_cells = 0;
7,882✔
1085
  for (pugi::xml_node cell_node : node.children("cell")) {
44,517✔
1086
    n_cells++;
36,635✔
1087
  }
1088

1089
  // Loop over XML cell elements and populate the array.
1090
  model::cells.reserve(n_cells);
7,882✔
1091
  for (pugi::xml_node cell_node : node.children("cell")) {
44,517✔
1092
    model::cells.push_back(make_unique<CSGCell>(cell_node));
36,635✔
1093
  }
1094

1095
  // Fill the cell map.
1096
  for (int i = 0; i < model::cells.size(); i++) {
44,517✔
1097
    int32_t id = model::cells[i]->id_;
36,635✔
1098
    auto search = model::cell_map.find(id);
36,635✔
1099
    if (search == model::cell_map.end()) {
36,635✔
1100
      model::cell_map[id] = i;
36,635✔
1101
    } else {
UNCOV
1102
      fatal_error(
×
UNCOV
1103
        fmt::format("Two or more cells use the same unique ID: {}", id));
×
1104
    }
1105
  }
1106

1107
  read_dagmc_universes(node);
7,882✔
1108

1109
  populate_universes();
7,880✔
1110

1111
  // Allocate the cell overlap count if necessary.
1112
  if (settings::check_overlaps) {
7,880✔
1113
    model::overlap_check_count.resize(model::cells.size(), 0);
301✔
1114
  }
1115

1116
  if (model::cells.size() == 0) {
7,880✔
UNCOV
1117
    fatal_error("No cells were found in the geometry.xml file");
×
1118
  }
1119
}
7,880✔
1120

1121
void populate_universes()
7,882✔
1122
{
1123
  // Used to map universe index to the index of an implicit complement cell for
1124
  // DAGMC universes
1125
  std::unordered_map<int, int> implicit_comp_cells;
7,882✔
1126

1127
  // Populate the Universe vector and map.
1128
  for (int index_cell = 0; index_cell < model::cells.size(); index_cell++) {
44,795✔
1129
    int32_t uid = model::cells[index_cell]->universe_;
36,913✔
1130
    auto it = model::universe_map.find(uid);
36,913✔
1131
    if (it == model::universe_map.end()) {
36,913✔
1132
      model::universes.push_back(make_unique<Universe>());
21,525✔
1133
      model::universes.back()->id_ = uid;
21,525✔
1134
      model::universes.back()->cells_.push_back(index_cell);
21,525✔
1135
      model::universe_map[uid] = model::universes.size() - 1;
21,525✔
1136
    } else {
1137
#ifdef OPENMC_DAGMC_ENABLED
1138
      // Skip implicit complement cells for now
1139
      Universe* univ = model::universes[it->second].get();
1,894✔
1140
      DAGUniverse* dag_univ = dynamic_cast<DAGUniverse*>(univ);
1,894✔
1141
      if (dag_univ && (dag_univ->implicit_complement_idx() == index_cell)) {
1,894✔
1142
        implicit_comp_cells[it->second] = index_cell;
57✔
1143
        continue;
57✔
1144
      }
1145
#endif
1146

1147
      model::universes[it->second]->cells_.push_back(index_cell);
15,331✔
1148
    }
1149
  }
1150

1151
  // Add DAGUniverse implicit complement cells last
1152
  for (const auto& it : implicit_comp_cells) {
7,939✔
1153
    int index_univ = it.first;
57✔
1154
    int index_cell = it.second;
57✔
1155
    model::universes[index_univ]->cells_.push_back(index_cell);
57✔
1156
  }
1157

1158
  model::universes.shrink_to_fit();
7,882✔
1159
}
7,882✔
1160

1161
//==============================================================================
1162
// C-API functions
1163
//==============================================================================
1164

1165
extern "C" int openmc_cell_get_fill(
307✔
1166
  int32_t index, int* type, int32_t** indices, int32_t* n)
1167
{
1168
  if (index >= 0 && index < model::cells.size()) {
307✔
1169
    Cell& c {*model::cells[index]};
307✔
1170
    *type = static_cast<int>(c.type_);
307✔
1171
    if (c.type_ == Fill::MATERIAL) {
307✔
1172
      *indices = c.material_.data();
307✔
1173
      *n = c.material_.size();
307✔
1174
    } else {
UNCOV
1175
      *indices = &c.fill_;
×
UNCOV
1176
      *n = 1;
×
1177
    }
1178
  } else {
UNCOV
1179
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1180
    return OPENMC_E_OUT_OF_BOUNDS;
×
1181
  }
1182
  return 0;
307✔
1183
}
1184

1185
extern "C" int openmc_cell_set_fill(
14✔
1186
  int32_t index, int type, int32_t n, const int32_t* indices)
1187
{
1188
  Fill filltype = static_cast<Fill>(type);
14✔
1189
  if (index >= 0 && index < model::cells.size()) {
14✔
1190
    Cell& c {*model::cells[index]};
14✔
1191
    if (filltype == Fill::MATERIAL) {
14✔
1192
      c.type_ = Fill::MATERIAL;
14✔
1193
      c.material_.clear();
14✔
1194
      for (int i = 0; i < n; i++) {
28✔
1195
        int i_mat = indices[i];
14✔
1196
        if (i_mat == MATERIAL_VOID) {
14✔
UNCOV
1197
          c.material_.push_back(MATERIAL_VOID);
×
1198
        } else if (i_mat >= 0 && i_mat < model::materials.size()) {
14✔
1199
          c.material_.push_back(i_mat);
14✔
1200
        } else {
UNCOV
1201
          set_errmsg("Index in materials array is out of bounds.");
×
UNCOV
1202
          return OPENMC_E_OUT_OF_BOUNDS;
×
1203
        }
1204
      }
1205
      c.material_.shrink_to_fit();
14✔
UNCOV
1206
    } else if (filltype == Fill::UNIVERSE) {
×
UNCOV
1207
      c.type_ = Fill::UNIVERSE;
×
1208
    } else {
UNCOV
1209
      c.type_ = Fill::LATTICE;
×
1210
    }
1211
  } else {
UNCOV
1212
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1213
    return OPENMC_E_OUT_OF_BOUNDS;
×
1214
  }
1215
  return 0;
14✔
1216
}
1217

1218
extern "C" int openmc_cell_set_temperature(
107✔
1219
  int32_t index, double T, const int32_t* instance, bool set_contained)
1220
{
1221
  if (index < 0 || index >= model::cells.size()) {
107✔
UNCOV
1222
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
UNCOV
1223
    return OPENMC_E_OUT_OF_BOUNDS;
×
1224
  }
1225

1226
  int32_t instance_index = instance ? *instance : -1;
107✔
1227
  try {
1228
    model::cells[index]->set_temperature(T, instance_index, set_contained);
107✔
UNCOV
1229
  } catch (const std::exception& e) {
×
1230
    set_errmsg(e.what());
×
1231
    return OPENMC_E_UNASSIGNED;
×
UNCOV
1232
  }
×
1233
  return 0;
107✔
1234
}
1235

1236
extern "C" int openmc_cell_set_density(
94✔
1237
  int32_t index, double rho, const int32_t* instance, bool set_contained)
1238
{
1239
  if (index < 0 || index >= model::cells.size()) {
94✔
NEW
UNCOV
1240
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
NEW
UNCOV
1241
    return OPENMC_E_OUT_OF_BOUNDS;
×
1242
  }
1243

1244
  int32_t instance_index = instance ? *instance : -1;
94✔
1245
  try {
1246
    if (model::cells[index]->type_ != Fill::MATERIAL) {
94✔
NEW
1247
      fatal_error(
×
NEW
UNCOV
1248
        fmt::format("Cell {}, instance {} is not filled with a material.",
×
NEW
UNCOV
1249
          model::cells[index]->id_, instance_index));
×
1250
    }
1251

1252
    int32_t mat_index = model::cells[index]->material(instance_index);
94✔
1253
    if (mat_index != MATERIAL_VOID) {
94✔
1254
      model::cells[index]->set_density_mult(
188✔
1255
        rho / model::materials[mat_index]->density_gpcc(), instance_index,
94✔
1256
        set_contained);
1257
    }
NEW
UNCOV
1258
  } catch (const std::exception& e) {
×
NEW
UNCOV
1259
    set_errmsg(e.what());
×
NEW
1260
    return OPENMC_E_UNASSIGNED;
×
NEW
1261
  }
×
1262
  return 0;
94✔
1263
}
1264

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

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

1283
extern "C" int openmc_cell_get_density(
83✔
1284
  int32_t index, const int32_t* instance, double* rho)
1285
{
1286
  if (index < 0 || index >= model::cells.size()) {
83✔
NEW
UNCOV
1287
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
NEW
UNCOV
1288
    return OPENMC_E_OUT_OF_BOUNDS;
×
1289
  }
1290

1291
  int32_t instance_index = instance ? *instance : -1;
83✔
1292
  try {
1293
    if (model::cells[index]->type_ != Fill::MATERIAL) {
83✔
NEW
UNCOV
1294
      fatal_error(
×
NEW
UNCOV
1295
        fmt::format("Cell {}, instance {} is not filled with a material.",
×
NEW
1296
          model::cells[index]->id_, instance_index));
×
1297
    }
1298

1299
    int32_t mat_index = model::cells[index]->material(instance_index);
83✔
1300
    if (mat_index == MATERIAL_VOID) {
83✔
NEW
1301
      *rho = 0.0;
×
1302
    } else {
1303
      *rho = model::cells[index]->density_mult(instance_index) *
166✔
1304
             model::materials[mat_index]->density_gpcc();
83✔
1305
    }
NEW
1306
  } catch (const std::exception& e) {
×
NEW
1307
    set_errmsg(e.what());
×
NEW
1308
    return OPENMC_E_UNASSIGNED;
×
NEW
1309
  }
×
1310
  return 0;
83✔
1311
}
1312

1313
//! Get the bounding box of a cell
1314
extern "C" int openmc_cell_bounding_box(
165✔
1315
  const int32_t index, double* llc, double* urc)
1316
{
1317

1318
  BoundingBox bbox;
165✔
1319

1320
  const auto& c = model::cells[index];
165✔
1321
  bbox = c->bounding_box();
165✔
1322

1323
  // set lower left corner values
1324
  llc[0] = bbox.xmin;
165✔
1325
  llc[1] = bbox.ymin;
165✔
1326
  llc[2] = bbox.zmin;
165✔
1327

1328
  // set upper right corner values
1329
  urc[0] = bbox.xmax;
165✔
1330
  urc[1] = bbox.ymax;
165✔
1331
  urc[2] = bbox.zmax;
165✔
1332

1333
  return 0;
165✔
1334
}
1335

1336
//! Get the name of a cell
1337
extern "C" int openmc_cell_get_name(int32_t index, const char** name)
28✔
1338
{
1339
  if (index < 0 || index >= model::cells.size()) {
28✔
UNCOV
1340
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1341
    return OPENMC_E_OUT_OF_BOUNDS;
×
1342
  }
1343

1344
  *name = model::cells[index]->name().data();
28✔
1345

1346
  return 0;
28✔
1347
}
1348

1349
//! Set the name of a cell
1350
extern "C" int openmc_cell_set_name(int32_t index, const char* name)
14✔
1351
{
1352
  if (index < 0 || index >= model::cells.size()) {
14✔
1353
    set_errmsg("Index in cells array is out of bounds.");
×
1354
    return OPENMC_E_OUT_OF_BOUNDS;
×
1355
  }
1356

1357
  model::cells[index]->set_name(name);
14✔
1358

1359
  return 0;
14✔
1360
}
1361

1362
//==============================================================================
1363
//! Define a containing (parent) cell
1364
//==============================================================================
1365

1366
//! Used to locate a universe fill in the geometry
1367
struct ParentCell {
1368
  bool operator==(const ParentCell& other) const
153✔
1369
  {
1370
    return cell_index == other.cell_index &&
306✔
1371
           lattice_index == other.lattice_index;
306✔
1372
  }
1373

1374
  bool operator<(const ParentCell& other) const
1375
  {
1376
    return cell_index < other.cell_index ||
1377
           (cell_index == other.cell_index &&
1378
             lattice_index < other.lattice_index);
1379
  }
1380

1381
  int64_t cell_index;
1382
  int64_t lattice_index;
1383
};
1384

1385
//! Structure used to insert ParentCell into hashed STL data structures
1386
struct ParentCellHash {
1387
  std::size_t operator()(const ParentCell& p) const
715✔
1388
  {
1389
    return 4096 * p.cell_index + p.lattice_index;
715✔
1390
  }
1391
};
1392

1393
//! Used to manage a traversal stack when locating parent cells of a cell
1394
//! instance in the model
1395
struct ParentCellStack {
1396

1397
  //! push method that adds to the parent_cells visited cells for this search
1398
  //! universe
1399
  void push(int32_t search_universe, const ParentCell& pc)
119✔
1400
  {
1401
    parent_cells_.push_back(pc);
119✔
1402
    // add parent cell to the set of cells we've visited for this search
1403
    // universe
1404
    visited_cells_[search_universe].insert(pc);
119✔
1405
  }
119✔
1406

1407
  //! removes the last parent_cell and clears the visited cells for the popped
1408
  //! cell's universe
1409
  void pop()
85✔
1410
  {
1411
    visited_cells_[this->current_univ()].clear();
85✔
1412
    parent_cells_.pop_back();
85✔
1413
  }
85✔
1414

1415
  //! checks whether or not the parent cell has been visited already for this
1416
  //! search universe
1417
  bool visited(int32_t search_universe, const ParentCell& parent_cell)
596✔
1418
  {
1419
    return visited_cells_[search_universe].count(parent_cell) != 0;
596✔
1420
  }
1421

1422
  //! return the next universe to search for a parent cell
1423
  int32_t current_univ() const
85✔
1424
  {
1425
    return model::cells[parent_cells_.back().cell_index]->universe_;
85✔
1426
  }
1427

1428
  //! indicates whether nor not parent cells are present on the stack
1429
  bool empty() const { return parent_cells_.empty(); }
85✔
1430

1431
  //! compute an instance for the provided distribcell index
1432
  int32_t compute_instance(int32_t distribcell_index) const
205✔
1433
  {
1434
    if (distribcell_index == C_NONE)
205✔
1435
      return 0;
69✔
1436

1437
    int32_t instance = 0;
136✔
1438
    for (const auto& parent_cell : this->parent_cells_) {
255✔
1439
      auto& cell = model::cells[parent_cell.cell_index];
119✔
1440
      if (cell->type_ == Fill::UNIVERSE) {
119✔
UNCOV
1441
        instance += cell->offset_[distribcell_index];
×
1442
      } else if (cell->type_ == Fill::LATTICE) {
119✔
1443
        auto& lattice = model::lattices[cell->fill_];
119✔
1444
        instance +=
119✔
1445
          lattice->offset(distribcell_index, parent_cell.lattice_index);
119✔
1446
      }
1447
    }
1448
    return instance;
136✔
1449
  }
1450

1451
  // Accessors
1452
  vector<ParentCell>& parent_cells() { return parent_cells_; }
360✔
1453
  const vector<ParentCell>& parent_cells() const { return parent_cells_; }
1454

1455
  // Data Members
1456
  vector<ParentCell> parent_cells_;
1457
  std::unordered_map<int32_t, std::unordered_set<ParentCell, ParentCellHash>>
1458
    visited_cells_;
1459
};
1460

UNCOV
1461
vector<ParentCell> Cell::find_parent_cells(
×
1462
  int32_t instance, const Position& r) const
1463
{
1464

1465
  // create a temporary particle
UNCOV
1466
  GeometryState dummy_particle {};
×
UNCOV
1467
  dummy_particle.r() = r;
×
UNCOV
1468
  dummy_particle.u() = {0., 0., 1.};
×
1469

UNCOV
1470
  return find_parent_cells(instance, dummy_particle);
×
1471
}
1472

UNCOV
1473
vector<ParentCell> Cell::find_parent_cells(
×
1474
  int32_t instance, GeometryState& p) const
1475
{
1476
  // look up the particle's location
UNCOV
1477
  exhaustive_find_cell(p);
×
UNCOV
1478
  const auto& coords = p.coord();
×
1479

1480
  // build a parent cell stack from the particle coordinates
1481
  ParentCellStack stack;
×
UNCOV
1482
  bool cell_found = false;
×
1483
  for (auto it = coords.begin(); it != coords.end(); it++) {
×
UNCOV
1484
    const auto& coord = *it;
×
UNCOV
1485
    const auto& cell = model::cells[coord.cell()];
×
1486
    // if the cell at this level matches the current cell, stop adding to the
1487
    // stack
UNCOV
1488
    if (coord.cell() == model::cell_map[this->id_]) {
×
UNCOV
1489
      cell_found = true;
×
1490
      break;
×
1491
    }
1492

1493
    // if filled with a lattice, get the lattice index from the next
1494
    // level in the coordinates to push to the stack
1495
    int lattice_idx = C_NONE;
×
1496
    if (cell->type_ == Fill::LATTICE) {
×
1497
      const auto& next_coord = *(it + 1);
×
1498
      lattice_idx = model::lattices[next_coord.lattice()]->get_flat_index(
×
1499
        next_coord.lattice_index());
1500
    }
1501
    stack.push(coord.universe(), {coord.cell(), lattice_idx});
×
1502
  }
1503

1504
  // if this loop finished because the cell was found and
1505
  // the instance matches the one requested in the call
1506
  // we have the correct path and can return the stack
UNCOV
1507
  if (cell_found &&
×
1508
      stack.compute_instance(this->distribcell_index_) == instance) {
×
1509
    return stack.parent_cells();
×
1510
  }
1511

1512
  // fall back on an exhaustive search for the cell's parents
UNCOV
1513
  return exhaustive_find_parent_cells(instance);
×
1514
}
1515

1516
vector<ParentCell> Cell::exhaustive_find_parent_cells(int32_t instance) const
120✔
1517
{
1518
  ParentCellStack stack;
120✔
1519
  // start with this cell's universe
1520
  int32_t prev_univ_idx;
1521
  int32_t univ_idx = this->universe_;
120✔
1522

1523
  while (true) {
1524
    const auto& univ = model::universes[univ_idx];
205✔
1525
    prev_univ_idx = univ_idx;
205✔
1526

1527
    // search for a cell that is filled w/ this universe
1528
    for (const auto& cell : model::cells) {
1,313✔
1529
      // if this is a material-filled cell, move on
1530
      if (cell->type_ == Fill::MATERIAL)
1,227✔
1531
        continue;
682✔
1532

1533
      if (cell->type_ == Fill::UNIVERSE) {
545✔
1534
        // if this is in the set of cells previously visited for this universe,
1535
        // move on
1536
        if (stack.visited(univ_idx, {model::cell_map[cell->id_], C_NONE}))
324✔
UNCOV
1537
          continue;
×
1538

1539
        // if this cell contains the universe we're searching for, add it to the
1540
        // stack
1541
        if (cell->fill_ == univ_idx) {
324✔
UNCOV
1542
          stack.push(univ_idx, {model::cell_map[cell->id_], C_NONE});
×
UNCOV
1543
          univ_idx = cell->universe_;
×
1544
        }
1545
      } else if (cell->type_ == Fill::LATTICE) {
221✔
1546
        // retrieve the lattice and lattice universes
1547
        const auto& lattice = model::lattices[cell->fill_];
221✔
1548
        const auto& lattice_univs = lattice->universes_;
221✔
1549

1550
        // start search for universe
1551
        auto lat_it = lattice_univs.begin();
221✔
1552
        while (true) {
1553
          // find the next lattice cell with this universe
1554
          lat_it = std::find(lat_it, lattice_univs.end(), univ_idx);
374✔
1555
          if (lat_it == lattice_univs.end())
374✔
1556
            break;
102✔
1557

1558
          int lattice_idx = lat_it - lattice_univs.begin();
272✔
1559

1560
          // move iterator forward one to avoid finding the same entry
1561
          lat_it++;
272✔
1562
          if (stack.visited(
272✔
1563
                univ_idx, {model::cell_map[cell->id_], lattice_idx}))
272✔
1564
            continue;
153✔
1565

1566
          // add this cell and lattice index to the stack and exit loop
1567
          stack.push(univ_idx, {model::cell_map[cell->id_], lattice_idx});
119✔
1568
          univ_idx = cell->universe_;
119✔
1569
          break;
119✔
1570
        }
153✔
1571
      }
1572
      // if we've updated the universe, break
1573
      if (prev_univ_idx != univ_idx)
545✔
1574
        break;
119✔
1575
    } // end cell loop search for universe
1576

1577
    // if we're at the top of the geometry and the instance matches, we're done
1578
    if (univ_idx == model::root_universe &&
410✔
1579
        stack.compute_instance(this->distribcell_index_) == instance)
205✔
1580
      break;
120✔
1581

1582
    // if there is no match on the original cell's universe, report an error
1583
    if (univ_idx == this->universe_) {
85✔
UNCOV
1584
      fatal_error(
×
UNCOV
1585
        fmt::format("Could not find the parent cells for cell {}, instance {}.",
×
UNCOV
1586
          this->id_, instance));
×
1587
    }
1588

1589
    // if we don't find a suitable update, adjust the stack and continue
1590
    if (univ_idx == model::root_universe || univ_idx == prev_univ_idx) {
85✔
1591
      stack.pop();
85✔
1592
      univ_idx = stack.empty() ? this->universe_ : stack.current_univ();
85✔
1593
    }
1594

1595
  } // end while
85✔
1596

1597
  // reverse the stack so the highest cell comes first
1598
  std::reverse(stack.parent_cells().begin(), stack.parent_cells().end());
120✔
1599
  return stack.parent_cells();
240✔
1600
}
120✔
1601

1602
std::unordered_map<int32_t, vector<int32_t>> Cell::get_contained_cells(
171✔
1603
  int32_t instance, Position* hint) const
1604
{
1605
  std::unordered_map<int32_t, vector<int32_t>> contained_cells;
171✔
1606

1607
  // if this is a material-filled cell it has no contained cells
1608
  if (this->type_ == Fill::MATERIAL)
171✔
1609
    return contained_cells;
51✔
1610

1611
  // find the pathway through the geometry to this cell
1612
  vector<ParentCell> parent_cells;
120✔
1613

1614
  // if a positional hint is provided, attempt to do a fast lookup
1615
  // of the parent cells
1616
  parent_cells = hint ? find_parent_cells(instance, *hint)
240✔
1617
                      : exhaustive_find_parent_cells(instance);
120✔
1618

1619
  // if this cell is filled w/ a material, it contains no other cells
1620
  if (type_ != Fill::MATERIAL) {
120✔
1621
    this->get_contained_cells_inner(contained_cells, parent_cells);
120✔
1622
  }
1623

1624
  return contained_cells;
120✔
1625
}
120✔
1626

1627
//! Get all cells within this cell
1628
void Cell::get_contained_cells_inner(
93,248✔
1629
  std::unordered_map<int32_t, vector<int32_t>>& contained_cells,
1630
  vector<ParentCell>& parent_cells) const
1631
{
1632

1633
  // filled by material, determine instance based on parent cells
1634
  if (type_ == Fill::MATERIAL) {
93,248✔
1635
    int instance = 0;
92,584✔
1636
    if (this->distribcell_index_ >= 0) {
92,584✔
1637
      for (auto& parent_cell : parent_cells) {
277,750✔
1638
        auto& cell = model::cells[parent_cell.cell_index];
185,166✔
1639
        if (cell->type_ == Fill::UNIVERSE) {
185,166✔
1640
          instance += cell->offset_[distribcell_index_];
90,884✔
1641
        } else if (cell->type_ == Fill::LATTICE) {
94,282✔
1642
          auto& lattice = model::lattices[cell->fill_];
94,282✔
1643
          instance += lattice->offset(
188,564✔
1644
            this->distribcell_index_, parent_cell.lattice_index);
94,282✔
1645
        }
1646
      }
1647
    }
1648
    // add entry to contained cells
1649
    contained_cells[model::cell_map[id_]].push_back(instance);
92,584✔
1650
    // filled with universe, add the containing cell to the parent cells
1651
    // and recurse
1652
  } else if (type_ == Fill::UNIVERSE) {
664✔
1653
    parent_cells.push_back({model::cell_map[id_], -1});
562✔
1654
    auto& univ = model::universes[fill_];
562✔
1655
    for (auto cell_index : univ->cells_) {
3,369✔
1656
      auto& cell = model::cells[cell_index];
2,807✔
1657
      cell->get_contained_cells_inner(contained_cells, parent_cells);
2,807✔
1658
    }
1659
    parent_cells.pop_back();
562✔
1660
    // filled with a lattice, visit each universe in the lattice
1661
    // with a recursive call to collect the cell instances
1662
  } else if (type_ == Fill::LATTICE) {
102✔
1663
    auto& lattice = model::lattices[fill_];
102✔
1664
    for (auto i = lattice->begin(); i != lattice->end(); ++i) {
90,066✔
1665
      auto& univ = model::universes[*i];
89,964✔
1666
      parent_cells.push_back({model::cell_map[id_], i.indx_});
89,964✔
1667
      for (auto cell_index : univ->cells_) {
180,285✔
1668
        auto& cell = model::cells[cell_index];
90,321✔
1669
        cell->get_contained_cells_inner(contained_cells, parent_cells);
90,321✔
1670
      }
1671
      parent_cells.pop_back();
89,964✔
1672
    }
1673
  }
1674
}
93,248✔
1675

1676
//! Return the index in the cells array of a cell with a given ID
1677
extern "C" int openmc_get_cell_index(int32_t id, int32_t* index)
734✔
1678
{
1679
  auto it = model::cell_map.find(id);
734✔
1680
  if (it != model::cell_map.end()) {
734✔
1681
    *index = it->second;
720✔
1682
    return 0;
720✔
1683
  } else {
1684
    set_errmsg("No cell exists with ID=" + std::to_string(id) + ".");
14✔
1685
    return OPENMC_E_INVALID_ID;
14✔
1686
  }
1687
}
1688

1689
//! Return the ID of a cell
1690
extern "C" int openmc_cell_get_id(int32_t index, int32_t* id)
801,767✔
1691
{
1692
  if (index >= 0 && index < model::cells.size()) {
801,767✔
1693
    *id = model::cells[index]->id_;
801,767✔
1694
    return 0;
801,767✔
1695
  } else {
UNCOV
1696
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1697
    return OPENMC_E_OUT_OF_BOUNDS;
×
1698
  }
1699
}
1700

1701
//! Set the ID of a cell
1702
extern "C" int openmc_cell_set_id(int32_t index, int32_t id)
28✔
1703
{
1704
  if (index >= 0 && index < model::cells.size()) {
28✔
1705
    model::cells[index]->id_ = id;
28✔
1706
    model::cell_map[id] = index;
28✔
1707
    return 0;
28✔
1708
  } else {
1709
    set_errmsg("Index in cells array is out of bounds.");
×
1710
    return OPENMC_E_OUT_OF_BOUNDS;
×
1711
  }
1712
}
1713

1714
//! Return the translation vector of a cell
1715
extern "C" int openmc_cell_get_translation(int32_t index, double xyz[])
68✔
1716
{
1717
  if (index >= 0 && index < model::cells.size()) {
68✔
1718
    auto& cell = model::cells[index];
68✔
1719
    xyz[0] = cell->translation_.x;
68✔
1720
    xyz[1] = cell->translation_.y;
68✔
1721
    xyz[2] = cell->translation_.z;
68✔
1722
    return 0;
68✔
1723
  } else {
UNCOV
1724
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1725
    return OPENMC_E_OUT_OF_BOUNDS;
×
1726
  }
1727
}
1728

1729
//! Set the translation vector of a cell
1730
extern "C" int openmc_cell_set_translation(int32_t index, const double xyz[])
41✔
1731
{
1732
  if (index >= 0 && index < model::cells.size()) {
41✔
1733
    if (model::cells[index]->fill_ == C_NONE) {
41✔
1734
      set_errmsg(fmt::format("Cannot apply a translation to cell {}"
14✔
1735
                             " because it is not filled with another universe",
1736
        index));
1737
      return OPENMC_E_GEOMETRY;
14✔
1738
    }
1739
    model::cells[index]->translation_ = Position(xyz);
27✔
1740
    return 0;
27✔
1741
  } else {
UNCOV
1742
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1743
    return OPENMC_E_OUT_OF_BOUNDS;
×
1744
  }
1745
}
1746

1747
//! Return the rotation matrix of a cell
1748
extern "C" int openmc_cell_get_rotation(int32_t index, double rot[], size_t* n)
68✔
1749
{
1750
  if (index >= 0 && index < model::cells.size()) {
68✔
1751
    auto& cell = model::cells[index];
68✔
1752
    *n = cell->rotation_.size();
68✔
1753
    std::memcpy(rot, cell->rotation_.data(), *n * sizeof(cell->rotation_[0]));
68✔
1754
    return 0;
68✔
1755
  } else {
1756
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1757
    return OPENMC_E_OUT_OF_BOUNDS;
×
1758
  }
1759
}
1760

1761
//! Set the flattened rotation matrix of a cell
1762
extern "C" int openmc_cell_set_rotation(
54✔
1763
  int32_t index, const double rot[], size_t rot_len)
1764
{
1765
  if (index >= 0 && index < model::cells.size()) {
54✔
1766
    if (model::cells[index]->fill_ == C_NONE) {
54✔
1767
      set_errmsg(fmt::format("Cannot apply a rotation to cell {}"
14✔
1768
                             " because it is not filled with another universe",
1769
        index));
1770
      return OPENMC_E_GEOMETRY;
14✔
1771
    }
1772
    std::vector<double> vec_rot(rot, rot + rot_len);
40✔
1773
    model::cells[index]->set_rotation(vec_rot);
40✔
1774
    return 0;
40✔
1775
  } else {
40✔
UNCOV
1776
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1777
    return OPENMC_E_OUT_OF_BOUNDS;
×
1778
  }
1779
}
1780

1781
//! Get the number of instances of the requested cell
1782
extern "C" int openmc_cell_get_num_instances(
14✔
1783
  int32_t index, int32_t* num_instances)
1784
{
1785
  if (index < 0 || index >= model::cells.size()) {
14✔
UNCOV
1786
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1787
    return OPENMC_E_OUT_OF_BOUNDS;
×
1788
  }
1789
  *num_instances = model::cells[index]->n_instances();
14✔
1790
  return 0;
14✔
1791
}
1792

1793
//! Extend the cells array by n elements
1794
extern "C" int openmc_extend_cells(
28✔
1795
  int32_t n, int32_t* index_start, int32_t* index_end)
1796
{
1797
  if (index_start)
28✔
1798
    *index_start = model::cells.size();
28✔
1799
  if (index_end)
28✔
1800
    *index_end = model::cells.size() + n - 1;
×
1801
  for (int32_t i = 0; i < n; i++) {
56✔
1802
    model::cells.push_back(make_unique<CSGCell>());
28✔
1803
  }
1804
  return 0;
28✔
1805
}
1806

1807
extern "C" int cells_size()
56✔
1808
{
1809
  return model::cells.size();
56✔
1810
}
1811

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