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

openmc-dev / openmc / 13344433530

15 Feb 2025 11:03AM UTC coverage: 84.938% (-0.03%) from 84.969%
13344433530

Pull #3305

github

web-flow
Merge 49da803a6 into 11587786e
Pull Request #3305: New Feature Development: Introduction of the CLSCell Class( #3286 )

4 of 26 new or added lines in 1 file covered. (15.38%)

218 existing lines in 6 files now uncovered.

50245 of 59155 relevant lines covered (84.94%)

35373994.5 hits per line

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

77.5
/src/cell.cpp
1

2
#include "openmc/cell.h"
3

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

12
#include <fmt/core.h>
13
#include <gsl/gsl-lite.hpp>
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
void Cell::set_rotation(const vector<double>& rot)
478✔
44
{
45
  if (fill_ == C_NONE) {
478✔
46
    fatal_error(fmt::format("Cannot apply a rotation to cell {}"
×
47
                            " because it is not filled with another universe",
48
      id_));
×
49
  }
50

51
  if (rot.size() != 3 && rot.size() != 9) {
478✔
52
    fatal_error(fmt::format("Non-3D rotation vector applied to cell {}", id_));
×
53
  }
54

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

76
    // When user specifies angles, write them at end of vector
77
    rotation_.push_back(rot[0]);
478✔
78
    rotation_.push_back(rot[1]);
478✔
79
    rotation_.push_back(rot[2]);
478✔
80
  } else {
81
    std::copy(rot.begin(), rot.end(), std::back_inserter(rotation_));
×
82
  }
83
}
478✔
84

85
double Cell::temperature(int32_t instance) const
105✔
86
{
87
  if (sqrtkT_.size() < 1) {
105✔
88
    throw std::runtime_error {"Cell temperature has not yet been set."};
×
89
  }
90

91
  if (instance >= 0) {
105✔
92
    double sqrtkT = sqrtkT_.size() == 1 ? sqrtkT_.at(0) : sqrtkT_.at(instance);
12✔
93
    return sqrtkT * sqrtkT / K_BOLTZMANN;
12✔
94
  } else {
95
    return sqrtkT_[0] * sqrtkT_[0] / K_BOLTZMANN;
93✔
96
  }
97
}
98

99
void Cell::set_temperature(double T, int32_t instance, bool set_contained)
493✔
100
{
101
  if (settings::temperature_method == TemperatureMethod::INTERPOLATION) {
493✔
102
    if (T < (data::temperature_min - settings::temperature_tolerance)) {
×
103
      throw std::runtime_error {
×
104
        fmt::format("Temperature of {} K is below minimum temperature at "
×
105
                    "which data is available of {} K.",
106
          T, data::temperature_min)};
×
107
    } else if (T > (data::temperature_max + settings::temperature_tolerance)) {
×
108
      throw std::runtime_error {
×
109
        fmt::format("Temperature of {} K is above maximum temperature at "
×
110
                    "which data is available of {} K.",
111
          T, data::temperature_max)};
×
112
    }
113
  }
114

115
  if (type_ == Fill::MATERIAL) {
493✔
116
    if (instance >= 0) {
459✔
117
      // If temperature vector is not big enough, resize it first
118
      if (sqrtkT_.size() != n_instances_)
375✔
119
        sqrtkT_.resize(n_instances_, sqrtkT_[0]);
51✔
120

121
      // Set temperature for the corresponding instance
122
      sqrtkT_.at(instance) = std::sqrt(K_BOLTZMANN * T);
375✔
123
    } else {
124
      // Set temperature for all instances
125
      for (auto& T_ : sqrtkT_) {
168✔
126
        T_ = std::sqrt(K_BOLTZMANN * T);
84✔
127
      }
128
    }
129
  } else {
130
    if (!set_contained) {
34✔
131
      throw std::runtime_error {
×
132
        fmt::format("Attempted to set the temperature of cell {} "
×
133
                    "which is not filled by a material.",
134
          id_)};
×
135
    }
136

137
    auto contained_cells = this->get_contained_cells(instance);
34✔
138
    for (const auto& entry : contained_cells) {
136✔
139
      auto& cell = model::cells[entry.first];
102✔
140
      Expects(cell->type_ == Fill::MATERIAL);
102✔
141
      auto& instances = entry.second;
102✔
142
      for (auto instance : instances) {
357✔
143
        cell->set_temperature(T, instance);
255✔
144
      }
145
    }
146
  }
34✔
147
}
493✔
148

149
void Cell::export_properties_hdf5(hid_t group) const
132✔
150
{
151
  // Create a group for this cell.
152
  auto cell_group = create_group(group, fmt::format("cell {}", id_));
264✔
153

154
  // Write temperature in [K] for one or more cell instances
155
  vector<double> temps;
132✔
156
  for (auto sqrtkT_val : sqrtkT_)
240✔
157
    temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
108✔
158
  write_dataset(cell_group, "temperature", temps);
132✔
159

160
  close_group(cell_group);
132✔
161
}
132✔
162

163
void Cell::import_properties_hdf5(hid_t group)
156✔
164
{
165
  auto cell_group = open_group(group, fmt::format("cell {}", id_));
312✔
166

167
  // Read temperatures from file
168
  vector<double> temps;
156✔
169
  read_dataset(cell_group, "temperature", temps);
156✔
170

171
  // Ensure number of temperatures makes sense
172
  auto n_temps = temps.size();
156✔
173
  if (n_temps > 1 && n_temps != n_instances_) {
156✔
174
    throw std::runtime_error(fmt::format(
×
175
      "Number of temperatures for cell {} doesn't match number of instances",
176
      id_));
×
177
  }
178

179
  // Modify temperatures for the cell
180
  sqrtkT_.clear();
156✔
181
  sqrtkT_.resize(temps.size());
156✔
182
  for (gsl::index i = 0; i < temps.size(); ++i) {
264✔
183
    this->set_temperature(temps[i], i);
108✔
184
  }
185

186
  close_group(cell_group);
156✔
187
}
156✔
188

189
void Cell::to_hdf5(hid_t cell_group) const
25,529✔
190
{
191

192
  // Create a group for this cell.
193
  auto group = create_group(cell_group, fmt::format("cell {}", id_));
51,058✔
194

195
  if (!name_.empty()) {
25,529✔
196
    write_string(group, "name", name_, false);
4,382✔
197
  }
198

199
  write_dataset(group, "universe", model::universes[universe_]->id_);
25,529✔
200

201
  to_hdf5_inner(group);
25,529✔
202

203
  // Write fill information.
204
  if (type_ == Fill::MATERIAL) {
25,529✔
205
    write_dataset(group, "fill_type", "material");
20,694✔
206
    std::vector<int32_t> mat_ids;
20,694✔
207
    for (auto i_mat : material_) {
43,737✔
208
      if (i_mat != MATERIAL_VOID) {
23,043✔
209
        mat_ids.push_back(model::materials[i_mat]->id_);
15,142✔
210
      } else {
211
        mat_ids.push_back(MATERIAL_VOID);
7,901✔
212
      }
213
    }
214
    if (mat_ids.size() == 1) {
20,694✔
215
      write_dataset(group, "material", mat_ids[0]);
20,391✔
216
    } else {
217
      write_dataset(group, "material", mat_ids);
303✔
218
    }
219

220
    std::vector<double> temps;
20,694✔
221
    for (auto sqrtkT_val : sqrtkT_)
43,872✔
222
      temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
23,178✔
223
    write_dataset(group, "temperature", temps);
20,694✔
224

225
  } else if (type_ == Fill::UNIVERSE) {
25,529✔
226
    write_dataset(group, "fill_type", "universe");
3,639✔
227
    write_dataset(group, "fill", model::universes[fill_]->id_);
3,639✔
228
    if (translation_ != Position(0, 0, 0)) {
3,639✔
229
      write_dataset(group, "translation", translation_);
2,004✔
230
    }
231
    if (!rotation_.empty()) {
3,639✔
232
      if (rotation_.size() == 12) {
288✔
233
        std::array<double, 3> rot {rotation_[9], rotation_[10], rotation_[11]};
288✔
234
        write_dataset(group, "rotation", rot);
288✔
235
      } else {
236
        write_dataset(group, "rotation", rotation_);
×
237
      }
238
    }
239

240
  } else if (type_ == Fill::LATTICE) {
1,196✔
241
    write_dataset(group, "fill_type", "lattice");
1,196✔
242
    write_dataset(group, "lattice", model::lattices[fill_]->id_);
1,196✔
243
  }
244

245
  close_group(group);
25,529✔
246
}
25,529✔
247

248
//==============================================================================
249
// CSGCell implementation
250
//==============================================================================
251

