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

openmc-dev / openmc / 17598200653

09 Sep 2025 11:18PM UTC coverage: 85.18% (-0.03%) from 85.209%
17598200653

Pull #3546

github

web-flow
Merge c6e1ade8d into 366509051
Pull Request #3546: Add distributed cell density multipliers

147 of 192 new or added lines in 12 files covered. (76.56%)

166 existing lines in 8 files now uncovered.

53068 of 62301 relevant lines covered (85.18%)

38500201.64 hits per line

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

77.72
/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
772,875✔
44
{
45
  return model::universes[universe_]->n_instances_;
772,875✔
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
108✔
91
{
92
  if (sqrtkT_.size() < 1) {
108✔
93
    throw std::runtime_error {"Cell temperature has not yet been set."};
×
94
  }
95

96
  if (instance >= 0) {
108✔
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;
96✔
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

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

NEW
121
  if (instance >= 0) {
×
122
    double rho =
NEW
UNCOV
123
      rho_mult_.size() == 1 ? rho_mult_.at(0) : rho_mult_.at(instance);
×
NEW
UNCOV
124
    return rho * model::materials[mat_index]->density_gpcc();
×
125
  } else {
NEW
126
    return rho_mult_[0];
×
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)
148✔
181
{
182
  if (type_ != Fill::MATERIAL && !set_contained) {
148✔
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) {
148✔
190
    const int32_t mat_index = material(instance);
132✔
191
    if (mat_index == MATERIAL_VOID)
132✔
NEW
192
      return;
×
193

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

199
      // Set density multiplier for the corresponding instance
200
      rho_mult_.at(instance) =
60✔
201
        rho / model::materials[mat_index]->density_gpcc();
60✔
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 multipliers for one or more cell instances
233
  write_dataset(cell_group, "density_mult", rho_mult_);
168✔
234

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

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

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

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

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

261
  // Read density multipliers
262
  if (object_exists(cell_group, "density_mult")) {
192✔
263
    read_dataset(cell_group, "density_mult", rho_mult_);
192✔
264

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

274
  close_group(cell_group);
192✔
275
}
192✔
276

277
void Cell::to_hdf5(hid_t cell_group) const
26,522✔
278
{
279

280
  // Create a group for this cell.
281
  auto group = create_group(cell_group, fmt::format("cell {}", id_));
53,044✔
282

283
  if (!name_.empty()) {
26,522✔
284
    write_string(group, "name", name_, false);
4,931✔
285
  }
286

287
  write_dataset(group, "universe", model::universes[universe_]->id_);
26,522✔
288

289
  to_hdf5_inner(group);
26,522✔
290

291
  // Write fill information.
292
  if (type_ == Fill::MATERIAL) {
26,522✔
293
    write_dataset(group, "fill_type", "material");
21,532✔
294
    std::vector<int32_t> mat_ids;
21,532✔
295
    for (auto i_mat : material_) {
44,625✔
296
      if (i_mat != MATERIAL_VOID) {
23,093✔
297
        mat_ids.push_back(model::materials[i_mat]->id_);
15,111✔
298
      } else {
299
        mat_ids.push_back(MATERIAL_VOID);
7,982✔
300
      }
301
    }
302
    if (mat_ids.size() == 1) {
21,532✔
303
      write_dataset(group, "material", mat_ids[0]);
21,305✔
304
    } else {
305
      write_dataset(group, "material", mat_ids);
227✔
306
    }
307

308
    std::vector<double> temps;
21,532✔
309
    for (auto sqrtkT_val : sqrtkT_)
44,739✔
310
      temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
23,207✔
311
    write_dataset(group, "temperature", temps);
21,532✔
312

313
    write_dataset(group, "density_mult", rho_mult_);
21,532✔
314

315
  } else if (type_ == Fill::UNIVERSE) {
26,522✔
316
    write_dataset(group, "fill_type", "universe");
3,565✔
317
    write_dataset(group, "fill", model::universes[fill_]->id_);
3,565✔
318
    if (translation_ != Position(0, 0, 0)) {
3,565✔
319
      write_dataset(group, "translation", translation_);
1,837✔
320
    }
321
    if (!rotation_.empty()) {
3,565✔
322
      if (rotation_.size() == 12) {
264✔
323
        std::array<double, 3> rot {rotation_[9], rotation_[10], rotation_[11]};
264✔
324
        write_dataset(group, "rotation", rot);
264✔
325
      } else {
UNCOV
326
        write_dataset(group, "rotation", rotation_);
×
327
      }
328
    }
329

330
  } else if (type_ == Fill::LATTICE) {
1,425✔
331
    write_dataset(group, "fill_type", "lattice");
1,425✔
332
    write_dataset(group, "lattice", model::lattices[fill_]->id_);
1,425✔
333
  }
334

335
  close_group(group);
26,522✔
336
}
26,522✔
337

338
//==============================================================================
339
// CSGCell implementation
340
//==============================================================================
341

342
CSGCell::CSGCell(pugi::xml_node cell_node)
32,899✔
343
{
344
  if (check_for_node(cell_node, "id")) {
32,899✔
345
    id_ = std::stoi(get_node_value(cell_node, "id"));
32,899✔
346
  } else {
UNCOV
347
    fatal_error("Must specify id of cell in geometry XML file.");
×
348
  }
349

350
  if (check_for_node(cell_node, "name")) {
32,899✔
351
    name_ = get_node_value(cell_node, "name");
6,950✔
352
  }
353

354
  if (check_for_node(cell_node, "universe")) {
32,899✔
355
    universe_ = std::stoi(get_node_value(cell_node, "universe"));
31,674✔
356
  } else {
357
    universe_ = 0;
1,225✔
358
  }
359

360
  // Make sure that either material or fill was specified, but not both.
361
  bool fill_present = check_for_node(cell_node, "fill");
32,899✔
362
  bool material_present = check_for_node(cell_node, "material");
32,899✔
363
  if (!(fill_present || material_present)) {
32,899✔
UNCOV
364
    fatal_error(
×
UNCOV
365
      fmt::format("Neither material nor fill was specified for cell {}", id_));
×
366
  }
367
  if (fill_present && material_present) {
32,899✔
UNCOV
368
    fatal_error(fmt::format("Cell {} has both a material and a fill specified; "
×
369
                            "only one can be specified per cell",
370
      id_));
×
371
  }
372

373
  if (fill_present) {
32,899✔
374
    fill_ = std::stoi(get_node_value(cell_node, "fill"));
6,935✔
375
    if (fill_ == universe_) {
6,935✔
UNCOV
376
      fatal_error(fmt::format("Cell {} is filled with the same universe that "
×
377
                              "it is contained in.",
UNCOV
378
        id_));
×
379
    }
380
  } else {
381
    fill_ = C_NONE;
25,964✔
382
  }
383

384
  // Read the material element.  There can be zero materials (filled with a
385
  // universe), more than one material (distribmats), and some materials may
386
  // be "void".
387
  if (material_present) {
32,899✔
388
    vector<std::string> mats {
389
      get_node_array<std::string>(cell_node, "material", true)};
25,964✔
390
    if (mats.size() > 0) {
25,964✔
391
      material_.reserve(mats.size());
25,964✔
392
      for (std::string mat : mats) {
53,516✔
393
        if (mat.compare("void") == 0) {
27,552✔
394
          material_.push_back(MATERIAL_VOID);
8,489✔
395
        } else {
396
          material_.push_back(std::stoi(mat));
19,063✔
397
        }
398
      }
27,552✔
399
    } else {
UNCOV
400
      fatal_error(fmt::format(
×
UNCOV
401
        "An empty material element was specified for cell {}", id_));
×
402
    }
403
  }
25,964✔
404

405
  // Read the temperature element which may be distributed like materials.
406
  if (check_for_node(cell_node, "temperature")) {
32,899✔
407
    sqrtkT_ = get_node_array<double>(cell_node, "temperature");
315✔
408
    sqrtkT_.shrink_to_fit();
315✔
409

410
    // Make sure this is a material-filled cell.
411
    if (material_.size() == 0) {
315✔
UNCOV
412
      fatal_error(fmt::format(
×
413
        "Cell {} was specified with a temperature but no material. Temperature"
414
        "specification is only valid for cells filled with a material.",
UNCOV
415
        id_));
×
416
    }
417

418
    // Make sure all temperatures are non-negative.
419
    for (auto T : sqrtkT_) {
678✔
420
      if (T < 0) {
363✔
UNCOV
421
        fatal_error(fmt::format(
×
UNCOV
422
          "Cell {} was specified with a negative temperature", id_));
×
423
      }
424
    }
425

426
    // Convert to sqrt(k*T).
427
    for (auto& T : sqrtkT_) {
678✔
428
      T = std::sqrt(K_BOLTZMANN * T);
363✔
429
    }
430
  }
431

432
  // Read the density element which can be distributed similar to temperature.
433
  // These get assigned to the density multiplier, requiring a division by
434
  // the material density.
435
  // Note: calculating the actual density multiplier is deferred until materials
436
  // are finalized. rho_mult_ contains the true density in the meantime.
437
  if (check_for_node(cell_node, "density")) {
32,899✔
438
    rho_mult_ = get_node_array<double>(cell_node, "density");
16✔
439
    rho_mult_.shrink_to_fit();
16✔
440
    xml_set_density_ = true;
16✔
441

442
    // Make sure this is a material-filled cell.
443
    if (material_.size() == 0) {
16✔
NEW
444
      fatal_error(fmt::format(
×
445
        "Cell {} was specified with a density but no material. Density"
446
        "specification is only valid for cells filled with a material.",
NEW
447
        id_));
×
448
    }
449

450
    // Make sure this is a non-void material.
451
    for (auto mat_id : material_) {
32✔
452
      if (mat_id == MATERIAL_VOID) {
16✔
NEW
453
        fatal_error(fmt::format(
×
454
          "Cell {} was specified with a density, but contains a void "
455
          "material. Density specification is only valid for cells "
456
          "filled with a non-void material.",
NEW
457
          id_));
×
458
      }
459
    }
460

461
    // Make sure all densities are non-negative and greater than zero.
462
    for (auto rho : rho_mult_) {
80✔
463
      if (rho <= 0) {
64✔
NEW
464
        fatal_error(fmt::format(
×
465
          "Cell {} was specified with a density less than or equal to zero",
NEW
466
          id_));
×
467
      }
468
    }
469
  }
470

471
  // Read the region specification.
472
  std::string region_spec;
32,899✔
473
  if (check_for_node(cell_node, "region")) {
32,899✔
474
    region_spec = get_node_value(cell_node, "region");
24,107✔
475
  }
476

477
  // Get a tokenized representation of the region specification and apply De
478
  // Morgans law
479
  Region region(region_spec, id_);
32,899✔
480
  region_ = region;
32,899✔
481

482
  // Read the translation vector.
483
  if (check_for_node(cell_node, "translation")) {
32,899✔
484
    if (fill_ == C_NONE) {
2,747✔
UNCOV
485
      fatal_error(fmt::format("Cannot apply a translation to cell {}"
×
486
                              " because it is not filled with another universe",
UNCOV
487
        id_));
×
488
    }
489

490
    auto xyz {get_node_array<double>(cell_node, "translation")};
2,747✔
491
    if (xyz.size() != 3) {
2,747✔
UNCOV
492
      fatal_error(
×
UNCOV
493
        fmt::format("Non-3D translation vector applied to cell {}", id_));
×
494
    }
495
    translation_ = xyz;
2,747✔
496
  }
2,747✔
497

498
  // Read the rotation transform.
499
  if (check_for_node(cell_node, "rotation")) {
32,899✔
500
    auto rot {get_node_array<double>(cell_node, "rotation")};
416✔
501
    set_rotation(rot);
416✔
502
  }
416✔
503
}
32,899✔
504

505
//==============================================================================
506

507
void CSGCell::to_hdf5_inner(hid_t group_id) const
26,312✔
508
{
509
  write_string(group_id, "geom_type", "csg", false);
26,312✔
510
  write_string(group_id, "region", region_.str(), false);
26,312✔
511
}
26,312✔
512

513
//==============================================================================
514

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

526
    // decrement parenthesis level if there are two adjacent surfaces
527
    if (one < OP_UNION && two < OP_UNION) {
×
528
      parenthesis_level--;
×
529
      // increment if there are two adjacent operators
UNCOV
530
    } else if (one >= OP_UNION && two >= OP_UNION) {
×
531
      parenthesis_level++;
×
532
    }
533

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

542
    // continue loop, one token at a time
543
    it--;
×
544
  }
UNCOV
545
  return it;
×
546
}
547

548
//==============================================================================
549
// Region implementation
550
//==============================================================================
551

552
Region::Region(std::string region_spec, int32_t cell_id)
32,899✔
553
{
554
  // Check if region_spec is not empty.
555
  if (!region_spec.empty()) {
32,899✔
556
    // Parse all halfspaces and operators except for intersection (whitespace).
557
    for (int i = 0; i < region_spec.size();) {
145,498✔
558
      if (region_spec[i] == '(') {
121,391✔
559
        expression_.push_back(OP_LEFT_PAREN);
2,000✔
560
        i++;
2,000✔
561

562
      } else if (region_spec[i] == ')') {
119,391✔
563
        expression_.push_back(OP_RIGHT_PAREN);
2,000✔
564
        i++;
2,000✔
565

566
      } else if (region_spec[i] == '|') {
117,391✔
567
        expression_.push_back(OP_UNION);
4,309✔
568
        i++;
4,309✔
569

570
      } else if (region_spec[i] == '~') {
113,082✔
571
        expression_.push_back(OP_COMPLEMENT);
32✔
572
        i++;
32✔
573

574
      } else if (region_spec[i] == '-' || region_spec[i] == '+' ||
191,015✔
575
                 std::isdigit(region_spec[i])) {
77,965✔
576
        // This is the start of a halfspace specification.  Iterate j until we
577
        // find the end, then push-back everything between i and j.
578
        int j = i + 1;
65,629✔
579
        while (j < region_spec.size() && std::isdigit(region_spec[j])) {
133,260✔
580
          j++;
67,631✔
581
        }
582
        expression_.push_back(std::stoi(region_spec.substr(i, j - i)));
65,629✔
583
        i = j;
65,629✔
584

585
      } else if (std::isspace(region_spec[i])) {
47,421✔
586
        i++;
47,421✔
587

588
      } else {
589
        auto err_msg =
590
          fmt::format("Region specification contains invalid character, \"{}\"",
UNCOV
591
            region_spec[i]);
×
UNCOV
592
        fatal_error(err_msg);
×
UNCOV
593
      }
×
594
    }
595

596
    // Add in intersection operators where a missing operator is needed.
597
    int i = 0;
24,107✔
598
    while (i < expression_.size() - 1) {
111,183✔
599
      bool left_compat {
600
        (expression_[i] < OP_UNION) || (expression_[i] == OP_RIGHT_PAREN)};
87,076✔
601
      bool right_compat {(expression_[i + 1] < OP_UNION) ||
87,076✔
602
                         (expression_[i + 1] == OP_LEFT_PAREN) ||
93,449✔
603
                         (expression_[i + 1] == OP_COMPLEMENT)};
6,373✔
604
      if (left_compat && right_compat) {
87,076✔
605
        expression_.insert(expression_.begin() + i + 1, OP_INTERSECTION);
37,213✔
606
      }
607
      i++;
87,076✔
608
    }
609

610
    // Remove complement operators using DeMorgan's laws
611
    auto it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
24,107✔
612
    while (it != expression_.end()) {
24,139✔
613
      // Erase complement
614
      expression_.erase(it);
32✔
615

616
      // Define stop given left parenthesis or not
617
      auto stop = it;
32✔
618
      if (*it == OP_LEFT_PAREN) {
32✔
619
        int depth = 1;
32✔
620
        do {
621
          stop++;
256✔
622
          if (*stop > OP_COMPLEMENT) {
256✔
623
            if (*stop == OP_RIGHT_PAREN) {
32✔
624
              depth--;
32✔
625
            } else {
UNCOV
626
              depth++;
×
627
            }
628
          }
629
        } while (depth > 0);
256✔
630
        it++;
32✔
631
      }
632

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

640
    // Convert user IDs to surface indices.
641
    for (auto& r : expression_) {
135,258✔
642
      if (r < OP_UNION) {
111,151✔
643
        const auto& it {model::surface_map.find(abs(r))};
65,629✔
644
        if (it == model::surface_map.end()) {
65,629✔
UNCOV
645
          throw std::runtime_error {
×
UNCOV
646
            "Invalid surface ID " + std::to_string(abs(r)) +
×
UNCOV
647
            " specified in region for cell " + std::to_string(cell_id) + "."};
×
648
        }
649
        r = (r > 0) ? it->second + 1 : -(it->second + 1);
65,629✔
650
      }
651
    }
652

653
    // Check if this is a simple cell.
654
    simple_ = true;
24,107✔
655
    for (int32_t token : expression_) {
118,120✔
656
      if (token == OP_UNION) {
95,284✔
657
        simple_ = false;
1,271✔
658
        // Ensure intersections have precedence over unions
659
        add_precedence();
1,271✔
660
        break;
1,271✔
661
      }
662
    }
663

664
    // If this cell is simple, remove all the superfluous operator tokens.
665
    if (simple_) {
24,107✔
666
      for (auto it = expression_.begin(); it != expression_.end(); it++) {
110,134✔
667
        if (*it == OP_INTERSECTION || *it > OP_COMPLEMENT) {
87,298✔
668
          expression_.erase(it);
32,231✔
669
          it--;
32,231✔
670
        }
671
      }
672
    }
673
    expression_.shrink_to_fit();
24,107✔
674

675
  } else {
676
    simple_ = true;
8,792✔
677
  }
678
}
32,899✔
679

680
//==============================================================================
681

682
void Region::apply_demorgan(
224✔
683
  vector<int32_t>::iterator start, vector<int32_t>::iterator stop)
684
{
685
  do {
686
    if (*start < OP_UNION) {
224✔
687
      *start *= -1;
128✔
688
    } else if (*start == OP_UNION) {
96✔
UNCOV
689
      *start = OP_INTERSECTION;
×
690
    } else if (*start == OP_INTERSECTION) {
96✔
691
      *start = OP_UNION;
96✔
692
    }
693
    start++;
224✔
694
  } while (start < stop);
224✔
695
}
32✔
696

697
//==============================================================================
698
//! Add precedence for infix regions so intersections have higher
699
//! precedence than unions using parentheses.
700
//==============================================================================
701

702
int64_t Region::add_parentheses(int64_t start)
32✔
703
{
704
  int32_t start_token = expression_[start];
32✔
705
  // Add left parenthesis and set new position to be after parenthesis
706
  if (start_token == OP_UNION) {
32✔
UNCOV
707
    start += 2;
×
708
  }
709
  expression_.insert(expression_.begin() + start - 1, OP_LEFT_PAREN);
32✔
710

711
  // Keep track of return iterator distance. If we don't encounter a left
712
  // parenthesis, we return an iterator corresponding to wherever the right
713
  // parenthesis is inserted. If a left parenthesis is encountered, an iterator
714
  // corresponding to the left parenthesis is returned. Also note that we keep
715
  // track of a *distance* instead of an iterator because the underlying memory
716
  // allocation may change.
717
  std::size_t return_it_dist = 0;
32✔
718

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

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

767
//==============================================================================
768

769
void Region::add_precedence()
1,271✔
770
{
771
  int32_t current_op = 0;
1,271✔
772
  std::size_t current_dist = 0;
1,271✔
773

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

777
    if (token == OP_UNION || token == OP_INTERSECTION) {
23,821✔
778
      if (current_op == 0) {
9,275✔
779
        // Set the current operator if is hasn't been set
780
        current_op = token;
3,351✔
781
        current_dist = i;
3,351✔
782
      } else if (token != current_op) {
5,924✔
783
        // If the current operator doesn't match the token, add parenthesis to
784
        // assert precedence
785
        if (current_op == OP_INTERSECTION) {
32✔
786
          i = add_parentheses(current_dist);
16✔
787
        } else {
788
          i = add_parentheses(i);
16✔
789
        }
790
        current_op = 0;
32✔
791
        current_dist = 0;
32✔
792
      }
793
    } else if (token > OP_COMPLEMENT) {
14,546✔
794
      // If the token is a parenthesis reset the current operator
795
      current_op = 0;
4,032✔
796
      current_dist = 0;
4,032✔
797
    }
798
  }
799
}
1,271✔
800

801
//==============================================================================
802
//! Convert infix region specification to Reverse Polish Notation (RPN)
803
//!
804
//! This function uses the shunting-yard algorithm.
805
//==============================================================================
806

807
vector<int32_t> Region::generate_postfix(int32_t cell_id) const
128✔
808
{
809
  vector<int32_t> rpn;
128✔
810
  vector<int32_t> stack;
128✔
811

812
  for (int32_t token : expression_) {
2,880✔
813
    if (token < OP_UNION) {
2,752✔
814
      // If token is not an operator, add it to output
815
      rpn.push_back(token);
1,152✔
816
    } else if (token < OP_RIGHT_PAREN) {
1,600✔
817
      // Regular operators union, intersection, complement
818
      while (stack.size() > 0) {
1,632✔
819
        int32_t op = stack.back();
1,344✔
820

821
        if (op < OP_RIGHT_PAREN && ((token == OP_COMPLEMENT && token < op) ||
1,344✔
822
                                     (token != OP_COMPLEMENT && token <= op))) {
608✔
823
          // While there is an operator, op, on top of the stack, if the token
824
          // is left-associative and its precedence is less than or equal to
825
          // that of op or if the token is right-associative and its precedence
826
          // is less than that of op, move op to the output queue and push the
827
          // token on to the stack. Note that only complement is
828
          // right-associative.
829
          rpn.push_back(op);
608✔
830
          stack.pop_back();
608✔
831
        } else {
832
          break;
833
        }
834
      }
835

836
      stack.push_back(token);
1,024✔
837

838
    } else if (token == OP_LEFT_PAREN) {
576✔
839
      // If the token is a left parenthesis, push it onto the stack
840
      stack.push_back(token);
288✔
841

842
    } else {
843
      // If the token is a right parenthesis, move operators from the stack to
844
      // the output queue until reaching the left parenthesis.
845
      for (auto it = stack.rbegin(); *it != OP_LEFT_PAREN; it++) {
576✔
846
        // If we run out of operators without finding a left parenthesis, it
847
        // means there are mismatched parentheses.
848
        if (it == stack.rend()) {
288✔
UNCOV
849
          fatal_error(fmt::format(
×
850
            "Mismatched parentheses in region specification for cell {}",
851
            cell_id));
852
        }
853
        rpn.push_back(stack.back());
288✔
854
        stack.pop_back();
288✔
855
      }
856

857
      // Pop the left parenthesis.
858
      stack.pop_back();
288✔
859
    }
860
  }
861

862
  while (stack.size() > 0) {
256✔
863
    int32_t op = stack.back();
128✔
864

865
    // If the operator is a parenthesis it is mismatched.
866
    if (op >= OP_RIGHT_PAREN) {
128✔
UNCOV
867
      fatal_error(fmt::format(
×
868
        "Mismatched parentheses in region specification for cell {}", cell_id));
869
    }
870

871
    rpn.push_back(stack.back());
128✔
872
    stack.pop_back();
128✔
873
  }
874

875
  return rpn;
256✔
876
}
128✔
877

878
//==============================================================================
879

880
std::string Region::str() const
26,312✔
881
{
882
  std::stringstream region_spec {};
26,312✔
883
  if (!expression_.empty()) {
26,312✔
884
    for (int32_t token : expression_) {
82,392✔
885
      if (token == OP_LEFT_PAREN) {
64,091✔
886
        region_spec << " (";
1,886✔
887
      } else if (token == OP_RIGHT_PAREN) {
62,205✔
888
        region_spec << " )";
1,886✔
889
      } else if (token == OP_COMPLEMENT) {
60,319✔
UNCOV
890
        region_spec << " ~";
×
891
      } else if (token == OP_INTERSECTION) {
60,319✔
892
      } else if (token == OP_UNION) {
55,841✔
893
        region_spec << " |";
4,095✔
894
      } else {
895
        // Note the off-by-one indexing
896
        auto surf_id = model::surfaces[abs(token) - 1]->id_;
51,746✔
897
        region_spec << " " << ((token > 0) ? surf_id : -surf_id);
51,746✔
898
      }
899
    }
900
  }
901
  return region_spec.str();
52,624✔
902
}
26,312✔
903

904
//==============================================================================
905

906
std::pair<double, int32_t> Region::distance(
2,147,483,647✔
907
  Position r, Direction u, int32_t on_surface) const
908
{
909
  double min_dist {INFTY};
2,147,483,647✔
910
  int32_t i_surf {std::numeric_limits<int32_t>::max()};
2,147,483,647✔
911

912
  for (int32_t token : expression_) {
2,147,483,647✔
913
    // Ignore this token if it corresponds to an operator rather than a region.
914
    if (token >= OP_UNION)
2,147,483,647✔
915
      continue;
169,611,236✔
916

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

922
    // Check if this distance is the new minimum.
923
    if (d < min_dist) {
2,147,483,647✔
924
      if (min_dist - d >= FP_PRECISION * min_dist) {
2,147,483,647✔
925
        min_dist = d;
2,147,483,647✔
926
        i_surf = -token;
2,147,483,647✔
927
      }
928
    }
929
  }
930

931
  return {min_dist, i_surf};
2,147,483,647✔
932
}
933

934
//==============================================================================
935

936
bool Region::contains(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
937
{
938
  if (simple_) {
2,147,483,647✔
939
    return contains_simple(r, u, on_surface);
2,147,483,647✔
940
  } else {
941
    return contains_complex(r, u, on_surface);
6,718,429✔
942
  }
943
}
944

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

947
bool Region::contains_simple(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
948
{
949
  for (int32_t token : expression_) {
2,147,483,647✔
950
    // Assume that no tokens are operators. Evaluate the sense of particle with
951
    // respect to the surface and see if the token matches the sense. If the
952
    // particle's surface attribute is set and matches the token, that
953
    // overrides the determination based on sense().
954
    if (token == on_surface) {
2,147,483,647✔
955
    } else if (-token == on_surface) {
2,147,483,647✔
956
      return false;
974,857,847✔
957
    } else {
958
      // Note the off-by-one indexing
959
      bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
2,147,483,647✔
960
      if (sense != (token > 0)) {
2,147,483,647✔
961
        return false;
827,893,836✔
962
      }
963
    }
964
  }
965
  return true;
2,147,483,647✔
966
}
967

968
//==============================================================================
969

970
bool Region::contains_complex(Position r, Direction u, int32_t on_surface) const
6,718,429✔
971
{
972
  bool in_cell = true;
6,718,429✔
973
  int total_depth = 0;
6,718,429✔
974

975
  // For each token
976
  for (auto it = expression_.begin(); it != expression_.end(); it++) {
106,404,504✔
977
    int32_t token = *it;
101,568,455✔
978

979
    // If the token is a surface evaluate the sense
980
    // If the token is a union or intersection check to
981
    // short circuit
982
    if (token < OP_UNION) {
101,568,455✔
983
      if (token == on_surface) {
44,393,958✔
984
        in_cell = true;
3,306,748✔
985
      } else if (-token == on_surface) {
41,087,210✔
986
        in_cell = false;
669,915✔
987
      } else {
988
        // Note the off-by-one indexing
989
        bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
40,417,295✔
990
        in_cell = (sense == (token > 0));
40,417,295✔
991
      }
992
    } else if ((token == OP_UNION && in_cell == true) ||
57,174,497✔
993
               (token == OP_INTERSECTION && in_cell == false)) {
27,910,721✔
994
      // If the total depth is zero return
995
      if (total_depth == 0) {
6,255,455✔
996
        return in_cell;
1,882,380✔
997
      }
998

999
      total_depth--;
4,373,075✔
1000

1001
      // While the iterator is within the bounds of the vector
1002
      int depth = 1;
4,373,075✔
1003
      do {
1004
        // Get next token
1005
        it++;
27,631,258✔
1006
        int32_t next_token = *it;
27,631,258✔
1007

1008
        // If the token is an a parenthesis
1009
        if (next_token > OP_COMPLEMENT) {
27,631,258✔
1010
          // Adjust depth accordingly
1011
          if (next_token == OP_RIGHT_PAREN) {
4,559,519✔
1012
            depth--;
4,466,297✔
1013
          } else {
1014
            depth++;
93,222✔
1015
          }
1016
        }
1017
      } while (depth > 0);
27,631,258✔
1018
    } else if (token == OP_LEFT_PAREN) {
55,292,117✔
1019
      total_depth++;
8,808,294✔
1020
    } else if (token == OP_RIGHT_PAREN) {
42,110,748✔
1021
      total_depth--;
4,435,219✔
1022
    }
1023
  }
1024
  return in_cell;
4,836,049✔
1025
}
1026

1027
//==============================================================================
1028

1029
BoundingBox Region::bounding_box(int32_t cell_id) const
196✔
1030
{
1031
  if (simple_) {
196✔
1032
    return bounding_box_simple();
68✔
1033
  } else {
1034
    auto postfix = generate_postfix(cell_id);
128✔
1035
    return bounding_box_complex(postfix);
128✔
1036
  }
128✔
1037
}
1038

1039
//==============================================================================
1040

1041
BoundingBox Region::bounding_box_simple() const
68✔
1042
{
1043
  BoundingBox bbox;
68✔
1044
  for (int32_t token : expression_) {
292✔
1045
    bbox &= model::surfaces[abs(token) - 1]->bounding_box(token > 0);
224✔
1046
  }
1047
  return bbox;
68✔
1048
}
1049

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

1052
BoundingBox Region::bounding_box_complex(vector<int32_t> postfix) const
128✔
1053
{
1054
  vector<BoundingBox> stack(postfix.size());
128✔
1055
  int i_stack = -1;
128✔
1056

1057
  for (auto& token : postfix) {
2,304✔
1058
    if (token == OP_UNION) {
2,176✔
1059
      stack[i_stack - 1] = stack[i_stack - 1] | stack[i_stack];
448✔
1060
      i_stack--;
448✔
1061
    } else if (token == OP_INTERSECTION) {
1,728✔
1062
      stack[i_stack - 1] = stack[i_stack - 1] & stack[i_stack];
576✔
1063
      i_stack--;
576✔
1064
    } else {
1065
      i_stack++;
1,152✔
1066
      stack[i_stack] = model::surfaces[abs(token) - 1]->bounding_box(token > 0);
1,152✔
1067
    }
1068
  }
1069

1070
  assert(i_stack == 0);
96✔
1071
  return stack.front();
256✔
1072
}
128✔
1073

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

1076
vector<int32_t> Region::surfaces() const
5,378✔
1077
{
1078
  if (simple_) {
5,378✔
1079
    return expression_;
5,378✔
1080
  }
1081

UNCOV
1082
  vector<int32_t> surfaces = expression_;
×
1083

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

UNCOV
1087
  while (it != surfaces.end()) {
×
1088
    surfaces.erase(it);
×
1089

UNCOV
1090
    it = std::find_if(surfaces.begin(), surfaces.end(),
×
1091
      [&](const auto& value) { return value >= OP_UNION; });
×
1092
  }
1093

1094
  return surfaces;
×
1095
}
1096

1097
//==============================================================================
1098
// Non-method functions
1099
//==============================================================================
1100

1101
void read_cells(pugi::xml_node node)
7,380✔
1102
{
1103
  // Count the number of cells.
1104
  int n_cells = 0;
7,380✔
1105
  for (pugi::xml_node cell_node : node.children("cell")) {
40,279✔
1106
    n_cells++;
32,899✔
1107
  }
1108

1109
  // Loop over XML cell elements and populate the array.
1110
  model::cells.reserve(n_cells);
7,380✔
1111
  for (pugi::xml_node cell_node : node.children("cell")) {
40,279✔
1112
    model::cells.push_back(make_unique<CSGCell>(cell_node));
32,899✔
1113
  }
1114

1115
  // Fill the cell map.
1116
  for (int i = 0; i < model::cells.size(); i++) {
40,279✔
1117
    int32_t id = model::cells[i]->id_;
32,899✔
1118
    auto search = model::cell_map.find(id);
32,899✔
1119
    if (search == model::cell_map.end()) {
32,899✔
1120
      model::cell_map[id] = i;
32,899✔
1121
    } else {
UNCOV
1122
      fatal_error(
×
UNCOV
1123
        fmt::format("Two or more cells use the same unique ID: {}", id));
×
1124
    }
1125
  }
1126

1127
  read_dagmc_universes(node);
7,380✔
1128

1129
  populate_universes();
7,378✔
1130

1131
  // Allocate the cell overlap count if necessary.
1132
  if (settings::check_overlaps) {
7,378✔
1133
    model::overlap_check_count.resize(model::cells.size(), 0);
276✔
1134
  }
1135

1136
  if (model::cells.size() == 0) {
7,378✔
UNCOV
1137
    fatal_error("No cells were found in the geometry.xml file");
×
1138
  }
1139
}
7,378✔
1140

1141
void populate_universes()
7,380✔
1142
{
1143
  // Used to map universe index to the index of an implicit complement cell for
1144
  // DAGMC universes
1145
  std::unordered_map<int, int> implicit_comp_cells;
7,380✔
1146

1147
  // Populate the Universe vector and map.
1148
  for (int index_cell = 0; index_cell < model::cells.size(); index_cell++) {
40,552✔
1149
    int32_t uid = model::cells[index_cell]->universe_;
33,172✔
1150
    auto it = model::universe_map.find(uid);
33,172✔
1151
    if (it == model::universe_map.end()) {
33,172✔
1152
      model::universes.push_back(make_unique<Universe>());
18,690✔
1153
      model::universes.back()->id_ = uid;
18,690✔
1154
      model::universes.back()->cells_.push_back(index_cell);
18,690✔
1155
      model::universe_map[uid] = model::universes.size() - 1;
18,690✔
1156
    } else {
1157
#ifdef OPENMC_DAGMC_ENABLED
1158
      // Skip implicit complement cells for now
1159
      Universe* univ = model::universes[it->second].get();
1,894✔
1160
      DAGUniverse* dag_univ = dynamic_cast<DAGUniverse*>(univ);
1,894✔
1161
      if (dag_univ && (dag_univ->implicit_complement_idx() == index_cell)) {
1,894✔
1162
        implicit_comp_cells[it->second] = index_cell;
56✔
1163
        continue;
56✔
1164
      }
1165
#endif
1166

1167
      model::universes[it->second]->cells_.push_back(index_cell);
14,426✔
1168
    }
1169
  }
1170

1171
  // Add DAGUniverse implicit complement cells last
1172
  for (const auto& it : implicit_comp_cells) {
7,436✔
1173
    int index_univ = it.first;
56✔
1174
    int index_cell = it.second;
56✔
1175
    model::universes[index_univ]->cells_.push_back(index_cell);
56✔
1176
  }
1177

1178
  model::universes.shrink_to_fit();
7,380✔
1179
}
7,380✔
1180

1181
//==============================================================================
1182
// C-API functions
1183
//==============================================================================
1184

1185
extern "C" int openmc_cell_get_fill(
299✔
1186
  int32_t index, int* type, int32_t** indices, int32_t* n)
1187
{
1188
  if (index >= 0 && index < model::cells.size()) {
299✔
1189
    Cell& c {*model::cells[index]};
299✔
1190
    *type = static_cast<int>(c.type_);
299✔
1191
    if (c.type_ == Fill::MATERIAL) {
299✔
1192
      *indices = c.material_.data();
299✔
1193
      *n = c.material_.size();
299✔
1194
    } else {
UNCOV
1195
      *indices = &c.fill_;
×
UNCOV
1196
      *n = 1;
×
1197
    }
1198
  } else {
1199
    set_errmsg("Index in cells array is out of bounds.");
×
1200
    return OPENMC_E_OUT_OF_BOUNDS;
×
1201
  }
1202
  return 0;
299✔
1203
}
1204

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

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

1246
  int32_t instance_index = instance ? *instance : -1;
96✔
1247
  try {
1248
    model::cells[index]->set_temperature(T, instance_index, set_contained);
96✔
UNCOV
1249
  } catch (const std::exception& e) {
×
UNCOV
1250
    set_errmsg(e.what());
×
UNCOV
1251
    return OPENMC_E_UNASSIGNED;
×
UNCOV
1252
  }
×
1253
  return 0;
96✔
1254
}
1255

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

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

1274
extern "C" int openmc_cell_get_temperature(
102✔
1275
  int32_t index, const int32_t* instance, double* T)
1276
{
1277
  if (index < 0 || index >= model::cells.size()) {
102✔
UNCOV
1278
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
UNCOV
1279
    return OPENMC_E_OUT_OF_BOUNDS;
×
1280
  }
1281

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

1292
extern "C" int openmc_cell_get_density(
72✔
1293
  int32_t index, const int32_t* instance, double* rho)
1294
{
1295
  if (index < 0 || index >= model::cells.size()) {
72✔
NEW
1296
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
NEW
1297
    return OPENMC_E_OUT_OF_BOUNDS;
×
1298
  }
1299

1300
  int32_t instance_index = instance ? *instance : -1;
72✔
1301
  try {
1302
    if (model::cells[index]->type_ != Fill::MATERIAL) {
72✔
NEW
1303
      fatal_error(
×
NEW
1304
        fmt::format("Cell {}, instance {} is not filled with a material.",
×
NEW
1305
          model::cells[index]->id_, instance_index));
×
1306
    }
1307

1308
    int32_t mat_index = model::cells[index]->material(instance_index);
72✔
1309
    if (mat_index == MATERIAL_VOID) {
72✔
NEW
1310
      *rho = 0.0;
×
1311
    } else {
1312
      *rho = model::cells[index]->density_mult(instance_index) *
144✔
1313
             model::materials[mat_index]->density_gpcc();
72✔
1314
    }
NEW
1315
  } catch (const std::exception& e) {
×
NEW
1316
    set_errmsg(e.what());
×
NEW
1317
    return OPENMC_E_UNASSIGNED;
×
NEW
1318
  }
×
1319
  return 0;
72✔
1320
}
1321

1322
//! Get the bounding box of a cell
1323
extern "C" int openmc_cell_bounding_box(
160✔
1324
  const int32_t index, double* llc, double* urc)
1325
{
1326

1327
  BoundingBox bbox;
160✔
1328

1329
  const auto& c = model::cells[index];
160✔
1330
  bbox = c->bounding_box();
160✔
1331

1332
  // set lower left corner values
1333
  llc[0] = bbox.xmin;
160✔
1334
  llc[1] = bbox.ymin;
160✔
1335
  llc[2] = bbox.zmin;
160✔
1336

1337
  // set upper right corner values
1338
  urc[0] = bbox.xmax;
160✔
1339
  urc[1] = bbox.ymax;
160✔
1340
  urc[2] = bbox.zmax;
160✔
1341

1342
  return 0;
160✔
1343
}
1344

1345
//! Get the name of a cell
1346
extern "C" int openmc_cell_get_name(int32_t index, const char** name)
24✔
1347
{
1348
  if (index < 0 || index >= model::cells.size()) {
24✔
UNCOV
1349
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1350
    return OPENMC_E_OUT_OF_BOUNDS;
×
1351
  }
1352

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

1355
  return 0;
24✔
1356
}
1357

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

1366
  model::cells[index]->set_name(name);
12✔
1367

1368
  return 0;
12✔
1369
}
1370

1371
//==============================================================================
1372
//! Define a containing (parent) cell
1373
//==============================================================================
1374

1375
//! Used to locate a universe fill in the geometry
1376
struct ParentCell {
1377
  bool operator==(const ParentCell& other) const
144✔
1378
  {
1379
    return cell_index == other.cell_index &&
288✔
1380
           lattice_index == other.lattice_index;
288✔
1381
  }
1382

1383
  bool operator<(const ParentCell& other) const
1384
  {
1385
    return cell_index < other.cell_index ||
1386
           (cell_index == other.cell_index &&
1387
             lattice_index < other.lattice_index);
1388
  }
1389

1390
  int64_t cell_index;
1391
  int64_t lattice_index;
1392
};
1393

1394
//! Structure used to insert ParentCell into hashed STL data structures
1395
struct ParentCellHash {
1396
  std::size_t operator()(const ParentCell& p) const
673✔
1397
  {
1398
    return 4096 * p.cell_index + p.lattice_index;
673✔
1399
  }
1400
};
1401

1402
//! Used to manage a traversal stack when locating parent cells of a cell
1403
//! instance in the model
1404
struct ParentCellStack {
1405

1406
  //! push method that adds to the parent_cells visited cells for this search
1407
  //! universe
1408
  void push(int32_t search_universe, const ParentCell& pc)
112✔
1409
  {
1410
    parent_cells_.push_back(pc);
112✔
1411
    // add parent cell to the set of cells we've visited for this search
1412
    // universe
1413
    visited_cells_[search_universe].insert(pc);
112✔
1414
  }
112✔
1415

1416
  //! removes the last parent_cell and clears the visited cells for the popped
1417
  //! cell's universe
1418
  void pop()
80✔
1419
  {
1420
    visited_cells_[this->current_univ()].clear();
80✔
1421
    parent_cells_.pop_back();
80✔
1422
  }
80✔
1423

1424
  //! checks whether or not the parent cell has been visited already for this
1425
  //! search universe
1426
  bool visited(int32_t search_universe, const ParentCell& parent_cell)
561✔
1427
  {
1428
    return visited_cells_[search_universe].count(parent_cell) != 0;
561✔
1429
  }
1430

1431
  //! return the next universe to search for a parent cell
1432
  int32_t current_univ() const
80✔
1433
  {
1434
    return model::cells[parent_cells_.back().cell_index]->universe_;
80✔
1435
  }
1436

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

1440
  //! compute an instance for the provided distribcell index
1441
  int32_t compute_instance(int32_t distribcell_index) const
193✔
1442
  {
1443
    if (distribcell_index == C_NONE)
193✔
1444
      return 0;
65✔
1445

1446
    int32_t instance = 0;
128✔
1447
    for (const auto& parent_cell : this->parent_cells_) {
240✔
1448
      auto& cell = model::cells[parent_cell.cell_index];
112✔
1449
      if (cell->type_ == Fill::UNIVERSE) {
112✔
UNCOV
1450
        instance += cell->offset_[distribcell_index];
×
1451
      } else if (cell->type_ == Fill::LATTICE) {
112✔
1452
        auto& lattice = model::lattices[cell->fill_];
112✔
1453
        instance +=
112✔
1454
          lattice->offset(distribcell_index, parent_cell.lattice_index);
112✔
1455
      }
1456
    }
1457
    return instance;
128✔
1458
  }
1459

1460
  // Accessors
1461
  vector<ParentCell>& parent_cells() { return parent_cells_; }
339✔
1462
  const vector<ParentCell>& parent_cells() const { return parent_cells_; }
1463

1464
  // Data Members
1465
  vector<ParentCell> parent_cells_;
1466
  std::unordered_map<int32_t, std::unordered_set<ParentCell, ParentCellHash>>
1467
    visited_cells_;
1468
};
1469

UNCOV
1470
vector<ParentCell> Cell::find_parent_cells(
×
1471
  int32_t instance, const Position& r) const
1472
{
1473

1474
  // create a temporary particle
UNCOV
1475
  GeometryState dummy_particle {};
×
UNCOV
1476
  dummy_particle.r() = r;
×
UNCOV
1477
  dummy_particle.u() = {0., 0., 1.};
×
1478

1479
  return find_parent_cells(instance, dummy_particle);
×
1480
}
1481

UNCOV
1482
vector<ParentCell> Cell::find_parent_cells(
×
1483
  int32_t instance, GeometryState& p) const
1484
{
1485
  // look up the particle's location
1486
  exhaustive_find_cell(p);
×
UNCOV
1487
  const auto& coords = p.coord();
×
1488

1489
  // build a parent cell stack from the particle coordinates
1490
  ParentCellStack stack;
×
1491
  bool cell_found = false;
×
UNCOV
1492
  for (auto it = coords.begin(); it != coords.end(); it++) {
×
UNCOV
1493
    const auto& coord = *it;
×
1494
    const auto& cell = model::cells[coord.cell()];
×
1495
    // if the cell at this level matches the current cell, stop adding to the
1496
    // stack
1497
    if (coord.cell() == model::cell_map[this->id_]) {
×
1498
      cell_found = true;
×
UNCOV
1499
      break;
×
1500
    }
1501

1502
    // if filled with a lattice, get the lattice index from the next
1503
    // level in the coordinates to push to the stack
UNCOV
1504
    int lattice_idx = C_NONE;
×
UNCOV
1505
    if (cell->type_ == Fill::LATTICE) {
×
UNCOV
1506
      const auto& next_coord = *(it + 1);
×
UNCOV
1507
      lattice_idx = model::lattices[next_coord.lattice()]->get_flat_index(
×
1508
        next_coord.lattice_index());
1509
    }
1510
    stack.push(coord.universe(), {coord.cell(), lattice_idx});
×
1511
  }
1512

1513
  // if this loop finished because the cell was found and
1514
  // the instance matches the one requested in the call
1515
  // we have the correct path and can return the stack
UNCOV
1516
  if (cell_found &&
×
UNCOV
1517
      stack.compute_instance(this->distribcell_index_) == instance) {
×
UNCOV
1518
    return stack.parent_cells();
×
1519
  }
1520

1521
  // fall back on an exhaustive search for the cell's parents
1522
  return exhaustive_find_parent_cells(instance);
×
1523
}
1524

1525
vector<ParentCell> Cell::exhaustive_find_parent_cells(int32_t instance) const
113✔
1526
{
1527
  ParentCellStack stack;
113✔
1528
  // start with this cell's universe
1529
  int32_t prev_univ_idx;
1530
  int32_t univ_idx = this->universe_;
113✔
1531

1532
  while (true) {
1533
    const auto& univ = model::universes[univ_idx];
193✔
1534
    prev_univ_idx = univ_idx;
193✔
1535

1536
    // search for a cell that is filled w/ this universe
1537
    for (const auto& cell : model::cells) {
1,236✔
1538
      // if this is a material-filled cell, move on
1539
      if (cell->type_ == Fill::MATERIAL)
1,155✔
1540
        continue;
642✔
1541

1542
      if (cell->type_ == Fill::UNIVERSE) {
513✔
1543
        // if this is in the set of cells previously visited for this universe,
1544
        // move on
1545
        if (stack.visited(univ_idx, {model::cell_map[cell->id_], C_NONE}))
305✔
UNCOV
1546
          continue;
×
1547

1548
        // if this cell contains the universe we're searching for, add it to the
1549
        // stack
1550
        if (cell->fill_ == univ_idx) {
305✔
UNCOV
1551
          stack.push(univ_idx, {model::cell_map[cell->id_], C_NONE});
×
UNCOV
1552
          univ_idx = cell->universe_;
×
1553
        }
1554
      } else if (cell->type_ == Fill::LATTICE) {
208✔
1555
        // retrieve the lattice and lattice universes
1556
        const auto& lattice = model::lattices[cell->fill_];
208✔
1557
        const auto& lattice_univs = lattice->universes_;
208✔
1558

1559
        // start search for universe
1560
        auto lat_it = lattice_univs.begin();
208✔
1561
        while (true) {
1562
          // find the next lattice cell with this universe
1563
          lat_it = std::find(lat_it, lattice_univs.end(), univ_idx);
352✔
1564
          if (lat_it == lattice_univs.end())
352✔
1565
            break;
96✔
1566

1567
          int lattice_idx = lat_it - lattice_univs.begin();
256✔
1568

1569
          // move iterator forward one to avoid finding the same entry
1570
          lat_it++;
256✔
1571
          if (stack.visited(
256✔
1572
                univ_idx, {model::cell_map[cell->id_], lattice_idx}))
256✔
1573
            continue;
144✔
1574

1575
          // add this cell and lattice index to the stack and exit loop
1576
          stack.push(univ_idx, {model::cell_map[cell->id_], lattice_idx});
112✔
1577
          univ_idx = cell->universe_;
112✔
1578
          break;
112✔
1579
        }
144✔
1580
      }
1581
      // if we've updated the universe, break
1582
      if (prev_univ_idx != univ_idx)
513✔
1583
        break;
112✔
1584
    } // end cell loop search for universe
1585

1586
    // if we're at the top of the geometry and the instance matches, we're done
1587
    if (univ_idx == model::root_universe &&
386✔
1588
        stack.compute_instance(this->distribcell_index_) == instance)
193✔
1589
      break;
113✔
1590

1591
    // if there is no match on the original cell's universe, report an error
1592
    if (univ_idx == this->universe_) {
80✔
UNCOV
1593
      fatal_error(
×
UNCOV
1594
        fmt::format("Could not find the parent cells for cell {}, instance {}.",
×
UNCOV
1595
          this->id_, instance));
×
1596
    }
1597

1598
    // if we don't find a suitable update, adjust the stack and continue
1599
    if (univ_idx == model::root_universe || univ_idx == prev_univ_idx) {
80✔
1600
      stack.pop();
80✔
1601
      univ_idx = stack.empty() ? this->universe_ : stack.current_univ();
80✔
1602
    }
1603

1604
  } // end while
80✔
1605

1606
  // reverse the stack so the highest cell comes first
1607
  std::reverse(stack.parent_cells().begin(), stack.parent_cells().end());
113✔
1608
  return stack.parent_cells();
226✔
1609
}
113✔
1610

1611
std::unordered_map<int32_t, vector<int32_t>> Cell::get_contained_cells(
161✔
1612
  int32_t instance, Position* hint) const
1613
{
1614
  std::unordered_map<int32_t, vector<int32_t>> contained_cells;
161✔
1615

1616
  // if this is a material-filled cell it has no contained cells
1617
  if (this->type_ == Fill::MATERIAL)
161✔
1618
    return contained_cells;
48✔
1619

1620
  // find the pathway through the geometry to this cell
1621
  vector<ParentCell> parent_cells;
113✔
1622

1623
  // if a positional hint is provided, attempt to do a fast lookup
1624
  // of the parent cells
1625
  parent_cells = hint ? find_parent_cells(instance, *hint)
226✔
1626
                      : exhaustive_find_parent_cells(instance);
113✔
1627

1628
  // if this cell is filled w/ a material, it contains no other cells
1629
  if (type_ != Fill::MATERIAL) {
113✔
1630
    this->get_contained_cells_inner(contained_cells, parent_cells);
113✔
1631
  }
1632

1633
  return contained_cells;
113✔
1634
}
113✔
1635

1636
//! Get all cells within this cell
1637
void Cell::get_contained_cells_inner(
87,763✔
1638
  std::unordered_map<int32_t, vector<int32_t>>& contained_cells,
1639
  vector<ParentCell>& parent_cells) const
1640
{
1641

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

1685
//! Return the index in the cells array of a cell with a given ID
1686
extern "C" int openmc_get_cell_index(int32_t id, int32_t* index)
678✔
1687
{
1688
  auto it = model::cell_map.find(id);
678✔
1689
  if (it != model::cell_map.end()) {
678✔
1690
    *index = it->second;
666✔
1691
    return 0;
666✔
1692
  } else {
1693
    set_errmsg("No cell exists with ID=" + std::to_string(id) + ".");
12✔
1694
    return OPENMC_E_INVALID_ID;
12✔
1695
  }
1696
}
1697

1698
//! Return the ID of a cell
1699
extern "C" int openmc_cell_get_id(int32_t index, int32_t* id)
601,646✔
1700
{
1701
  if (index >= 0 && index < model::cells.size()) {
601,646✔
1702
    *id = model::cells[index]->id_;
601,646✔
1703
    return 0;
601,646✔
1704
  } else {
UNCOV
1705
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1706
    return OPENMC_E_OUT_OF_BOUNDS;
×
1707
  }
1708
}
1709

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

1723
//! Return the translation vector of a cell
1724
extern "C" int openmc_cell_get_translation(int32_t index, double xyz[])
60✔
1725
{
1726
  if (index >= 0 && index < model::cells.size()) {
60✔
1727
    auto& cell = model::cells[index];
60✔
1728
    xyz[0] = cell->translation_.x;
60✔
1729
    xyz[1] = cell->translation_.y;
60✔
1730
    xyz[2] = cell->translation_.z;
60✔
1731
    return 0;
60✔
1732
  } else {
UNCOV
1733
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1734
    return OPENMC_E_OUT_OF_BOUNDS;
×
1735
  }
1736
}
1737

1738
//! Set the translation vector of a cell
1739
extern "C" int openmc_cell_set_translation(int32_t index, const double xyz[])
36✔
1740
{
1741
  if (index >= 0 && index < model::cells.size()) {
36✔
1742
    if (model::cells[index]->fill_ == C_NONE) {
36✔
1743
      set_errmsg(fmt::format("Cannot apply a translation to cell {}"
12✔
1744
                             " because it is not filled with another universe",
1745
        index));
1746
      return OPENMC_E_GEOMETRY;
12✔
1747
    }
1748
    model::cells[index]->translation_ = Position(xyz);
24✔
1749
    return 0;
24✔
1750
  } else {
UNCOV
1751
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1752
    return OPENMC_E_OUT_OF_BOUNDS;
×
1753
  }
1754
}
1755

1756
//! Return the rotation matrix of a cell
1757
extern "C" int openmc_cell_get_rotation(int32_t index, double rot[], size_t* n)
60✔
1758
{
1759
  if (index >= 0 && index < model::cells.size()) {
60✔
1760
    auto& cell = model::cells[index];
60✔
1761
    *n = cell->rotation_.size();
60✔
1762
    std::memcpy(rot, cell->rotation_.data(), *n * sizeof(cell->rotation_[0]));
60✔
1763
    return 0;
60✔
1764
  } else {
UNCOV
1765
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1766
    return OPENMC_E_OUT_OF_BOUNDS;
×
1767
  }
1768
}
1769

1770
//! Set the flattened rotation matrix of a cell
1771
extern "C" int openmc_cell_set_rotation(
48✔
1772
  int32_t index, const double rot[], size_t rot_len)
1773
{
1774
  if (index >= 0 && index < model::cells.size()) {
48✔
1775
    if (model::cells[index]->fill_ == C_NONE) {
48✔
1776
      set_errmsg(fmt::format("Cannot apply a rotation to cell {}"
12✔
1777
                             " because it is not filled with another universe",
1778
        index));
1779
      return OPENMC_E_GEOMETRY;
12✔
1780
    }
1781
    std::vector<double> vec_rot(rot, rot + rot_len);
36✔
1782
    model::cells[index]->set_rotation(vec_rot);
36✔
1783
    return 0;
36✔
1784
  } else {
36✔
UNCOV
1785
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1786
    return OPENMC_E_OUT_OF_BOUNDS;
×
1787
  }
1788
}
1789

1790
//! Get the number of instances of the requested cell
1791
extern "C" int openmc_cell_get_num_instances(
12✔
1792
  int32_t index, int32_t* num_instances)
1793
{
1794
  if (index < 0 || index >= model::cells.size()) {
12✔
UNCOV
1795
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1796
    return OPENMC_E_OUT_OF_BOUNDS;
×
1797
  }
1798
  *num_instances = model::cells[index]->n_instances();
12✔
1799
  return 0;
12✔
1800
}
1801

1802
//! Extend the cells array by n elements
1803
extern "C" int openmc_extend_cells(
24✔
1804
  int32_t n, int32_t* index_start, int32_t* index_end)
1805
{
1806
  if (index_start)
24✔
1807
    *index_start = model::cells.size();
24✔
1808
  if (index_end)
24✔
UNCOV
1809
    *index_end = model::cells.size() + n - 1;
×
1810
  for (int32_t i = 0; i < n; i++) {
48✔
1811
    model::cells.push_back(make_unique<CSGCell>());
24✔
1812
  }
1813
  return 0;
24✔
1814
}
1815

1816
extern "C" int cells_size()
48✔
1817
{
1818
  return model::cells.size();
48✔
1819
}
1820

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