252
// default constructor
253
CSGCell::CSGCell()
24✔
254
{
255
  geom_type() = GeometryType::CSG;
24✔
256
}
24✔
257

258
CSGCell::CSGCell(pugi::xml_node cell_node)
31,349✔
259
{
260
  geom_type() = GeometryType::CSG;
31,349✔
261

262
  if (check_for_node(cell_node, "id")) {
31,349✔
263
    id_ = std::stoi(get_node_value(cell_node, "id"));
31,349✔
264
  } else {
265
    fatal_error("Must specify id of cell in geometry XML file.");
×
266
  }
267

268
  if (check_for_node(cell_node, "name")) {
31,349✔
269
    name_ = get_node_value(cell_node, "name");
6,079✔
270
  }
271

272
  if (check_for_node(cell_node, "universe")) {
31,349✔
273
    universe_ = std::stoi(get_node_value(cell_node, "universe"));
30,020✔
274
  } else {
275
    universe_ = 0;
1,329✔
276
  }
277

278
  // Make sure that either material or fill was specified, but not both.
279
  bool fill_present = check_for_node(cell_node, "fill");
31,349✔
280
  bool material_present = check_for_node(cell_node, "material");
31,349✔
281
  if (!(fill_present || material_present)) {
31,349✔
282
    fatal_error(
×
283
      fmt::format("Neither material nor fill was specified for cell {}", id_));
×
284
  }
285
  if (fill_present && material_present) {
31,349✔
286
    fatal_error(fmt::format("Cell {} has both a material and a fill specified; "
×
287
                            "only one can be specified per cell",
288
      id_));
×
289
  }
290

291
  if (fill_present) {
31,349✔
292
    fill_ = std::stoi(get_node_value(cell_node, "fill"));
6,552✔
293
    if (fill_ == universe_) {
6,552✔
294
      fatal_error(fmt::format("Cell {} is filled with the same universe that "
×
295
                              "it is contained in.",
296
        id_));
×
297
    }
298
  } else {
299
    fill_ = C_NONE;
24,797✔
300
  }
301

302
  // Read the material element.  There can be zero materials (filled with a
303
  // universe), more than one material (distribmats), and some materials may
304
  // be "void".
305
  if (material_present) {
31,349✔
306
    vector<std::string> mats {
307
      get_node_array<std::string>(cell_node, "material", true)};
24,797✔
308
    if (mats.size() > 0) {
24,797✔
309
      material_.reserve(mats.size());
24,797✔
310
      for (std::string mat : mats) {
51,949✔
311
        if (mat.compare("void") == 0) {
27,152✔
312
          material_.push_back(MATERIAL_VOID);
8,175✔
313
        } else {
314
          material_.push_back(std::stoi(mat));
18,977✔
315
        }
316
      }
27,152✔
317
    } else {
318
      fatal_error(fmt::format(
×
319
        "An empty material element was specified for cell {}", id_));
×
320
    }
321
  }
24,797✔
322

323
  // Read the temperature element which may be distributed like materials.
324
  if (check_for_node(cell_node, "temperature")) {
31,349✔
325
    sqrtkT_ = get_node_array<double>(cell_node, "temperature");
335✔
326
    sqrtkT_.shrink_to_fit();
335✔
327

328
    // Make sure this is a material-filled cell.
329
    if (material_.size() == 0) {
335✔
330
      fatal_error(fmt::format(
×
331
        "Cell {} was specified with a temperature but no material. Temperature"
332
        "specification is only valid for cells filled with a material.",
333
        id_));
×
334
    }
335

336
    // Make sure all temperatures are non-negative.
337
    for (auto T : sqrtkT_) {
721✔
338
      if (T < 0) {
386✔
339
        fatal_error(fmt::format(
×
340
          "Cell {} was specified with a negative temperature", id_));
×
341
      }
342
    }
343

344
    // Convert to sqrt(k*T).
345
    for (auto& T : sqrtkT_) {
721✔
346
      T = std::sqrt(K_BOLTZMANN * T);
386✔
347
    }
348
  }
349

350
  // Read the region specification.
351
  std::string region_spec;
31,349✔
352
  if (check_for_node(cell_node, "region")) {
31,349✔
353
    region_spec = get_node_value(cell_node, "region");
22,975✔
354
  }
355

356
  // Get a tokenized representation of the region specification and apply De
357
  // Morgans law
358
  Region region(region_spec, id_);
31,349✔
359
  region_ = region;
31,349✔
360

361
  // Read the translation vector.
362
  if (check_for_node(cell_node, "translation")) {
31,349✔
363
    if (fill_ == C_NONE) {
2,897✔
364
      fatal_error(fmt::format("Cannot apply a translation to cell {}"
×
365
                              " because it is not filled with another universe",
366
        id_));
×
367
    }
368

369
    auto xyz {get_node_array<double>(cell_node, "translation")};
2,897✔
370
    if (xyz.size() != 3) {
2,897✔
371
      fatal_error(
×
372
        fmt::format("Non-3D translation vector applied to cell {}", id_));
×
373
    }
374
    translation_ = xyz;
2,897✔
375
  }
2,897✔
376

377
  // Read the rotation transform.
378
  if (check_for_node(cell_node, "rotation")) {
31,349✔
379
    auto rot {get_node_array<double>(cell_node, "rotation")};
442✔
380
    set_rotation(rot);
442✔
381
  }
442✔
382
}
31,349✔
383

384
//==============================================================================
385

386
void CSGCell::to_hdf5_inner(hid_t group_id) const
25,388✔
387
{
388
  write_string(group_id, "geom_type", "csg", false);
25,388✔
389
  write_string(group_id, "region", region_.str(), false);
25,388✔
390
}
25,388✔
391

392
//==============================================================================
393

394
vector<int32_t>::iterator CSGCell::find_left_parenthesis(
×
395
  vector<int32_t>::iterator start, const vector<int32_t>& infix)
396
{
397
  // start search at zero
398
  int parenthesis_level = 0;
×
399
  auto it = start;
×
400
  while (it != infix.begin()) {
×
401
    // look at two tokens at a time
402
    int32_t one = *it;
×
403
    int32_t two = *(it - 1);
×
404

405
    // decrement parenthesis level if there are two adjacent surfaces
406
    if (one < OP_UNION && two < OP_UNION) {
×
407
      parenthesis_level--;
×
408
      // increment if there are two adjacent operators
409
    } else if (one >= OP_UNION && two >= OP_UNION) {
×
410
      parenthesis_level++;
×
411
    }
412

413
    // if the level gets to zero, return the position
414
    if (parenthesis_level == 0) {
×
415
      // move the iterator back one before leaving the loop
416
      // so that all tokens in the parenthesis block are included
417
      it--;
×
418
      break;
×
419
    }
420

421
    // continue loop, one token at a time
422
    it--;
×
423
  }
424
  return it;
×
425
}
426
//==============================================================================
427
// CLSCell implementation
428
//==============================================================================
429
// default constructor
NEW
UNCOV
430
CLSCell::CLSCell()
×
431
{
NEW
UNCOV
432
  geom_type() = GeometryType::CLS;
×
433
}
434

NEW
UNCOV
435
CLSCell::CLSCell(pugi::xml_node cell_node)
×
NEW
UNCOV
436
  : CSGCell(cell_node) // Initialize base Cell properties (id, material, etc.)
×
437
{
NEW
UNCOV
438
  geom_type() = GeometryType::CLS;
×
439
  // --- Parse CLS-Specific Parameters ---
NEW
UNCOV
440
  auto dispersion_node = cell_node.child("dispersion");
×
441

442
  // Boolean flag to enable/disable CLS for this cell
NEW
UNCOV
443
  bool is_dispersed = dispersion_node.attribute("enable").as_bool(false);
×
444

445
  // Particle parameters (conditional parsing)
NEW
UNCOV
446
  if (is_dispersed) {
×
NEW
UNCOV
447
    particle_radius_ = dispersion_node.attribute("particle_radius").as_double();
×
NEW
UNCOV
448
    fill_ratio_ = dispersion_node.attribute("fill_ratio").as_double();
×
449

NEW
UNCOV
450
    validate_parameters(); // Ensure 0 < fill_ratio < 1 and radius > 0
×
451
  }
452
}
453

NEW
UNCOV
454
void CLSCell::validate_parameters() const
×
455
{
456
  // Check that fill_ratio is between 0 and 1
NEW
UNCOV
457
  if (fill_ratio_ <= 0 || fill_ratio_ >= 1) {
×
NEW
UNCOV
458
    throw std::runtime_error("CLSCell: fill_ratio must be beteween (0, 1)");
×
459
  }
460
  // Check that particle_radius is positive
NEW
UNCOV
461
  if (particle_radius_ <= 0) {
×
NEW
UNCOV
462
    throw std::runtime_error("CLSCell: particle_radius must be positive");
×
463
  }
464
  // Check that the cell is filled with a sphere universe
NEW
UNCOV
465
  if (type_ != Fill::UNIVERSE) {
×
NEW
UNCOV
466
    throw std::runtime_error(
×
NEW
UNCOV
467
      "CLSCell: cell must be filled with a sphere universe");
×
468
  }
469
}
470

471
//==============================================================================
472
// Region implementation
473
//==============================================================================
474

475
Region::Region(std::string region_spec, int32_t cell_id)
31,349✔
476
{
477
  // Check if region_spec is not empty.
478
  if (!region_spec.empty()) {
31,349✔
479
    // Parse all halfspaces and operators except for intersection (whitespace).
480
    for (int i = 0; i < region_spec.size();) {
130,570✔
481
      if (region_spec[i] == '(') {
107,595✔
482
        expression_.push_back(OP_LEFT_PAREN);
1,100✔
483
        i++;
1,100✔
484

485
      } else if (region_spec[i] == ')') {
106,495✔
486
        expression_.push_back(OP_RIGHT_PAREN);
1,100✔
487
        i++;
1,100✔
488

489
      } else if (region_spec[i] == '|') {
105,395✔
490
        expression_.push_back(OP_UNION);
2,830✔
491
        i++;
2,830✔
492

493
      } else if (region_spec[i] == '~') {
102,565✔
494
        expression_.push_back(OP_COMPLEMENT);
34✔
495
        i++;
34✔
496

497
      } else if (region_spec[i] == '-' || region_spec[i] == '+' ||
172,724✔
498
                 std::isdigit(region_spec[i])) {
70,193✔
499
        // This is the start of a halfspace specification.  Iterate j until we
500
        // find the end, then push-back everything between i and j.
501
        int j = i + 1;
60,493✔
502
        while (j < region_spec.size() && std::isdigit(region_spec[j])) {
123,377✔
503
          j++;
62,884✔
504
        }
505
        expression_.push_back(std::stoi(region_spec.substr(i, j - i)));
60,493✔
506
        i = j;
60,493✔
507

508
      } else if (std::isspace(region_spec[i])) {
42,038✔
509
        i++;
42,038✔
510

511
      } else {
512
        auto err_msg =
513
          fmt::format("Region specification contains invalid character, \"{}\"",
UNCOV
514
            region_spec[i]);
×
UNCOV
515
        fatal_error(err_msg);
×
UNCOV
516
      }
×
517
    }
518

519
    // Add in intersection operators where a missing operator is needed.
520
    int i = 0;
22,975✔
521
    while (i < expression_.size() - 1) {
100,245✔
522
      bool left_compat {
523
        (expression_[i] < OP_UNION) || (expression_[i] == OP_RIGHT_PAREN)};
77,270✔
524
      bool right_compat {(expression_[i + 1] < OP_UNION) ||
77,270✔
525
                         (expression_[i + 1] == OP_LEFT_PAREN) ||
81,268✔
526
                         (expression_[i + 1] == OP_COMPLEMENT)};
3,998✔
527
      if (left_compat && right_compat) {
77,270✔
528
        expression_.insert(expression_.begin() + i + 1, OP_INTERSECTION);
34,688✔
529
      }
530
      i++;
77,270✔
531
    }
532

533
    // Remove complement operators using DeMorgan's laws
534
    auto it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
22,975✔
535
    while (it != expression_.end()) {
23,009✔
536
      // Erase complement
537
      expression_.erase(it);
34✔
538

539
      // Define stop given left parenthesis or not
540
      auto stop = it;
34✔
541
      if (*it == OP_LEFT_PAREN) {
34✔
542
        int depth = 1;
34✔
543
        do {
544
          stop++;
272✔
545
          if (*stop > OP_COMPLEMENT) {
272✔
546
            if (*stop == OP_RIGHT_PAREN) {
34✔
547
              depth--;
34✔
548
            } else {
UNCOV
549
              depth++;
×
550
            }
551
          }
552
        } while (depth > 0);
272✔
553
        it++;
34✔
554
      }
555

556
      // apply DeMorgan's law to any surfaces/operators between these
557
      // positions in the RPN
558
      apply_demorgan(it, stop);
34✔
559
      // update iterator position
560
      it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
34✔
561
    }
562

563
    // Convert user IDs to surface indices.
564
    for (auto& r : expression_) {
123,186✔
565
      if (r < OP_UNION) {
100,211✔
566
        const auto& it {model::surface_map.find(abs(r))};
60,493✔
567
        if (it == model::surface_map.end()) {
60,493✔
568
          throw std::runtime_error {
×
UNCOV
569
            "Invalid surface ID " + std::to_string(abs(r)) +
×
UNCOV
570
            " specified in region for cell " + std::to_string(cell_id) + "."};
×
571
        }
572
        r = (r > 0) ? it->second + 1 : -(it->second + 1);
60,493✔
573
      }
574
    }
575

576
    // Check if this is a simple cell.
577
    simple_ = true;
22,975✔
578
    for (int32_t token : expression_) {
112,532✔
579
      if (token == OP_UNION) {
90,441✔
580
        simple_ = false;
884✔
581
        // Ensure intersections have precedence over unions
582
        add_precedence();
884✔
583
        break;
884✔
584
      }
585
    }
586

587
    // If this cell is simple, remove all the superfluous operator tokens.
588
    if (simple_) {
22,975✔
589
      for (auto it = expression_.begin(); it != expression_.end(); it++) {
107,066✔
590
        if (*it == OP_INTERSECTION || *it > OP_COMPLEMENT) {
84,975✔
591
          expression_.erase(it);
31,442✔
592
          it--;
31,442✔
593
        }
594
      }
595
    }
596
    expression_.shrink_to_fit();
22,975✔
597

598
  } else {
599
    simple_ = true;
8,374✔
600
  }
601
}
31,349✔
602

603
//==============================================================================
604

605
void Region::apply_demorgan(
238✔
606
  vector<int32_t>::iterator start, vector<int32_t>::iterator stop)
607
{
608
  do {
609
    if (*start < OP_UNION) {
238✔
610
      *start *= -1;
136✔
611
    } else if (*start == OP_UNION) {
102✔
612
      *start = OP_INTERSECTION;
×
613
    } else if (*start == OP_INTERSECTION) {
102✔
614
      *start = OP_UNION;
102✔
615
    }
616
    start++;
238✔
617
  } while (start < stop);
238✔
618
}
34✔
619

620
//==============================================================================
621
//! Add precedence for infix regions so intersections have higher
622
//! precedence than unions using parentheses.
623
//==============================================================================
624

625
gsl::index Region::add_parentheses(gsl::index start)
34✔
626
{
627
  int32_t start_token = expression_[start];
34✔
628
  // Add left parenthesis and set new position to be after parenthesis
629
  if (start_token == OP_UNION) {
34✔
UNCOV
630
    start += 2;
×
631
  }
632
  expression_.insert(expression_.begin() + start - 1, OP_LEFT_PAREN);
34✔
633

634
  // Keep track of return iterator distance. If we don't encounter a left
635
  // parenthesis, we return an iterator corresponding to wherever the right
636
  // parenthesis is inserted. If a left parenthesis is encountered, an iterator
637
  // corresponding to the left parenthesis is returned. Also note that we keep
638
  // track of a *distance* instead of an iterator because the underlying memory
639
  // allocation may change.
640
  std::size_t return_it_dist = 0;
34✔
641

642
  // Add right parenthesis
643
  // While the start iterator is within the bounds of infix
644
  while (start + 1 < expression_.size()) {
238✔
645
    start++;
238✔
646

647
    // If the current token is an operator and is different than the start token
648
    if (expression_[start] >= OP_UNION && expression_[start] != start_token) {
238✔
649
      // Skip wrapped regions but save iterator position to check precedence and
650
      // add right parenthesis, right parenthesis position depends on the
651
      // operator, when the operator is a union then do not include the operator
652
      // in the region, when the operator is an intersection then include the
653
      // operator and next surface
654
      if (expression_[start] == OP_LEFT_PAREN) {
34✔
UNCOV
655
        return_it_dist = start;
×
UNCOV
656
        int depth = 1;
×
657
        do {
UNCOV
658
          start++;
×
UNCOV
659
          if (expression_[start] > OP_COMPLEMENT) {
×
UNCOV
660
            if (expression_[start] == OP_RIGHT_PAREN) {
×
UNCOV
661
              depth--;
×
662
            } else {
UNCOV
663
              depth++;
×
664
            }
665
          }
UNCOV
666
        } while (depth > 0);
×
667
      } else {
668
        if (start_token == OP_UNION) {
34✔
UNCOV
669
          --start;
×
670
        }
671
        expression_.insert(expression_.begin() + start, OP_RIGHT_PAREN);
34✔
672
        if (return_it_dist > 0) {
34✔
UNCOV
673
          return return_it_dist;
×
674
        } else {
675
          return start - 1;
34✔
676
        }
677
      }
678
    }
679
  }
680
  // If we get here a right parenthesis hasn't been placed,
681
  // return iterator
UNCOV
682
  expression_.push_back(OP_RIGHT_PAREN);
×
UNCOV
683
  if (return_it_dist > 0) {
×
UNCOV
684
    return return_it_dist;
×
685
  } else {
UNCOV
686
    return start - 1;
×
687
  }
688
}
689

690
//==============================================================================
691

692
void Region::add_precedence()
884✔
693
{
694
  int32_t current_op = 0;
884✔
695
  std::size_t current_dist = 0;
884✔
696

697
  for (gsl::index i = 0; i < expression_.size(); i++) {
16,086✔
698
    int32_t token = expression_[i];
15,202✔
699

700
    if (token == OP_UNION || token == OP_INTERSECTION) {
15,202✔
701
      if (current_op == 0) {
6,059✔
702
        // Set the current operator if is hasn't been set
703
        current_op = token;
2,069✔
704
        current_dist = i;
2,069✔
705
      } else if (token != current_op) {
3,990✔
706
        // If the current operator doesn't match the token, add parenthesis to
707
        // assert precedence
708
        if (current_op == OP_INTERSECTION) {
34✔
709
          i = add_parentheses(current_dist);
17✔
710
        } else {
711
          i = add_parentheses(i);
17✔
712
        }
713
        current_op = 0;
34✔
714
        current_dist = 0;
34✔
715
      }
716
    } else if (token > OP_COMPLEMENT) {
9,143✔
717
      // If the token is a parenthesis reset the current operator
718
      current_op = 0;
2,234✔
719
      current_dist = 0;
2,234✔
720
    }
721
  }
722
}
884✔
723

724
//==============================================================================
725
//! Convert infix region specification to Reverse Polish Notation (RPN)
726
//!
727
//! This function uses the shunting-yard algorithm.
728
//==============================================================================
729

730
vector<int32_t> Region::generate_postfix(int32_t cell_id) const
48✔
731
{
732
  vector<int32_t> rpn;
48✔
733
  vector<int32_t> stack;
48✔
734

735
  for (int32_t token : expression_) {
1,080✔
736
    if (token < OP_UNION) {
1,032✔
737
      // If token is not an operator, add it to output
738
      rpn.push_back(token);
432✔
739
    } else if (token < OP_RIGHT_PAREN) {
600✔
740
      // Regular operators union, intersection, complement
741
      while (stack.size() > 0) {
612✔
742
        int32_t op = stack.back();
504✔
743

744
        if (op < OP_RIGHT_PAREN && ((token == OP_COMPLEMENT && token < op) ||
504✔
745
                                     (token != OP_COMPLEMENT && token <= op))) {
228✔
746
          // While there is an operator, op, on top of the stack, if the token
747
          // is left-associative and its precedence is less than or equal to
748
          // that of op or if the token is right-associative and its precedence
749
          // is less than that of op, move op to the output queue and push the
750
          // token on to the stack. Note that only complement is
751
          // right-associative.
752
          rpn.push_back(op);
228✔
753
          stack.pop_back();
228✔
754
        } else {
755
          break;
756
        }
757
      }
758

759
      stack.push_back(token);
384✔
760

761
    } else if (token == OP_LEFT_PAREN) {
216✔
762
      // If the token is a left parenthesis, push it onto the stack
763
      stack.push_back(token);
108✔
764

765
    } else {
766
      // If the token is a right parenthesis, move operators from the stack to
767
      // the output queue until reaching the left parenthesis.
768
      for (auto it = stack.rbegin(); *it != OP_LEFT_PAREN; it++) {
216✔
769
        // If we run out of operators without finding a left parenthesis, it
770
        // means there are mismatched parentheses.
771
        if (it == stack.rend()) {
108✔
UNCOV
772
          fatal_error(fmt::format(
×
773
            "Mismatched parentheses in region specification for cell {}",
774
            cell_id));
775
        }
776
        rpn.push_back(stack.back());
108✔
777
        stack.pop_back();
108✔
778
      }
779

780
      // Pop the left parenthesis.
781
      stack.pop_back();
108✔
782
    }
783
  }
784

785
  while (stack.size() > 0) {
96✔
786
    int32_t op = stack.back();
48✔
787

788
    // If the operator is a parenthesis it is mismatched.
789
    if (op >= OP_RIGHT_PAREN) {
48✔
UNCOV
790
      fatal_error(fmt::format(
×
791
        "Mismatched parentheses in region specification for cell {}", cell_id));
792
    }
793

794
    rpn.push_back(stack.back());
48✔
795
    stack.pop_back();
48✔
796
  }
797

798
  return rpn;
96✔
799
}
48✔
800

801
//==============================================================================
802

803
std::string Region::str() const
25,388✔
804
{
805
  std::stringstream region_spec {};
25,388✔
806
  if (!expression_.empty()) {
25,388✔
807
    for (int32_t token : expression_) {
72,540✔
808
      if (token == OP_LEFT_PAREN) {
54,904✔
809
        region_spec << " (";
988✔
810
      } else if (token == OP_RIGHT_PAREN) {
53,916✔
811
        region_spec << " )";
988✔
812
      } else if (token == OP_COMPLEMENT) {
52,928✔
UNCOV
813
        region_spec << " ~";
×
814
      } else if (token == OP_INTERSECTION) {
52,928✔
815
      } else if (token == OP_UNION) {
50,192✔
816
        region_spec << " |";
2,622✔
817
      } else {
818
        // Note the off-by-one indexing
819
        auto surf_id = model::surfaces[abs(token) - 1]->id_;
47,570✔
820
        region_spec << " " << ((token > 0) ? surf_id : -surf_id);
47,570✔
821
      }
822
    }
823
  }
824
  return region_spec.str();
50,776✔
825
}
25,388✔
826

827
//==============================================================================
828

829
std::pair<double, int32_t> Region::distance(
2,147,483,647✔
830
  Position r, Direction u, int32_t on_surface) const
831
{
832
  double min_dist {INFTY};
2,147,483,647✔
833
  int32_t i_surf {std::numeric_limits<int32_t>::max()};
2,147,483,647✔
834

835
  for (int32_t token : expression_) {
2,147,483,647✔
836
    // Ignore this token if it corresponds to an operator rather than a region.
837
    if (token >= OP_UNION)
2,147,483,647✔
838
      continue;
176,973,483✔
839

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

845
    // Check if this distance is the new minimum.
846
    if (d < min_dist) {
2,147,483,647✔
847
      if (min_dist - d >= FP_PRECISION * min_dist) {
2,147,483,647✔
848
        min_dist = d;
2,147,483,647✔
849
        i_surf = -token;
2,147,483,647✔
850
      }
851
    }
852
  }
853

854
  return {min_dist, i_surf};
2,147,483,647✔
855
}
856

857
//==============================================================================
858

859
bool Region::contains(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
860
{
861
  if (simple_) {
2,147,483,647✔
862
    return contains_simple(r, u, on_surface);
2,147,483,647✔
863
  } else {
864
    return contains_complex(r, u, on_surface);
6,342,631✔
865
  }
866
}
867

868
//==============================================================================
869

870
bool Region::contains_simple(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
871
{
872
  for (int32_t token : expression_) {
2,147,483,647✔
873
    // Assume that no tokens are operators. Evaluate the sense of particle with
874
    // respect to the surface and see if the token matches the sense. If the
875
    // particle's surface attribute is set and matches the token, that
876
    // overrides the determination based on sense().
877
    if (token == on_surface) {
2,147,483,647✔
878
    } else if (-token == on_surface) {
2,147,483,647✔
879
      return false;
1,034,582,436✔
880
    } else {
881
      // Note the off-by-one indexing
882
      bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
2,147,483,647✔
883
      if (sense != (token > 0)) {
2,147,483,647✔
884
        return false;
895,304,900✔
885
      }
886
    }
887
  }
888
  return true;
2,147,483,647✔
889
}
890

891
//==============================================================================
892

893
bool Region::contains_complex(Position r, Direction u, int32_t on_surface) const
6,342,631✔
894
{
895
  bool in_cell = true;
6,342,631✔
896
  int total_depth = 0;
6,342,631✔
897

898
  // For each token
899
  for (auto it = expression_.begin(); it != expression_.end(); it++) {
101,537,886✔
900
    int32_t token = *it;
96,946,465✔
901

902
    // If the token is a surface evaluate the sense
903
    // If the token is a union or intersection check to
904
    // short circuit
905
    if (token < OP_UNION) {
96,946,465✔
906
      if (token == on_surface) {
42,414,173✔
907
        in_cell = true;
3,498,860✔
908
      } else if (-token == on_surface) {
38,915,313✔
909
        in_cell = false;
708,801✔
910
      } else {
911
        // Note the off-by-one indexing
912
        bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
38,206,512✔
913
        in_cell = (sense == (token > 0));
38,206,512✔
914
      }
915
    } else if ((token == OP_UNION && in_cell == true) ||
54,532,292✔
916
               (token == OP_INTERSECTION && in_cell == false)) {
26,758,315✔
917
      // If the total depth is zero return
918
      if (total_depth == 0) {
5,934,309✔
919
        return in_cell;
1,751,210✔
920
      }
921

922
      total_depth--;
4,183,099✔
923

924
      // While the iterator is within the bounds of the vector
925
      int depth = 1;
4,183,099✔
926
      do {
927
        // Get next token
928
        it++;
25,360,742✔
929
        int32_t next_token = *it;
25,360,742✔
930

931
        // If the token is an a parenthesis
932
        if (next_token > OP_COMPLEMENT) {
25,360,742✔
933
          // Adjust depth accordingly
934
          if (next_token == OP_RIGHT_PAREN) {
4,387,711✔
935
            depth--;
4,285,405✔
936
          } else {
937
            depth++;
102,306✔
938
          }
939
        }
940
      } while (depth > 0);
25,360,742✔
941
    } else if (token == OP_LEFT_PAREN) {
52,781,082✔
942
      total_depth++;
8,354,770✔
943
    } else if (token == OP_RIGHT_PAREN) {
40,243,213✔
944
      total_depth--;
4,171,671✔
945
    }
946
  }
947
  return in_cell;
4,591,421✔
948
}
949

950
//==============================================================================
951

952
BoundingBox Region::bounding_box(int32_t cell_id) const
96✔
953
{
954
  if (simple_) {
96✔
955
    return bounding_box_simple();
48✔
956
  } else {
957
    auto postfix = generate_postfix(cell_id);
48✔
958
    return bounding_box_complex(postfix);
48✔
959
  }
48✔
960
}
961

962
//==============================================================================
963

964
BoundingBox Region::bounding_box_simple() const
48✔
965
{
966
  BoundingBox bbox;
48✔
967
  for (int32_t token : expression_) {
192✔
968
    bbox &= model::surfaces[abs(token) - 1]->bounding_box(token > 0);
144✔
969
  }
970
  return bbox;
48✔
971
}
972

973
//==============================================================================
974

975
BoundingBox Region::bounding_box_complex(vector<int32_t> postfix) const
48✔
976
{
977
  vector<BoundingBox> stack(postfix.size());
48✔
978
  int i_stack = -1;
48✔
979

980
  for (auto& token : postfix) {
864✔
981
    if (token == OP_UNION) {
816✔
982
      stack[i_stack - 1] = stack[i_stack - 1] | stack[i_stack];
168✔
983
      i_stack--;
168✔
984
    } else if (token == OP_INTERSECTION) {
648✔
985
      stack[i_stack - 1] = stack[i_stack - 1] & stack[i_stack];
216✔
986
      i_stack--;
216✔
987
    } else {
988
      i_stack++;
432✔
989
      stack[i_stack] = model::surfaces[abs(token) - 1]->bounding_box(token > 0);
432✔
990
    }
991
  }
992

993
  Ensures(i_stack == 0);
48✔
994
  return stack.front();
96✔
995
}
48✔
996

997
//==============================================================================
998

999
vector<int32_t> Region::surfaces() const
5,853✔
1000
{
1001
  if (simple_) {
5,853✔
1002
    return expression_;
5,853✔
1003
  }
1004

UNCOV
1005
  vector<int32_t> surfaces = expression_;
×
1006

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

UNCOV
1010
  while (it != surfaces.end()) {
×
UNCOV
1011
    surfaces.erase(it);
×
1012

UNCOV
1013
    it = std::find_if(surfaces.begin(), surfaces.end(),
×
UNCOV
1014
      [&](const auto& value) { return value >= OP_UNION; });
×
1015
  }
1016

UNCOV
1017
  return surfaces;
×
1018
}
1019

1020
//==============================================================================
1021
// Non-method functions
1022
//==============================================================================
1023

1024
void read_cells(pugi::xml_node node)
6,869✔
1025
{
1026
  // Count the number of cells.
1027
  int n_cells = 0;
6,869✔
1028
  for (pugi::xml_node cell_node : node.children("cell")) {
38,218✔
1029
    n_cells++;
31,349✔
1030
  }
1031

1032
  // Loop over XML cell elements and populate the array.
1033
  model::cells.reserve(n_cells);
6,869✔
1034
  for (pugi::xml_node cell_node : node.children("cell")) {
38,218✔
1035
    auto dispersion_node = cell_node.child("dispersion");
31,349✔
1036
    bool is_dispersed = dispersion_node;
31,349✔
1037

1038
    if (is_dispersed) {
31,349✔
NEW
UNCOV
1039
      auto cell = make_unique<CLSCell>(cell_node);
×
NEW
UNCOV
1040
      model::cells.push_back(std::move(cell));
×
NEW
UNCOV
1041
    } else {
×
1042
      model::cells.push_back(make_unique<CSGCell>(cell_node));
31,349✔
1043
    }
1044
  }
1045

1046
  // Fill the cell map.
1047
  for (int i = 0; i < model::cells.size(); i++) {
38,218✔
1048
    int32_t id = model::cells[i]->id_;
31,349✔
1049
    auto search = model::cell_map.find(id);
31,349✔
1050
    if (search == model::cell_map.end()) {
31,349✔
1051
      model::cell_map[id] = i;
31,349✔
1052
    } else {
UNCOV
1053
      fatal_error(
×
UNCOV
1054
        fmt::format("Two or more cells use the same unique ID: {}", id));
×
1055
    }
1056
  }
1057

1058
  read_dagmc_universes(node);
6,869✔
1059

1060
  populate_universes();
6,867✔
1061

1062
  // Allocate the cell overlap count if necessary.
1063
  if (settings::check_overlaps) {
6,867✔
1064
    model::overlap_check_count.resize(model::cells.size(), 0);
286✔
1065
  }
1066

1067
  if (model::cells.size() == 0) {
6,867✔
UNCOV
1068
    fatal_error("No cells were found in the geometry.xml file");
×
1069
  }
1070
}
6,867✔
1071

1072
void populate_universes()
6,869✔
1073
{
1074
  // Used to map universe index to the index of an implicit complement cell for
1075
  // DAGMC universes
1076
  std::unordered_map<int, int> implicit_comp_cells;
6,869✔
1077

1078
  // Populate the Universe vector and map.
1079
  for (int index_cell = 0; index_cell < model::cells.size(); index_cell++) {
38,405✔
1080
    int32_t uid = model::cells[index_cell]->universe_;
31,536✔
1081
    auto it = model::universe_map.find(uid);
31,536✔
1082
    if (it == model::universe_map.end()) {
31,536✔
1083
      model::universes.push_back(make_unique<Universe>());
17,794✔
1084
      model::universes.back()->id_ = uid;
17,794✔
1085
      model::universes.back()->cells_.push_back(index_cell);
17,794✔
1086
      model::universe_map[uid] = model::universes.size() - 1;
17,794✔
1087
    } else {
1088
#ifdef DAGMC
1089
      // Skip implicit complement cells for now
1090
      Universe* univ = model::universes[it->second].get();
1,715✔
1091
      DAGUniverse* dag_univ = dynamic_cast<DAGUniverse*>(univ);
1,715✔
1092
      if (dag_univ && (dag_univ->implicit_complement_idx() == index_cell)) {
1,715✔
1093
        implicit_comp_cells[it->second] = index_cell;
37✔
1094
        continue;
37✔
1095
      }
1096
#endif
1097

1098
      model::universes[it->second]->cells_.push_back(index_cell);
13,705✔
1099
    }
1100
  }
1101

1102
  // Add DAGUniverse implicit complement cells last
1103
  for (const auto& it : implicit_comp_cells) {
6,906✔
1104
    int index_univ = it.first;
37✔
1105
    int index_cell = it.second;
37✔
1106
    model::universes[index_univ]->cells_.push_back(index_cell);
37✔
1107
  }
1108

1109
  model::universes.shrink_to_fit();
6,869✔
1110
}
6,869✔
1111

1112
//==============================================================================
1113
// C-API functions
1114
//==============================================================================
1115

1116
extern "C" int openmc_cell_get_fill(
177✔
1117
  int32_t index, int* type, int32_t** indices, int32_t* n)
1118
{
1119
  if (index >= 0 && index < model::cells.size()) {
177✔
1120
    Cell& c {*model::cells[index]};
177✔
1121
    *type = static_cast<int>(c.type_);
177✔
1122
    if (c.type_ == Fill::MATERIAL) {
177✔
1123
      *indices = c.material_.data();
177✔
1124
      *n = c.material_.size();
177✔
1125
    } else {
UNCOV
1126
      *indices = &c.fill_;
×
UNCOV
1127
      *n = 1;
×
1128
    }
1129
  } else {
1130
    set_errmsg("Index in cells array is out of bounds.");
×
1131
    return OPENMC_E_OUT_OF_BOUNDS;
×
1132
  }
1133
  return 0;
177✔
1134
}
1135

1136
extern "C" int openmc_cell_set_fill(
12✔
1137
  int32_t index, int type, int32_t n, const int32_t* indices)
1138
{
1139
  Fill filltype = static_cast<Fill>(type);
12✔
1140
  if (index >= 0 && index < model::cells.size()) {
12✔
1141
    Cell& c {*model::cells[index]};
12✔
1142
    if (filltype == Fill::MATERIAL) {
12✔
1143
      c.type_ = Fill::MATERIAL;
12✔
1144
      c.material_.clear();
12✔
1145
      for (int i = 0; i < n; i++) {
24✔
1146
        int i_mat = indices[i];
12✔
1147
        if (i_mat == MATERIAL_VOID) {
12✔
1148
          c.material_.push_back(MATERIAL_VOID);
×
1149
        } else if (i_mat >= 0 && i_mat < model::materials.size()) {
12✔
1150
          c.material_.push_back(i_mat);
12✔
1151
        } else {
UNCOV
1152
          set_errmsg("Index in materials array is out of bounds.");
×
UNCOV
1153
          return OPENMC_E_OUT_OF_BOUNDS;
×
1154
        }
1155
      }
1156
      c.material_.shrink_to_fit();
12✔
UNCOV
1157
    } else if (filltype == Fill::UNIVERSE) {
×
UNCOV
1158
      c.type_ = Fill::UNIVERSE;
×
1159
    } else {
UNCOV
1160
      c.type_ = Fill::LATTICE;
×
1161
    }
1162
  } else {
UNCOV
1163
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1164
    return OPENMC_E_OUT_OF_BOUNDS;
×
1165
  }
1166
  return 0;
12✔
1167
}
1168

1169
extern "C" int openmc_cell_set_temperature(
96✔
1170
  int32_t index, double T, const int32_t* instance, bool set_contained)
1171
{
1172
  if (index < 0 || index >= model::cells.size()) {
96✔
UNCOV
1173
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
UNCOV
1174
    return OPENMC_E_OUT_OF_BOUNDS;
×
1175
  }
1176

1177
  int32_t instance_index = instance ? *instance : -1;
96✔
1178
  try {
1179
    model::cells[index]->set_temperature(T, instance_index, set_contained);
96✔
1180
  } catch (const std::exception& e) {
×
1181
    set_errmsg(e.what());
×
UNCOV
1182
    return OPENMC_E_UNASSIGNED;
×
UNCOV
1183
  }
×
1184
  return 0;
96✔
1185
}
1186

1187
extern "C" int openmc_cell_get_temperature(
99✔
1188
  int32_t index, const int32_t* instance, double* T)
1189
{
1190
  if (index < 0 || index >= model::cells.size()) {
99✔
UNCOV
1191
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
UNCOV
1192
    return OPENMC_E_OUT_OF_BOUNDS;
×
1193
  }
1194

1195
  int32_t instance_index = instance ? *instance : -1;
99✔
1196
  try {
1197
    *T = model::cells[index]->temperature(instance_index);
99✔
UNCOV
1198
  } catch (const std::exception& e) {
×
UNCOV
1199
    set_errmsg(e.what());
×
UNCOV
1200
    return OPENMC_E_UNASSIGNED;
×
UNCOV
1201
  }
×
1202
  return 0;
99✔
1203
}
1204

1205
//! Get the bounding box of a cell
1206
extern "C" int openmc_cell_bounding_box(
60✔
1207
  const int32_t index, double* llc, double* urc)
1208
{
1209

1210
  BoundingBox bbox;
60✔
1211

1212
  const auto& c = model::cells[index];
60✔
1213
  bbox = c->bounding_box();
60✔
1214

1215
  // set lower left corner values
1216
  llc[0] = bbox.xmin;
60✔
1217
  llc[1] = bbox.ymin;
60✔
1218
  llc[2] = bbox.zmin;
60✔
1219

1220
  // set upper right corner values
1221
  urc[0] = bbox.xmax;
60✔
1222
  urc[1] = bbox.ymax;
60✔
1223
  urc[2] = bbox.zmax;
60✔
1224

1225
  return 0;
60✔
1226
}
1227

1228
//! Get the name of a cell
1229
extern "C" int openmc_cell_get_name(int32_t index, const char** name)
24✔
1230
{
1231
  if (index < 0 || index >= model::cells.size()) {
24✔
UNCOV
1232
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1233
    return OPENMC_E_OUT_OF_BOUNDS;
×
1234
  }
1235

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

1238
  return 0;
24✔
1239
}
1240

1241
//! Set the name of a cell
1242
extern "C" int openmc_cell_set_name(int32_t index, const char* name)
12✔
1243
{
1244
  if (index < 0 || index >= model::cells.size()) {
12✔
UNCOV
1245
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1246
    return OPENMC_E_OUT_OF_BOUNDS;
×
1247
  }
1248

1249
  model::cells[index]->set_name(name);
12✔
1250

1251
  return 0;
12✔
1252
}
1253

1254
//==============================================================================
1255
//! Define a containing (parent) cell
1256
//==============================================================================
1257

1258
//! Used to locate a universe fill in the geometry
1259
struct ParentCell {
1260
  bool operator==(const ParentCell& other) const
102✔
1261
  {
1262
    return cell_index == other.cell_index &&
204✔
1263
           lattice_index == other.lattice_index;
204✔
1264
  }
1265

1266
  bool operator<(const ParentCell& other) const
1267
  {
1268
    return cell_index < other.cell_index ||
1269
           (cell_index == other.cell_index &&
1270
             lattice_index < other.lattice_index);
1271
  }
1272

1273
  gsl::index cell_index;
1274
  gsl::index lattice_index;
1275
};
1276

1277
//! Structure used to insert ParentCell into hashed STL data structures
1278
struct ParentCellHash {
1279
  std::size_t operator()(const ParentCell& p) const
323✔
1280
  {
1281
    return 4096 * p.cell_index + p.lattice_index;
323✔
1282
  }
1283
};
1284

1285
//! Used to manage a traversal stack when locating parent cells of a cell
1286
//! instance in the model
1287
struct ParentCellStack {
1288

1289
  //! push method that adds to the parent_cells visited cells for this search
1290
  //! universe
1291
  void push(int32_t search_universe, const ParentCell& pc)
68✔
1292
  {
1293
    parent_cells_.push_back(pc);
68✔
1294
    // add parent cell to the set of cells we've visited for this search
1295
    // universe
1296
    visited_cells_[search_universe].insert(pc);
68✔
1297
  }
68✔
1298

1299
  //! removes the last parent_cell and clears the visited cells for the popped
1300
  //! cell's universe
1301
  void pop()
51✔
1302
  {
1303
    visited_cells_[this->current_univ()].clear();
51✔
1304
    parent_cells_.pop_back();
51✔
1305
  }
51✔
1306

1307
  //! checks whether or not the parent cell has been visited already for this
1308
  //! search universe
1309
  bool visited(int32_t search_universe, const ParentCell& parent_cell)
255✔
1310
  {
1311
    return visited_cells_[search_universe].count(parent_cell) != 0;
255✔
1312
  }
1313

1314
  //! return the next universe to search for a parent cell
1315
  int32_t current_univ() const
51✔
1316
  {
1317
    return model::cells[parent_cells_.back().cell_index]->universe_;
51✔
1318
  }
1319

1320
  //! indicates whether nor not parent cells are present on the stack
1321
  bool empty() const { return parent_cells_.empty(); }
51✔
1322

1323
  //! compute an instance for the provided distribcell index
1324
  int32_t compute_instance(int32_t distribcell_index) const
85✔
1325
  {
1326
    if (distribcell_index == C_NONE)
85✔
UNCOV
1327
      return 0;
×
1328

1329
    int32_t instance = 0;
85✔
1330
    for (const auto& parent_cell : this->parent_cells_) {
153✔
1331
      auto& cell = model::cells[parent_cell.cell_index];
68✔
1332
      if (cell->type_ == Fill::UNIVERSE) {
68✔
UNCOV
1333
        instance += cell->offset_[distribcell_index];
×
1334
      } else if (cell->type_ == Fill::LATTICE) {
68✔
1335
        auto& lattice = model::lattices[cell->fill_];
68✔
1336
        instance +=
68✔
1337
          lattice->offset(distribcell_index, parent_cell.lattice_index);
68✔
1338
      }
1339
    }
1340
    return instance;
85✔
1341
  }
1342

1343
  // Accessors
1344
  vector<ParentCell>& parent_cells() { return parent_cells_; }
102✔
1345
  const vector<ParentCell>& parent_cells() const { return parent_cells_; }
1346

1347
  // Data Members
1348
  vector<ParentCell> parent_cells_;
1349
  std::unordered_map<int32_t, std::unordered_set<ParentCell, ParentCellHash>>
1350
    visited_cells_;
1351
};
1352

1353
vector<ParentCell> Cell::find_parent_cells(
×
1354
  int32_t instance, const Position& r) const
1355
{
1356

1357
  // create a temporary particle
UNCOV
1358
  GeometryState dummy_particle {};
×
UNCOV
1359
  dummy_particle.r() = r;
×
UNCOV
1360
  dummy_particle.u() = {0., 0., 1.};
×
1361

UNCOV
1362
  return find_parent_cells(instance, dummy_particle);
×
1363
}
1364

UNCOV
1365
vector<ParentCell> Cell::find_parent_cells(
×
1366
  int32_t instance, GeometryState& p) const
1367
{
1368
  // look up the particle's location
UNCOV
1369
  exhaustive_find_cell(p);
×
UNCOV
1370
  const auto& coords = p.coord();
×
1371

1372
  // build a parent cell stack from the particle coordinates
UNCOV
1373
  ParentCellStack stack;
×
UNCOV
1374
  bool cell_found = false;
×
UNCOV
1375
  for (auto it = coords.begin(); it != coords.end(); it++) {
×
UNCOV
1376
    const auto& coord = *it;
×
1377
    const auto& cell = model::cells[coord.cell];
×
1378
    // if the cell at this level matches the current cell, stop adding to the
1379
    // stack
UNCOV
1380
    if (coord.cell == model::cell_map[this->id_]) {
×
UNCOV
1381
      cell_found = true;
×
1382
      break;
×
1383
    }
1384

1385
    // if filled with a lattice, get the lattice index from the next
1386
    // level in the coordinates to push to the stack
UNCOV
1387
    int lattice_idx = C_NONE;
×
UNCOV
1388
    if (cell->type_ == Fill::LATTICE) {
×
UNCOV
1389
      const auto& next_coord = *(it + 1);
×
UNCOV
1390
      lattice_idx = model::lattices[next_coord.lattice]->get_flat_index(
×
UNCOV
1391
        next_coord.lattice_i);
×
1392
    }
UNCOV
1393
    stack.push(coord.universe, {coord.cell, lattice_idx});
×
1394
  }
1395

1396
  // if this loop finished because the cell was found and
1397
  // the instance matches the one requested in the call
1398
  // we have the correct path and can return the stack
UNCOV
1399
  if (cell_found &&
×
UNCOV
1400
      stack.compute_instance(this->distribcell_index_) == instance) {
×
UNCOV
1401
    return stack.parent_cells();
×
1402
  }
1403

1404
  // fall back on an exhaustive search for the cell's parents
UNCOV
1405
  return exhaustive_find_parent_cells(instance);
×
1406
}
1407

1408
vector<ParentCell> Cell::exhaustive_find_parent_cells(int32_t instance) const
34✔
1409
{
1410
  ParentCellStack stack;
34✔
1411
  // start with this cell's universe
1412
  int32_t prev_univ_idx;
1413
  int32_t univ_idx = this->universe_;
34✔
1414

1415
  while (true) {
1416
    const auto& univ = model::universes[univ_idx];
85✔
1417
    prev_univ_idx = univ_idx;
85✔
1418

1419
    // search for a cell that is filled w/ this universe
1420
    for (const auto& cell : model::cells) {
442✔
1421
      // if this is a material-filled cell, move on
1422
      if (cell->type_ == Fill::MATERIAL)
425✔
1423
        continue;
255✔
1424

1425
      if (cell->type_ == Fill::UNIVERSE) {
170✔
1426
        // if this is in the set of cells previously visited for this universe,
1427
        // move on
1428
        if (stack.visited(univ_idx, {model::cell_map[cell->id_], C_NONE}))
85✔
UNCOV
1429
          continue;
×
1430

1431
        // if this cell contains the universe we're searching for, add it to the
1432
        // stack
1433
        if (cell->fill_ == univ_idx) {
85✔
UNCOV
1434
          stack.push(univ_idx, {model::cell_map[cell->id_], C_NONE});
×
UNCOV
1435
          univ_idx = cell->universe_;
×
1436
        }
1437
      } else if (cell->type_ == Fill::LATTICE) {
85✔
1438
        // retrieve the lattice and lattice universes
1439
        const auto& lattice = model::lattices[cell->fill_];
85✔
1440
        const auto& lattice_univs = lattice->universes_;
85✔
1441

1442
        // start search for universe
1443
        auto lat_it = lattice_univs.begin();
85✔
1444
        while (true) {
1445
          // find the next lattice cell with this universe
1446
          lat_it = std::find(lat_it, lattice_univs.end(), univ_idx);
187✔
1447
          if (lat_it == lattice_univs.end())
187✔
1448
            break;
17✔
1449

1450
          int lattice_idx = lat_it - lattice_univs.begin();
170✔
1451

1452
          // move iterator forward one to avoid finding the same entry
1453
          lat_it++;
170✔
1454
          if (stack.visited(
170✔
1455
                univ_idx, {model::cell_map[cell->id_], lattice_idx}))
170✔
1456
            continue;
102✔
1457

1458
          // add this cell and lattice index to the stack and exit loop
1459
          stack.push(univ_idx, {model::cell_map[cell->id_], lattice_idx});
68✔
1460
          univ_idx = cell->universe_;
68✔
1461
          break;
68✔
1462
        }
102✔
1463
      }
1464
      // if we've updated the universe, break
1465
      if (prev_univ_idx != univ_idx)
170✔
1466
        break;
68✔
1467
    } // end cell loop search for universe
1468

1469
    // if we're at the top of the geometry and the instance matches, we're done
1470
    if (univ_idx == model::root_universe &&
170✔
1471
        stack.compute_instance(this->distribcell_index_) == instance)
85✔
1472
      break;
34✔
1473

1474
    // if there is no match on the original cell's universe, report an error
1475
    if (univ_idx == this->universe_) {
51✔
UNCOV
1476
      fatal_error(
×
UNCOV
1477
        fmt::format("Could not find the parent cells for cell {}, instance {}.",
×
UNCOV
1478
          this->id_, instance));
×
1479
    }
1480

1481
    // if we don't find a suitable update, adjust the stack and continue
1482
    if (univ_idx == model::root_universe || univ_idx == prev_univ_idx) {
51✔
1483
      stack.pop();
51✔
1484
      univ_idx = stack.empty() ? this->universe_ : stack.current_univ();
51✔
1485
    }
1486

1487
  } // end while
51✔
1488

1489
  // reverse the stack so the highest cell comes first
1490
  std::reverse(stack.parent_cells().begin(), stack.parent_cells().end());
34✔
1491
  return stack.parent_cells();
68✔
1492
}
34✔
1493

1494
std::unordered_map<int32_t, vector<int32_t>> Cell::get_contained_cells(
85✔
1495
  int32_t instance, Position* hint) const
1496
{
1497
  std::unordered_map<int32_t, vector<int32_t>> contained_cells;
85✔
1498

1499
  // if this is a material-filled cell it has no contained cells
1500
  if (this->type_ == Fill::MATERIAL)
85✔
1501
    return contained_cells;
51✔
1502

1503
  // find the pathway through the geometry to this cell
1504
  vector<ParentCell> parent_cells;
34✔
1505

1506
  // if a positional hint is provided, attempt to do a fast lookup
1507
  // of the parent cells
1508
  parent_cells = hint ? find_parent_cells(instance, *hint)
68✔
1509
                      : exhaustive_find_parent_cells(instance);
34✔
1510

1511
  // if this cell is filled w/ a material, it contains no other cells
1512
  if (type_ != Fill::MATERIAL) {
34✔
1513
    this->get_contained_cells_inner(contained_cells, parent_cells);
34✔
1514
  }
1515

1516
  return contained_cells;
34✔
1517
}
34✔
1518

1519
//! Get all cells within this cell
1520
void Cell::get_contained_cells_inner(
357✔
1521
  std::unordered_map<int32_t, vector<int32_t>>& contained_cells,
1522
  vector<ParentCell>& parent_cells) const
1523
{
1524

1525
  // filled by material, determine instance based on parent cells
1526
  if (type_ == Fill::MATERIAL) {
357✔
1527
    int instance = 0;
255✔
1528
    if (this->distribcell_index_ >= 0) {
255✔
1529
      for (auto& parent_cell : parent_cells) {
765✔
1530
        auto& cell = model::cells[parent_cell.cell_index];
510✔
1531
        if (cell->type_ == Fill::UNIVERSE) {
510✔
1532
          instance += cell->offset_[distribcell_index_];
255✔
1533
        } else if (cell->type_ == Fill::LATTICE) {
255✔
1534
          auto& lattice = model::lattices[cell->fill_];
255✔
1535
          instance += lattice->offset(
510✔
1536
            this->distribcell_index_, parent_cell.lattice_index);
255✔
1537
        }
1538
      }
1539
    }
1540
    // add entry to contained cells
1541
    contained_cells[model::cell_map[id_]].push_back(instance);
255✔
1542
    // filled with universe, add the containing cell to the parent cells
1543
    // and recurse
1544
  } else if (type_ == Fill::UNIVERSE) {
102✔
1545
    parent_cells.push_back({model::cell_map[id_], -1});
85✔
1546
    auto& univ = model::universes[fill_];
85✔
1547
    for (auto cell_index : univ->cells_) {
340✔
1548
      auto& cell = model::cells[cell_index];
255✔
1549
      cell->get_contained_cells_inner(contained_cells, parent_cells);
255✔
1550
    }
1551
    parent_cells.pop_back();
85✔
1552
    // filled with a lattice, visit each universe in the lattice
1553
    // with a recursive call to collect the cell instances
1554
  } else if (type_ == Fill::LATTICE) {
17✔
1555
    auto& lattice = model::lattices[fill_];
17✔
1556
    for (auto i = lattice->begin(); i != lattice->end(); ++i) {
85✔
1557
      auto& univ = model::universes[*i];
68✔
1558
      parent_cells.push_back({model::cell_map[id_], i.indx_});
68✔
1559
      for (auto cell_index : univ->cells_) {
136✔
1560
        auto& cell = model::cells[cell_index];
68✔
1561
        cell->get_contained_cells_inner(contained_cells, parent_cells);
68✔
1562
      }
1563
      parent_cells.pop_back();
68✔
1564
    }
1565
  }
1566
}
357✔
1567

1568
//! Return the index in the cells array of a cell with a given ID
1569
extern "C" int openmc_get_cell_index(int32_t id, int32_t* index)
456✔
1570
{
1571
  auto it = model::cell_map.find(id);
456✔
1572
  if (it != model::cell_map.end()) {
456✔
1573
    *index = it->second;
444✔
1574
    return 0;
444✔
1575
  } else {
1576
    set_errmsg("No cell exists with ID=" + std::to_string(id) + ".");
12✔
1577
    return OPENMC_E_INVALID_ID;
12✔
1578
  }
1579
}
1580

1581
//! Return the ID of a cell
1582
extern "C" int openmc_cell_get_id(int32_t index, int32_t* id)
600,984✔
1583
{
1584
  if (index >= 0 && index < model::cells.size()) {
600,984✔
1585
    *id = model::cells[index]->id_;
600,984✔
1586
    return 0;
600,984✔
1587
  } else {
UNCOV
1588
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1589
    return OPENMC_E_OUT_OF_BOUNDS;
×
1590
  }
1591
}
1592

1593
//! Set the ID of a cell
1594
extern "C" int openmc_cell_set_id(int32_t index, int32_t id)
24✔
1595
{
1596
  if (index >= 0 && index < model::cells.size()) {
24✔
1597
    model::cells[index]->id_ = id;
24✔
1598
    model::cell_map[id] = index;
24✔
1599
    return 0;
24✔
1600
  } else {
UNCOV
1601
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1602
    return OPENMC_E_OUT_OF_BOUNDS;
×
1603
  }
1604
}
1605

1606
//! Return the translation vector of a cell
1607
extern "C" int openmc_cell_get_translation(int32_t index, double xyz[])
60✔
1608
{
1609
  if (index >= 0 && index < model::cells.size()) {
60✔
1610
    auto& cell = model::cells[index];
60✔
1611
    xyz[0] = cell->translation_.x;
60✔
1612
    xyz[1] = cell->translation_.y;
60✔
1613
    xyz[2] = cell->translation_.z;
60✔
1614
    return 0;
60✔
1615
  } else {
1616
    set_errmsg("Index in cells array is out of bounds.");
×
1617
    return OPENMC_E_OUT_OF_BOUNDS;
×
1618
  }
1619
}
1620

1621
//! Set the translation vector of a cell
1622
extern "C" int openmc_cell_set_translation(int32_t index, const double xyz[])
36✔
1623
{
1624
  if (index >= 0 && index < model::cells.size()) {
36✔
1625
    if (model::cells[index]->fill_ == C_NONE) {
36✔
1626
      set_errmsg(fmt::format("Cannot apply a translation to cell {}"
12✔
1627
                             " because it is not filled with another universe",
1628
        index));
1629
      return OPENMC_E_GEOMETRY;
12✔
1630
    }
1631
    model::cells[index]->translation_ = Position(xyz);
24✔
1632
    return 0;
24✔
1633
  } else {
UNCOV
1634
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1635
    return OPENMC_E_OUT_OF_BOUNDS;
×
1636
  }
1637
}
1638

1639
//! Return the rotation matrix of a cell
1640
extern "C" int openmc_cell_get_rotation(int32_t index, double rot[], size_t* n)
60✔
1641
{
1642
  if (index >= 0 && index < model::cells.size()) {
60✔
1643
    auto& cell = model::cells[index];
60✔
1644
    *n = cell->rotation_.size();
60✔
1645
    std::memcpy(rot, cell->rotation_.data(), *n * sizeof(cell->rotation_[0]));
60✔
1646
    return 0;
60✔
1647
  } else {
UNCOV
1648
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1649
    return OPENMC_E_OUT_OF_BOUNDS;
×
1650
  }
1651
}
1652

1653
//! Set the flattened rotation matrix of a cell
1654
extern "C" int openmc_cell_set_rotation(
48✔
1655
  int32_t index, const double rot[], size_t rot_len)
1656
{
1657
  if (index >= 0 && index < model::cells.size()) {
48✔
1658
    if (model::cells[index]->fill_ == C_NONE) {
48✔
1659
      set_errmsg(fmt::format("Cannot apply a rotation to cell {}"
12✔
1660
                             " because it is not filled with another universe",
1661
        index));
1662
      return OPENMC_E_GEOMETRY;
12✔
1663
    }
1664
    std::vector<double> vec_rot(rot, rot + rot_len);
36✔
1665
    model::cells[index]->set_rotation(vec_rot);
36✔
1666
    return 0;
36✔
1667
  } else {
36✔
UNCOV
1668
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1669
    return OPENMC_E_OUT_OF_BOUNDS;
×
1670
  }
1671
}
1672

1673
//! Get the number of instances of the requested cell
1674
extern "C" int openmc_cell_get_num_instances(
12✔
1675
  int32_t index, int32_t* num_instances)
1676
{
1677
  if (index < 0 || index >= model::cells.size()) {
12✔
UNCOV
1678
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1679
    return OPENMC_E_OUT_OF_BOUNDS;
×
1680
  }
1681
  *num_instances = model::cells[index]->n_instances_;
12✔
1682
  return 0;
12✔
1683
}
1684

1685
//! Extend the cells array by n elements
1686
extern "C" int openmc_extend_cells(
24✔
1687
  int32_t n, int32_t* index_start, int32_t* index_end)
1688
{
1689
  if (index_start)
24✔
1690
    *index_start = model::cells.size();
24✔
1691
  if (index_end)
24✔
UNCOV
1692
    *index_end = model::cells.size() + n - 1;
×
1693
  for (int32_t i = 0; i < n; i++) {
48✔
1694
    model::cells.push_back(make_unique<CSGCell>());
24✔
1695
  }
1696
  return 0;
24✔
1697
}
1698

1699
extern "C" int cells_size()
48✔
1700
{
1701
  return model::cells.size();
48✔
1702
}
1703

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

© 2025 Coveralls, Inc