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

openmc-dev / openmc / 16689981707

02 Aug 2025 04:41AM UTC coverage: 84.901% (-0.3%) from 85.155%
16689981707

Pull #3508

github

web-flow
Merge 97a72e488 into a64cc96ed
Pull Request #3508: Automate workflow for mesh- or cell-based R2S calculations

44 of 236 new or added lines in 5 files covered. (18.64%)

176 existing lines in 4 files now uncovered.

52934 of 62348 relevant lines covered (84.9%)

36162154.38 hits per line

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

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

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

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

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

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

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

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

120
  if (type_ == Fill::MATERIAL) {
459✔
121
    if (instance >= 0) {
427✔
122
      // If temperature vector is not big enough, resize it first
123
      if (sqrtkT_.size() != n_instances())
350✔
124
        sqrtkT_.resize(n_instances(), sqrtkT_[0]);
48✔
125

126
      // Set temperature for the corresponding instance
127
      sqrtkT_.at(instance) = std::sqrt(K_BOLTZMANN * T);
350✔
128
    } else {
129
      // Set temperature for all instances
130
      for (auto& T_ : sqrtkT_) {
154✔
131
        T_ = std::sqrt(K_BOLTZMANN * T);
77✔
132
      }
133
    }
134
  } else {
135
    if (!set_contained) {
32✔
UNCOV
136
      throw std::runtime_error {
×
UNCOV
137
        fmt::format("Attempted to set the temperature of cell {} "
×
138
                    "which is not filled by a material.",
UNCOV
139
          id_)};
×
140
    }
141

142
    auto contained_cells = this->get_contained_cells(instance);
32✔
143
    for (const auto& entry : contained_cells) {
128✔
144
      auto& cell = model::cells[entry.first];
96✔
145
      assert(cell->type_ == Fill::MATERIAL);
78✔
146
      auto& instances = entry.second;
96✔
147
      for (auto instance : instances) {
336✔
148
        cell->set_temperature(T, instance);
240✔
149
      }
150
    }
151
  }
32✔
152
}
459✔
153

154
void Cell::export_properties_hdf5(hid_t group) const
121✔
155
{
156
  // Create a group for this cell.
157
  auto cell_group = create_group(group, fmt::format("cell {}", id_));
242✔
158

159
  // Write temperature in [K] for one or more cell instances
160
  vector<double> temps;
121✔
161
  for (auto sqrtkT_val : sqrtkT_)
220✔
162
    temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
99✔
163
  write_dataset(cell_group, "temperature", temps);
121✔
164

165
  close_group(cell_group);
121✔
166
}
121✔
167

168
void Cell::import_properties_hdf5(hid_t group)
143✔
169
{
170
  auto cell_group = open_group(group, fmt::format("cell {}", id_));
286✔
171

172
  // Read temperatures from file
173
  vector<double> temps;
143✔
174
  read_dataset(cell_group, "temperature", temps);
143✔
175

176
  // Ensure number of temperatures makes sense
177
  auto n_temps = temps.size();
143✔
178
  if (n_temps > 1 && n_temps != n_instances()) {
143✔
UNCOV
179
    throw std::runtime_error(fmt::format(
×
180
      "Number of temperatures for cell {} doesn't match number of instances",
UNCOV
181
      id_));
×
182
  }
183

184
  // Modify temperatures for the cell
185
  sqrtkT_.clear();
143✔
186
  sqrtkT_.resize(temps.size());
143✔
187
  for (int64_t i = 0; i < temps.size(); ++i) {
242✔
188
    this->set_temperature(temps[i], i);
99✔
189
  }
190

191
  close_group(cell_group);
143✔
192
}
143✔
193

194
void Cell::to_hdf5(hid_t cell_group) const
25,296✔
195
{
196

197
  // Create a group for this cell.
198
  auto group = create_group(cell_group, fmt::format("cell {}", id_));
50,592✔
199

200
  if (!name_.empty()) {
25,296✔
201
    write_string(group, "name", name_, false);
4,861✔
202
  }
203

204
  write_dataset(group, "universe", model::universes[universe_]->id_);
25,296✔
205

206
  to_hdf5_inner(group);
25,296✔
207

208
  // Write fill information.
209
  if (type_ == Fill::MATERIAL) {
25,296✔
210
    write_dataset(group, "fill_type", "material");
20,424✔
211
    std::vector<int32_t> mat_ids;
20,424✔
212
    for (auto i_mat : material_) {
42,461✔
213
      if (i_mat != MATERIAL_VOID) {
22,037✔
214
        mat_ids.push_back(model::materials[i_mat]->id_);
14,107✔
215
      } else {
216
        mat_ids.push_back(MATERIAL_VOID);
7,930✔
217
      }
218
    }
219
    if (mat_ids.size() == 1) {
20,424✔
220
      write_dataset(group, "material", mat_ids[0]);
20,193✔
221
    } else {
222
      write_dataset(group, "material", mat_ids);
231✔
223
    }
224

225
    std::vector<double> temps;
20,424✔
226
    for (auto sqrtkT_val : sqrtkT_)
42,584✔
227
      temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
22,160✔
228
    write_dataset(group, "temperature", temps);
20,424✔
229

230
  } else if (type_ == Fill::UNIVERSE) {
25,296✔
231
    write_dataset(group, "fill_type", "universe");
3,554✔
232
    write_dataset(group, "fill", model::universes[fill_]->id_);
3,554✔
233
    if (translation_ != Position(0, 0, 0)) {
3,554✔
234
      write_dataset(group, "translation", translation_);
1,837✔
235
    }
236
    if (!rotation_.empty()) {
3,554✔
237
      if (rotation_.size() == 12) {
264✔
238
        std::array<double, 3> rot {rotation_[9], rotation_[10], rotation_[11]};
264✔
239
        write_dataset(group, "rotation", rot);
264✔
240
      } else {
UNCOV
241
        write_dataset(group, "rotation", rotation_);
×
242
      }
243
    }
244

245
  } else if (type_ == Fill::LATTICE) {
1,318✔
246
    write_dataset(group, "fill_type", "lattice");
1,318✔
247
    write_dataset(group, "lattice", model::lattices[fill_]->id_);
1,318✔
248
  }
249

250
  close_group(group);
25,296✔
251
}
25,296✔
252

253
//==============================================================================
254
// CSGCell implementation
255
//==============================================================================
256

257
CSGCell::CSGCell(pugi::xml_node cell_node)
31,631✔
258
{
259
  if (check_for_node(cell_node, "id")) {
31,631✔
260
    id_ = std::stoi(get_node_value(cell_node, "id"));
31,631✔
261
  } else {
UNCOV
262
    fatal_error("Must specify id of cell in geometry XML file.");
×
263
  }
264

265
  if (check_for_node(cell_node, "name")) {
31,631✔
266
    name_ = get_node_value(cell_node, "name");
6,880✔
267
  }
268

269
  if (check_for_node(cell_node, "universe")) {
31,631✔
270
    universe_ = std::stoi(get_node_value(cell_node, "universe"));
30,406✔
271
  } else {
272
    universe_ = 0;
1,225✔
273
  }
274

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

288
  if (fill_present) {
31,631✔
289
    fill_ = std::stoi(get_node_value(cell_node, "fill"));
6,812✔
290
    if (fill_ == universe_) {
6,812✔
UNCOV
291
      fatal_error(fmt::format("Cell {} is filled with the same universe that "
×
292
                              "it is contained in.",
UNCOV
293
        id_));
×
294
    }
295
  } else {
296
    fill_ = C_NONE;
24,819✔
297
  }
298

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

320
  // Read the temperature element which may be distributed like materials.
321
  if (check_for_node(cell_node, "temperature")) {
31,631✔
322
    sqrtkT_ = get_node_array<double>(cell_node, "temperature");
315✔
323
    sqrtkT_.shrink_to_fit();
315✔
324

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

333
    // Make sure all temperatures are non-negative.
334
    for (auto T : sqrtkT_) {
678✔
335
      if (T < 0) {
363✔
UNCOV
336
        fatal_error(fmt::format(
×
UNCOV
337
          "Cell {} was specified with a negative temperature", id_));
×
338
      }
339
    }
340

341
    // Convert to sqrt(k*T).
342
    for (auto& T : sqrtkT_) {
678✔
343
      T = std::sqrt(K_BOLTZMANN * T);
363✔
344
    }
345
  }
346

347
  // Read the region specification.
348
  std::string region_spec;
31,631✔
349
  if (check_for_node(cell_node, "region")) {
31,631✔
350
    region_spec = get_node_value(cell_node, "region");
22,868✔
351
  }
352

353
  // Get a tokenized representation of the region specification and apply De
354
  // Morgans law
355
  Region region(region_spec, id_);
31,631✔
356
  region_ = region;
31,631✔
357

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

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

374
  // Read the rotation transform.
375
  if (check_for_node(cell_node, "rotation")) {
31,631✔
376
    auto rot {get_node_array<double>(cell_node, "rotation")};
416✔
377
    set_rotation(rot);
416✔
378
  }
416✔
379
}
31,631✔
380

381
//==============================================================================
382

383
void CSGCell::to_hdf5_inner(hid_t group_id) const
25,151✔
384
{
385
  write_string(group_id, "geom_type", "csg", false);
25,151✔
386
  write_string(group_id, "region", region_.str(), false);
25,151✔
387
}
25,151✔
388

389
//==============================================================================
390

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

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

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

418
    // continue loop, one token at a time
UNCOV
419
    it--;
×
420
  }
UNCOV
421
  return it;
×
422
}
423

424
//==============================================================================
425
// Region implementation
426
//==============================================================================
427

428
Region::Region(std::string region_spec, int32_t cell_id)
31,631✔
429
{
430
  // Check if region_spec is not empty.
431
  if (!region_spec.empty()) {
31,631✔
432
    // Parse all halfspaces and operators except for intersection (whitespace).
433
    for (int i = 0; i < region_spec.size();) {
128,952✔
434
      if (region_spec[i] == '(') {
106,084✔
435
        expression_.push_back(OP_LEFT_PAREN);
1,054✔
436
        i++;
1,054✔
437

438
      } else if (region_spec[i] == ')') {
105,030✔
439
        expression_.push_back(OP_RIGHT_PAREN);
1,054✔
440
        i++;
1,054✔
441

442
      } else if (region_spec[i] == '|') {
103,976✔
443
        expression_.push_back(OP_UNION);
2,825✔
444
        i++;
2,825✔
445

446
      } else if (region_spec[i] == '~') {
101,151✔
447
        expression_.push_back(OP_COMPLEMENT);
32✔
448
        i++;
32✔
449

450
      } else if (region_spec[i] == '-' || region_spec[i] == '+' ||
170,220✔
451
                 std::isdigit(region_spec[i])) {
69,101✔
452
        // This is the start of a halfspace specification.  Iterate j until we
453
        // find the end, then push-back everything between i and j.
454
        int j = i + 1;
59,786✔
455
        while (j < region_spec.size() && std::isdigit(region_spec[j])) {
122,184✔
456
          j++;
62,398✔
457
        }
458
        expression_.push_back(std::stoi(region_spec.substr(i, j - i)));
59,786✔
459
        i = j;
59,786✔
460

461
      } else if (std::isspace(region_spec[i])) {
41,333✔
462
        i++;
41,333✔
463

464
      } else {
465
        auto err_msg =
466
          fmt::format("Region specification contains invalid character, \"{}\"",
UNCOV
467
            region_spec[i]);
×
UNCOV
468
        fatal_error(err_msg);
×
UNCOV
469
      }
×
470
    }
471

472
    // Add in intersection operators where a missing operator is needed.
473
    int i = 0;
22,868✔
474
    while (i < expression_.size() - 1) {
98,844✔
475
      bool left_compat {
476
        (expression_[i] < OP_UNION) || (expression_[i] == OP_RIGHT_PAREN)};
75,976✔
477
      bool right_compat {(expression_[i + 1] < OP_UNION) ||
75,976✔
478
                         (expression_[i + 1] == OP_LEFT_PAREN) ||
79,919✔
479
                         (expression_[i + 1] == OP_COMPLEMENT)};
3,943✔
480
      if (left_compat && right_compat) {
75,976✔
481
        expression_.insert(expression_.begin() + i + 1, OP_INTERSECTION);
34,093✔
482
      }
483
      i++;
75,976✔
484
    }
485

486
    // Remove complement operators using DeMorgan's laws
487
    auto it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
22,868✔
488
    while (it != expression_.end()) {
22,900✔
489
      // Erase complement
490
      expression_.erase(it);
32✔
491

492
      // Define stop given left parenthesis or not
493
      auto stop = it;
32✔
494
      if (*it == OP_LEFT_PAREN) {
32✔
495
        int depth = 1;
32✔
496
        do {
497
          stop++;
256✔
498
          if (*stop > OP_COMPLEMENT) {
256✔
499
            if (*stop == OP_RIGHT_PAREN) {
32✔
500
              depth--;
32✔
501
            } else {
UNCOV
502
              depth++;
×
503
            }
504
          }
505
        } while (depth > 0);
256✔
506
        it++;
32✔
507
      }
508

509
      // apply DeMorgan's law to any surfaces/operators between these
510
      // positions in the RPN
511
      apply_demorgan(it, stop);
32✔
512
      // update iterator position
513
      it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
32✔
514
    }
515

516
    // Convert user IDs to surface indices.
517
    for (auto& r : expression_) {
121,680✔
518
      if (r < OP_UNION) {
98,812✔
519
        const auto& it {model::surface_map.find(abs(r))};
59,786✔
520
        if (it == model::surface_map.end()) {
59,786✔
UNCOV
521
          throw std::runtime_error {
×
UNCOV
522
            "Invalid surface ID " + std::to_string(abs(r)) +
×
UNCOV
523
            " specified in region for cell " + std::to_string(cell_id) + "."};
×
524
        }
525
        r = (r > 0) ? it->second + 1 : -(it->second + 1);
59,786✔
526
      }
527
    }
528

529
    // Check if this is a simple cell.
530
    simple_ = true;
22,868✔
531
    for (int32_t token : expression_) {
111,186✔
532
      if (token == OP_UNION) {
89,167✔
533
        simple_ = false;
849✔
534
        // Ensure intersections have precedence over unions
535
        add_precedence();
849✔
536
        break;
849✔
537
      }
538
    }
539

540
    // If this cell is simple, remove all the superfluous operator tokens.
541
    if (simple_) {
22,868✔
542
      for (auto it = expression_.begin(); it != expression_.end(); it++) {
106,042✔
543
        if (*it == OP_INTERSECTION || *it > OP_COMPLEMENT) {
84,023✔
544
          expression_.erase(it);
31,002✔
545
          it--;
31,002✔
546
        }
547
      }
548
    }
549
    expression_.shrink_to_fit();
22,868✔
550

551
  } else {
552
    simple_ = true;
8,763✔
553
  }
554
}
31,631✔
555

556
//==============================================================================
557

558
void Region::apply_demorgan(
224✔
559
  vector<int32_t>::iterator start, vector<int32_t>::iterator stop)
560
{
561
  do {
562
    if (*start < OP_UNION) {
224✔
563
      *start *= -1;
128✔
564
    } else if (*start == OP_UNION) {
96✔
UNCOV
565
      *start = OP_INTERSECTION;
×
566
    } else if (*start == OP_INTERSECTION) {
96✔
567
      *start = OP_UNION;
96✔
568
    }
569
    start++;
224✔
570
  } while (start < stop);
224✔
571
}
32✔
572

573
//==============================================================================
574
//! Add precedence for infix regions so intersections have higher
575
//! precedence than unions using parentheses.
576
//==============================================================================
577

578
int64_t Region::add_parentheses(int64_t start)
32✔
579
{
580
  int32_t start_token = expression_[start];
32✔
581
  // Add left parenthesis and set new position to be after parenthesis
582
  if (start_token == OP_UNION) {
32✔
UNCOV
583
    start += 2;
×
584
  }
585
  expression_.insert(expression_.begin() + start - 1, OP_LEFT_PAREN);
32✔
586

587
  // Keep track of return iterator distance. If we don't encounter a left
588
  // parenthesis, we return an iterator corresponding to wherever the right
589
  // parenthesis is inserted. If a left parenthesis is encountered, an iterator
590
  // corresponding to the left parenthesis is returned. Also note that we keep
591
  // track of a *distance* instead of an iterator because the underlying memory
592
  // allocation may change.
593
  std::size_t return_it_dist = 0;
32✔
594

595
  // Add right parenthesis
596
  // While the start iterator is within the bounds of infix
597
  while (start + 1 < expression_.size()) {
224✔
598
    start++;
224✔
599

600
    // If the current token is an operator and is different than the start token
601
    if (expression_[start] >= OP_UNION && expression_[start] != start_token) {
224✔
602
      // Skip wrapped regions but save iterator position to check precedence and
603
      // add right parenthesis, right parenthesis position depends on the
604
      // operator, when the operator is a union then do not include the operator
605
      // in the region, when the operator is an intersection then include the
606
      // operator and next surface
607
      if (expression_[start] == OP_LEFT_PAREN) {
32✔
608
        return_it_dist = start;
×
609
        int depth = 1;
×
610
        do {
611
          start++;
×
UNCOV
612
          if (expression_[start] > OP_COMPLEMENT) {
×
UNCOV
613
            if (expression_[start] == OP_RIGHT_PAREN) {
×
614
              depth--;
×
615
            } else {
UNCOV
616
              depth++;
×
617
            }
618
          }
UNCOV
619
        } while (depth > 0);
×
620
      } else {
621
        if (start_token == OP_UNION) {
32✔
UNCOV
622
          --start;
×
623
        }
624
        expression_.insert(expression_.begin() + start, OP_RIGHT_PAREN);
32✔
625
        if (return_it_dist > 0) {
32✔
UNCOV
626
          return return_it_dist;
×
627
        } else {
628
          return start - 1;
32✔
629
        }
630
      }
631
    }
632
  }
633
  // If we get here a right parenthesis hasn't been placed,
634
  // return iterator
UNCOV
635
  expression_.push_back(OP_RIGHT_PAREN);
×
UNCOV
636
  if (return_it_dist > 0) {
×
UNCOV
637
    return return_it_dist;
×
638
  } else {
UNCOV
639
    return start - 1;
×
640
  }
641
}
642

643
//==============================================================================
644

645
void Region::add_precedence()
849✔
646
{
647
  int32_t current_op = 0;
849✔
648
  std::size_t current_dist = 0;
849✔
649

650
  for (int64_t i = 0; i < expression_.size(); i++) {
15,606✔
651
    int32_t token = expression_[i];
14,757✔
652

653
    if (token == OP_UNION || token == OP_INTERSECTION) {
14,757✔
654
      if (current_op == 0) {
5,900✔
655
        // Set the current operator if is hasn't been set
656
        current_op = token;
1,983✔
657
        current_dist = i;
1,983✔
658
      } else if (token != current_op) {
3,917✔
659
        // If the current operator doesn't match the token, add parenthesis to
660
        // assert precedence
661
        if (current_op == OP_INTERSECTION) {
32✔
662
          i = add_parentheses(current_dist);
16✔
663
        } else {
664
          i = add_parentheses(i);
16✔
665
        }
666
        current_op = 0;
32✔
667
        current_dist = 0;
32✔
668
      }
669
    } else if (token > OP_COMPLEMENT) {
8,857✔
670
      // If the token is a parenthesis reset the current operator
671
      current_op = 0;
2,140✔
672
      current_dist = 0;
2,140✔
673
    }
674
  }
675
}
849✔
676

677
//==============================================================================
678
//! Convert infix region specification to Reverse Polish Notation (RPN)
679
//!
680
//! This function uses the shunting-yard algorithm.
681
//==============================================================================
682

683
vector<int32_t> Region::generate_postfix(int32_t cell_id) const
44✔
684
{
685
  vector<int32_t> rpn;
44✔
686
  vector<int32_t> stack;
44✔
687

688
  for (int32_t token : expression_) {
990✔
689
    if (token < OP_UNION) {
946✔
690
      // If token is not an operator, add it to output
691
      rpn.push_back(token);
396✔
692
    } else if (token < OP_RIGHT_PAREN) {
550✔
693
      // Regular operators union, intersection, complement
694
      while (stack.size() > 0) {
561✔
695
        int32_t op = stack.back();
462✔
696

697
        if (op < OP_RIGHT_PAREN && ((token == OP_COMPLEMENT && token < op) ||
462✔
698
                                     (token != OP_COMPLEMENT && token <= op))) {
209✔
699
          // While there is an operator, op, on top of the stack, if the token
700
          // is left-associative and its precedence is less than or equal to
701
          // that of op or if the token is right-associative and its precedence
702
          // is less than that of op, move op to the output queue and push the
703
          // token on to the stack. Note that only complement is
704
          // right-associative.
705
          rpn.push_back(op);
209✔
706
          stack.pop_back();
209✔
707
        } else {
708
          break;
709
        }
710
      }
711

712
      stack.push_back(token);
352✔
713

714
    } else if (token == OP_LEFT_PAREN) {
198✔
715
      // If the token is a left parenthesis, push it onto the stack
716
      stack.push_back(token);
99✔
717

718
    } else {
719
      // If the token is a right parenthesis, move operators from the stack to
720
      // the output queue until reaching the left parenthesis.
721
      for (auto it = stack.rbegin(); *it != OP_LEFT_PAREN; it++) {
198✔
722
        // If we run out of operators without finding a left parenthesis, it
723
        // means there are mismatched parentheses.
724
        if (it == stack.rend()) {
99✔
UNCOV
725
          fatal_error(fmt::format(
×
726
            "Mismatched parentheses in region specification for cell {}",
727
            cell_id));
728
        }
729
        rpn.push_back(stack.back());
99✔
730
        stack.pop_back();
99✔
731
      }
732

733
      // Pop the left parenthesis.
734
      stack.pop_back();
99✔
735
    }
736
  }
737

738
  while (stack.size() > 0) {
88✔
739
    int32_t op = stack.back();
44✔
740

741
    // If the operator is a parenthesis it is mismatched.
742
    if (op >= OP_RIGHT_PAREN) {
44✔
UNCOV
743
      fatal_error(fmt::format(
×
744
        "Mismatched parentheses in region specification for cell {}", cell_id));
745
    }
746

747
    rpn.push_back(stack.back());
44✔
748
    stack.pop_back();
44✔
749
  }
750

751
  return rpn;
88✔
752
}
44✔
753

754
//==============================================================================
755

756
std::string Region::str() const
25,151✔
757
{
758
  std::stringstream region_spec {};
25,151✔
759
  if (!expression_.empty()) {
25,151✔
760
    for (int32_t token : expression_) {
70,272✔
761
      if (token == OP_LEFT_PAREN) {
53,103✔
762
        region_spec << " (";
940✔
763
      } else if (token == OP_RIGHT_PAREN) {
52,163✔
764
        region_spec << " )";
940✔
765
      } else if (token == OP_COMPLEMENT) {
51,223✔
UNCOV
766
        region_spec << " ~";
×
767
      } else if (token == OP_INTERSECTION) {
51,223✔
768
      } else if (token == OP_UNION) {
48,636✔
769
        region_spec << " |";
2,611✔
770
      } else {
771
        // Note the off-by-one indexing
772
        auto surf_id = model::surfaces[abs(token) - 1]->id_;
46,025✔
773
        region_spec << " " << ((token > 0) ? surf_id : -surf_id);
46,025✔
774
      }
775
    }
776
  }
777
  return region_spec.str();
50,302✔
778
}
25,151✔
779

780
//==============================================================================
781

782
std::pair<double, int32_t> Region::distance(
2,147,483,647✔
783
  Position r, Direction u, int32_t on_surface) const
784
{
785
  double min_dist {INFTY};
2,147,483,647✔
786
  int32_t i_surf {std::numeric_limits<int32_t>::max()};
2,147,483,647✔
787

788
  for (int32_t token : expression_) {
2,147,483,647✔
789
    // Ignore this token if it corresponds to an operator rather than a region.
790
    if (token >= OP_UNION)
2,147,483,647✔
791
      continue;
169,611,236✔
792

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

798
    // Check if this distance is the new minimum.
799
    if (d < min_dist) {
2,147,483,647✔
800
      if (min_dist - d >= FP_PRECISION * min_dist) {
2,147,483,647✔
801
        min_dist = d;
2,147,483,647✔
802
        i_surf = -token;
2,147,483,647✔
803
      }
804
    }
805
  }
806

807
  return {min_dist, i_surf};
2,147,483,647✔
808
}
809

810
//==============================================================================
811

812
bool Region::contains(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
813
{
814
  if (simple_) {
2,147,483,647✔
815
    return contains_simple(r, u, on_surface);
2,147,483,647✔
816
  } else {
817
    return contains_complex(r, u, on_surface);
6,692,939✔
818
  }
819
}
820

821
//==============================================================================
822

823
bool Region::contains_simple(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
824
{
825
  for (int32_t token : expression_) {
2,147,483,647✔
826
    // Assume that no tokens are operators. Evaluate the sense of particle with
827
    // respect to the surface and see if the token matches the sense. If the
828
    // particle's surface attribute is set and matches the token, that
829
    // overrides the determination based on sense().
830
    if (token == on_surface) {
2,147,483,647✔
831
    } else if (-token == on_surface) {
2,147,483,647✔
832
      return false;
941,699,009✔
833
    } else {
834
      // Note the off-by-one indexing
835
      bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
2,147,483,647✔
836
      if (sense != (token > 0)) {
2,147,483,647✔
837
        return false;
810,591,868✔
838
      }
839
    }
840
  }
841
  return true;
2,147,483,647✔
842
}
843

844
//==============================================================================
845

846
bool Region::contains_complex(Position r, Direction u, int32_t on_surface) const
6,692,939✔
847
{
848
  bool in_cell = true;
6,692,939✔
849
  int total_depth = 0;
6,692,939✔
850

851
  // For each token
852
  for (auto it = expression_.begin(); it != expression_.end(); it++) {
106,353,522✔
853
    int32_t token = *it;
101,518,974✔
854

855
    // If the token is a surface evaluate the sense
856
    // If the token is a union or intersection check to
857
    // short circuit
858
    if (token < OP_UNION) {
101,518,974✔
859
      if (token == on_surface) {
44,355,220✔
860
        in_cell = true;
3,307,179✔
861
      } else if (-token == on_surface) {
41,048,041✔
862
        in_cell = false;
666,056✔
863
      } else {
864
        // Note the off-by-one indexing
865
        bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
40,381,985✔
866
        in_cell = (sense == (token > 0));
40,381,985✔
867
      }
868
    } else if ((token == OP_UNION && in_cell == true) ||
57,163,754✔
869
               (token == OP_INTERSECTION && in_cell == false)) {
27,919,103✔
870
      // If the total depth is zero return
871
      if (total_depth == 0) {
6,237,030✔
872
        return in_cell;
1,858,391✔
873
      }
874

875
      total_depth--;
4,378,639✔
876

877
      // While the iterator is within the bounds of the vector
878
      int depth = 1;
4,378,639✔
879
      do {
880
        // Get next token
881
        it++;
27,677,828✔
882
        int32_t next_token = *it;
27,677,828✔
883

884
        // If the token is an a parenthesis
885
        if (next_token > OP_COMPLEMENT) {
27,677,828✔
886
          // Adjust depth accordingly
887
          if (next_token == OP_RIGHT_PAREN) {
4,567,183✔
888
            depth--;
4,472,911✔
889
          } else {
890
            depth++;
94,272✔
891
          }
892
        }
893
      } while (depth > 0);
27,677,828✔
894
    } else if (token == OP_LEFT_PAREN) {
55,305,363✔
895
      total_depth++;
8,821,541✔
896
    } else if (token == OP_RIGHT_PAREN) {
42,105,183✔
897
      total_depth--;
4,442,902✔
898
    }
899
  }
900
  return in_cell;
4,834,548✔
901
}
902

903
//==============================================================================
904

905
BoundingBox Region::bounding_box(int32_t cell_id) const
88✔
906
{
907
  if (simple_) {
88✔
908
    return bounding_box_simple();
44✔
909
  } else {
910
    auto postfix = generate_postfix(cell_id);
44✔
911
    return bounding_box_complex(postfix);
44✔
912
  }
44✔
913
}
914

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

917
BoundingBox Region::bounding_box_simple() const
44✔
918
{
919
  BoundingBox bbox;
44✔
920
  for (int32_t token : expression_) {
176✔
921
    bbox &= model::surfaces[abs(token) - 1]->bounding_box(token > 0);
132✔
922
  }
923
  return bbox;
44✔
924
}
925

926
//==============================================================================
927

928
BoundingBox Region::bounding_box_complex(vector<int32_t> postfix) const
44✔
929
{
930
  vector<BoundingBox> stack(postfix.size());
44✔
931
  int i_stack = -1;
44✔
932

933
  for (auto& token : postfix) {
792✔
934
    if (token == OP_UNION) {
748✔
935
      stack[i_stack - 1] = stack[i_stack - 1] | stack[i_stack];
154✔
936
      i_stack--;
154✔
937
    } else if (token == OP_INTERSECTION) {
594✔
938
      stack[i_stack - 1] = stack[i_stack - 1] & stack[i_stack];
198✔
939
      i_stack--;
198✔
940
    } else {
941
      i_stack++;
396✔
942
      stack[i_stack] = model::surfaces[abs(token) - 1]->bounding_box(token > 0);
396✔
943
    }
944
  }
945

946
  assert(i_stack == 0);
36✔
947
  return stack.front();
88✔
948
}
44✔
949

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

952
vector<int32_t> Region::surfaces() const
5,389✔
953
{
954
  if (simple_) {
5,389✔
955
    return expression_;
5,389✔
956
  }
957

958
  vector<int32_t> surfaces = expression_;
×
959

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

UNCOV
963
  while (it != surfaces.end()) {
×
UNCOV
964
    surfaces.erase(it);
×
965

UNCOV
966
    it = std::find_if(surfaces.begin(), surfaces.end(),
×
UNCOV
967
      [&](const auto& value) { return value >= OP_UNION; });
×
968
  }
969

UNCOV
970
  return surfaces;
×
971
}
972

973
//==============================================================================
974
// Non-method functions
975
//==============================================================================
976

977
void read_cells(pugi::xml_node node)
6,879✔
978
{
979
  // Count the number of cells.
980
  int n_cells = 0;
6,879✔
981
  for (pugi::xml_node cell_node : node.children("cell")) {
38,510✔
982
    n_cells++;
31,631✔
983
  }
984

985
  // Loop over XML cell elements and populate the array.
986
  model::cells.reserve(n_cells);
6,879✔
987
  for (pugi::xml_node cell_node : node.children("cell")) {
38,510✔
988
    model::cells.push_back(make_unique<CSGCell>(cell_node));
31,631✔
989
  }
990

991
  // Fill the cell map.
992
  for (int i = 0; i < model::cells.size(); i++) {
38,510✔
993
    int32_t id = model::cells[i]->id_;
31,631✔
994
    auto search = model::cell_map.find(id);
31,631✔
995
    if (search == model::cell_map.end()) {
31,631✔
996
      model::cell_map[id] = i;
31,631✔
997
    } else {
UNCOV
998
      fatal_error(
×
UNCOV
999
        fmt::format("Two or more cells use the same unique ID: {}", id));
×
1000
    }
1001
  }
1002

1003
  read_dagmc_universes(node);
6,879✔
1004

1005
  populate_universes();
6,877✔
1006

1007
  // Allocate the cell overlap count if necessary.
1008
  if (settings::check_overlaps) {
6,877✔
1009
    model::overlap_check_count.resize(model::cells.size(), 0);
276✔
1010
  }
1011

1012
  if (model::cells.size() == 0) {
6,877✔
UNCOV
1013
    fatal_error("No cells were found in the geometry.xml file");
×
1014
  }
1015
}
6,877✔
1016

1017
void populate_universes()
6,879✔
1018
{
1019
  // Used to map universe index to the index of an implicit complement cell for
1020
  // DAGMC universes
1021
  std::unordered_map<int, int> implicit_comp_cells;
6,879✔
1022

1023
  // Populate the Universe vector and map.
1024
  for (int index_cell = 0; index_cell < model::cells.size(); index_cell++) {
38,718✔
1025
    int32_t uid = model::cells[index_cell]->universe_;
31,839✔
1026
    auto it = model::universe_map.find(uid);
31,839✔
1027
    if (it == model::universe_map.end()) {
31,839✔
1028
      model::universes.push_back(make_unique<Universe>());
18,120✔
1029
      model::universes.back()->id_ = uid;
18,120✔
1030
      model::universes.back()->cells_.push_back(index_cell);
18,120✔
1031
      model::universe_map[uid] = model::universes.size() - 1;
18,120✔
1032
    } else {
1033
#ifdef OPENMC_DAGMC_ENABLED
1034
      // Skip implicit complement cells for now
1035
      Universe* univ = model::universes[it->second].get();
1,797✔
1036
      DAGUniverse* dag_univ = dynamic_cast<DAGUniverse*>(univ);
1,797✔
1037
      if (dag_univ && (dag_univ->implicit_complement_idx() == index_cell)) {
1,797✔
1038
        implicit_comp_cells[it->second] = index_cell;
43✔
1039
        continue;
43✔
1040
      }
1041
#endif
1042

1043
      model::universes[it->second]->cells_.push_back(index_cell);
13,676✔
1044
    }
1045
  }
1046

1047
  // Add DAGUniverse implicit complement cells last
1048
  for (const auto& it : implicit_comp_cells) {
6,922✔
1049
    int index_univ = it.first;
43✔
1050
    int index_cell = it.second;
43✔
1051
    model::universes[index_univ]->cells_.push_back(index_cell);
43✔
1052
  }
1053

1054
  model::universes.shrink_to_fit();
6,879✔
1055
}
6,879✔
1056

1057
//==============================================================================
1058
// C-API functions
1059
//==============================================================================
1060

1061
extern "C" int openmc_cell_get_fill(
177✔
1062
  int32_t index, int* type, int32_t** indices, int32_t* n)
1063
{
1064
  if (index >= 0 && index < model::cells.size()) {
177✔
1065
    Cell& c {*model::cells[index]};
177✔
1066
    *type = static_cast<int>(c.type_);
177✔
1067
    if (c.type_ == Fill::MATERIAL) {
177✔
1068
      *indices = c.material_.data();
177✔
1069
      *n = c.material_.size();
177✔
1070
    } else {
1071
      *indices = &c.fill_;
×
UNCOV
1072
      *n = 1;
×
1073
    }
1074
  } else {
UNCOV
1075
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1076
    return OPENMC_E_OUT_OF_BOUNDS;
×
1077
  }
1078
  return 0;
177✔
1079
}
1080

1081
extern "C" int openmc_cell_set_fill(
11✔
1082
  int32_t index, int type, int32_t n, const int32_t* indices)
1083
{
1084
  Fill filltype = static_cast<Fill>(type);
11✔
1085
  if (index >= 0 && index < model::cells.size()) {
11✔
1086
    Cell& c {*model::cells[index]};
11✔
1087
    if (filltype == Fill::MATERIAL) {
11✔
1088
      c.type_ = Fill::MATERIAL;
11✔
1089
      c.material_.clear();
11✔
1090
      for (int i = 0; i < n; i++) {
22✔
1091
        int i_mat = indices[i];
11✔
1092
        if (i_mat == MATERIAL_VOID) {
11✔
1093
          c.material_.push_back(MATERIAL_VOID);
×
1094
        } else if (i_mat >= 0 && i_mat < model::materials.size()) {
11✔
1095
          c.material_.push_back(i_mat);
11✔
1096
        } else {
1097
          set_errmsg("Index in materials array is out of bounds.");
×
1098
          return OPENMC_E_OUT_OF_BOUNDS;
×
1099
        }
1100
      }
1101
      c.material_.shrink_to_fit();
11✔
UNCOV
1102
    } else if (filltype == Fill::UNIVERSE) {
×
1103
      c.type_ = Fill::UNIVERSE;
×
1104
    } else {
UNCOV
1105
      c.type_ = Fill::LATTICE;
×
1106
    }
1107
  } else {
UNCOV
1108
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1109
    return OPENMC_E_OUT_OF_BOUNDS;
×
1110
  }
1111
  return 0;
11✔
1112
}
1113

1114
extern "C" int openmc_cell_set_temperature(
88✔
1115
  int32_t index, double T, const int32_t* instance, bool set_contained)
1116
{
1117
  if (index < 0 || index >= model::cells.size()) {
88✔
UNCOV
1118
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
UNCOV
1119
    return OPENMC_E_OUT_OF_BOUNDS;
×
1120
  }
1121

1122
  int32_t instance_index = instance ? *instance : -1;
88✔
1123
  try {
1124
    model::cells[index]->set_temperature(T, instance_index, set_contained);
88✔
UNCOV
1125
  } catch (const std::exception& e) {
×
UNCOV
1126
    set_errmsg(e.what());
×
UNCOV
1127
    return OPENMC_E_UNASSIGNED;
×
UNCOV
1128
  }
×
1129
  return 0;
88✔
1130
}
1131

1132
extern "C" int openmc_cell_get_temperature(
91✔
1133
  int32_t index, const int32_t* instance, double* T)
1134
{
1135
  if (index < 0 || index >= model::cells.size()) {
91✔
UNCOV
1136
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
UNCOV
1137
    return OPENMC_E_OUT_OF_BOUNDS;
×
1138
  }
1139

1140
  int32_t instance_index = instance ? *instance : -1;
91✔
1141
  try {
1142
    *T = model::cells[index]->temperature(instance_index);
91✔
UNCOV
1143
  } catch (const std::exception& e) {
×
UNCOV
1144
    set_errmsg(e.what());
×
UNCOV
1145
    return OPENMC_E_UNASSIGNED;
×
UNCOV
1146
  }
×
1147
  return 0;
91✔
1148
}
1149

1150
//! Get the bounding box of a cell
1151
extern "C" int openmc_cell_bounding_box(
55✔
1152
  const int32_t index, double* llc, double* urc)
1153
{
1154

1155
  BoundingBox bbox;
55✔
1156

1157
  const auto& c = model::cells[index];
55✔
1158
  bbox = c->bounding_box();
55✔
1159

1160
  // set lower left corner values
1161
  llc[0] = bbox.xmin;
55✔
1162
  llc[1] = bbox.ymin;
55✔
1163
  llc[2] = bbox.zmin;
55✔
1164

1165
  // set upper right corner values
1166
  urc[0] = bbox.xmax;
55✔
1167
  urc[1] = bbox.ymax;
55✔
1168
  urc[2] = bbox.zmax;
55✔
1169

1170
  return 0;
55✔
1171
}
1172

1173
//! Get the name of a cell
1174
extern "C" int openmc_cell_get_name(int32_t index, const char** name)
22✔
1175
{
1176
  if (index < 0 || index >= model::cells.size()) {
22✔
UNCOV
1177
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1178
    return OPENMC_E_OUT_OF_BOUNDS;
×
1179
  }
1180

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

1183
  return 0;
22✔
1184
}
1185

1186
//! Set the name of a cell
1187
extern "C" int openmc_cell_set_name(int32_t index, const char* name)
11✔
1188
{
1189
  if (index < 0 || index >= model::cells.size()) {
11✔
UNCOV
1190
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1191
    return OPENMC_E_OUT_OF_BOUNDS;
×
1192
  }
1193

1194
  model::cells[index]->set_name(name);
11✔
1195

1196
  return 0;
11✔
1197
}
1198

1199
//==============================================================================
1200
//! Define a containing (parent) cell
1201
//==============================================================================
1202

1203
//! Used to locate a universe fill in the geometry
1204
struct ParentCell {
1205
  bool operator==(const ParentCell& other) const
96✔
1206
  {
1207
    return cell_index == other.cell_index &&
192✔
1208
           lattice_index == other.lattice_index;
192✔
1209
  }
1210

1211
  bool operator<(const ParentCell& other) const
1212
  {
1213
    return cell_index < other.cell_index ||
1214
           (cell_index == other.cell_index &&
1215
             lattice_index < other.lattice_index);
1216
  }
1217

1218
  int64_t cell_index;
1219
  int64_t lattice_index;
1220
};
1221

1222
//! Structure used to insert ParentCell into hashed STL data structures
1223
struct ParentCellHash {
1224
  std::size_t operator()(const ParentCell& p) const
481✔
1225
  {
1226
    return 4096 * p.cell_index + p.lattice_index;
481✔
1227
  }
1228
};
1229

1230
//! Used to manage a traversal stack when locating parent cells of a cell
1231
//! instance in the model
1232
struct ParentCellStack {
1233

1234
  //! push method that adds to the parent_cells visited cells for this search
1235
  //! universe
1236
  void push(int32_t search_universe, const ParentCell& pc)
64✔
1237
  {
1238
    parent_cells_.push_back(pc);
64✔
1239
    // add parent cell to the set of cells we've visited for this search
1240
    // universe
1241
    visited_cells_[search_universe].insert(pc);
64✔
1242
  }
64✔
1243

1244
  //! removes the last parent_cell and clears the visited cells for the popped
1245
  //! cell's universe
1246
  void pop()
48✔
1247
  {
1248
    visited_cells_[this->current_univ()].clear();
48✔
1249
    parent_cells_.pop_back();
48✔
1250
  }
48✔
1251

1252
  //! checks whether or not the parent cell has been visited already for this
1253
  //! search universe
1254
  bool visited(int32_t search_universe, const ParentCell& parent_cell)
417✔
1255
  {
1256
    return visited_cells_[search_universe].count(parent_cell) != 0;
417✔
1257
  }
1258

1259
  //! return the next universe to search for a parent cell
1260
  int32_t current_univ() const
48✔
1261
  {
1262
    return model::cells[parent_cells_.back().cell_index]->universe_;
48✔
1263
  }
1264

1265
  //! indicates whether nor not parent cells are present on the stack
1266
  bool empty() const { return parent_cells_.empty(); }
48✔
1267

1268
  //! compute an instance for the provided distribcell index
1269
  int32_t compute_instance(int32_t distribcell_index) const
145✔
1270
  {
1271
    if (distribcell_index == C_NONE)
145✔
1272
      return 0;
65✔
1273

1274
    int32_t instance = 0;
80✔
1275
    for (const auto& parent_cell : this->parent_cells_) {
144✔
1276
      auto& cell = model::cells[parent_cell.cell_index];
64✔
1277
      if (cell->type_ == Fill::UNIVERSE) {
64✔
UNCOV
1278
        instance += cell->offset_[distribcell_index];
×
1279
      } else if (cell->type_ == Fill::LATTICE) {
64✔
1280
        auto& lattice = model::lattices[cell->fill_];
64✔
1281
        instance +=
64✔
1282
          lattice->offset(distribcell_index, parent_cell.lattice_index);
64✔
1283
      }
1284
    }
1285
    return instance;
80✔
1286
  }
1287

1288
  // Accessors
1289
  vector<ParentCell>& parent_cells() { return parent_cells_; }
291✔
1290
  const vector<ParentCell>& parent_cells() const { return parent_cells_; }
1291

1292
  // Data Members
1293
  vector<ParentCell> parent_cells_;
1294
  std::unordered_map<int32_t, std::unordered_set<ParentCell, ParentCellHash>>
1295
    visited_cells_;
1296
};
1297

1298
vector<ParentCell> Cell::find_parent_cells(
×
1299
  int32_t instance, const Position& r) const
1300
{
1301

1302
  // create a temporary particle
UNCOV
1303
  GeometryState dummy_particle {};
×
UNCOV
1304
  dummy_particle.r() = r;
×
1305
  dummy_particle.u() = {0., 0., 1.};
×
1306

UNCOV
1307
  return find_parent_cells(instance, dummy_particle);
×
1308
}
1309

1310
vector<ParentCell> Cell::find_parent_cells(
×
1311
  int32_t instance, GeometryState& p) const
1312
{
1313
  // look up the particle's location
1314
  exhaustive_find_cell(p);
×
1315
  const auto& coords = p.coord();
×
1316

1317
  // build a parent cell stack from the particle coordinates
UNCOV
1318
  ParentCellStack stack;
×
UNCOV
1319
  bool cell_found = false;
×
1320
  for (auto it = coords.begin(); it != coords.end(); it++) {
×
1321
    const auto& coord = *it;
×
1322
    const auto& cell = model::cells[coord.cell()];
×
1323
    // if the cell at this level matches the current cell, stop adding to the
1324
    // stack
UNCOV
1325
    if (coord.cell() == model::cell_map[this->id_]) {
×
UNCOV
1326
      cell_found = true;
×
1327
      break;
×
1328
    }
1329

1330
    // if filled with a lattice, get the lattice index from the next
1331
    // level in the coordinates to push to the stack
UNCOV
1332
    int lattice_idx = C_NONE;
×
1333
    if (cell->type_ == Fill::LATTICE) {
×
UNCOV
1334
      const auto& next_coord = *(it + 1);
×
UNCOV
1335
      lattice_idx = model::lattices[next_coord.lattice()]->get_flat_index(
×
1336
        next_coord.lattice_index());
1337
    }
UNCOV
1338
    stack.push(coord.universe(), {coord.cell(), lattice_idx});
×
1339
  }
1340

1341
  // if this loop finished because the cell was found and
1342
  // the instance matches the one requested in the call
1343
  // we have the correct path and can return the stack
UNCOV
1344
  if (cell_found &&
×
1345
      stack.compute_instance(this->distribcell_index_) == instance) {
×
UNCOV
1346
    return stack.parent_cells();
×
1347
  }
1348

1349
  // fall back on an exhaustive search for the cell's parents
UNCOV
1350
  return exhaustive_find_parent_cells(instance);
×
1351
}
1352

1353
vector<ParentCell> Cell::exhaustive_find_parent_cells(int32_t instance) const
97✔
1354
{
1355
  ParentCellStack stack;
97✔
1356
  // start with this cell's universe
1357
  int32_t prev_univ_idx;
1358
  int32_t univ_idx = this->universe_;
97✔
1359

1360
  while (true) {
1361
    const auto& univ = model::universes[univ_idx];
145✔
1362
    prev_univ_idx = univ_idx;
145✔
1363

1364
    // search for a cell that is filled w/ this universe
1365
    for (const auto& cell : model::cells) {
996✔
1366
      // if this is a material-filled cell, move on
1367
      if (cell->type_ == Fill::MATERIAL)
915✔
1368
        continue;
498✔
1369

1370
      if (cell->type_ == Fill::UNIVERSE) {
417✔
1371
        // if this is in the set of cells previously visited for this universe,
1372
        // move on
1373
        if (stack.visited(univ_idx, {model::cell_map[cell->id_], C_NONE}))
257✔
1374
          continue;
×
1375

1376
        // if this cell contains the universe we're searching for, add it to the
1377
        // stack
1378
        if (cell->fill_ == univ_idx) {
257✔
UNCOV
1379
          stack.push(univ_idx, {model::cell_map[cell->id_], C_NONE});
×
UNCOV
1380
          univ_idx = cell->universe_;
×
1381
        }
1382
      } else if (cell->type_ == Fill::LATTICE) {
160✔
1383
        // retrieve the lattice and lattice universes
1384
        const auto& lattice = model::lattices[cell->fill_];
160✔
1385
        const auto& lattice_univs = lattice->universes_;
160✔
1386

1387
        // start search for universe
1388
        auto lat_it = lattice_univs.begin();
160✔
1389
        while (true) {
1390
          // find the next lattice cell with this universe
1391
          lat_it = std::find(lat_it, lattice_univs.end(), univ_idx);
256✔
1392
          if (lat_it == lattice_univs.end())
256✔
1393
            break;
96✔
1394

1395
          int lattice_idx = lat_it - lattice_univs.begin();
160✔
1396

1397
          // move iterator forward one to avoid finding the same entry
1398
          lat_it++;
160✔
1399
          if (stack.visited(
160✔
1400
                univ_idx, {model::cell_map[cell->id_], lattice_idx}))
160✔
1401
            continue;
96✔
1402

1403
          // add this cell and lattice index to the stack and exit loop
1404
          stack.push(univ_idx, {model::cell_map[cell->id_], lattice_idx});
64✔
1405
          univ_idx = cell->universe_;
64✔
1406
          break;
64✔
1407
        }
96✔
1408
      }
1409
      // if we've updated the universe, break
1410
      if (prev_univ_idx != univ_idx)
417✔
1411
        break;
64✔
1412
    } // end cell loop search for universe
1413

1414
    // if we're at the top of the geometry and the instance matches, we're done
1415
    if (univ_idx == model::root_universe &&
290✔
1416
        stack.compute_instance(this->distribcell_index_) == instance)
145✔
1417
      break;
97✔
1418

1419
    // if there is no match on the original cell's universe, report an error
1420
    if (univ_idx == this->universe_) {
48✔
UNCOV
1421
      fatal_error(
×
UNCOV
1422
        fmt::format("Could not find the parent cells for cell {}, instance {}.",
×
UNCOV
1423
          this->id_, instance));
×
1424
    }
1425

1426
    // if we don't find a suitable update, adjust the stack and continue
1427
    if (univ_idx == model::root_universe || univ_idx == prev_univ_idx) {
48✔
1428
      stack.pop();
48✔
1429
      univ_idx = stack.empty() ? this->universe_ : stack.current_univ();
48✔
1430
    }
1431

1432
  } // end while
48✔
1433

1434
  // reverse the stack so the highest cell comes first
1435
  std::reverse(stack.parent_cells().begin(), stack.parent_cells().end());
97✔
1436
  return stack.parent_cells();
194✔
1437
}
97✔
1438

1439
std::unordered_map<int32_t, vector<int32_t>> Cell::get_contained_cells(
145✔
1440
  int32_t instance, Position* hint) const
1441
{
1442
  std::unordered_map<int32_t, vector<int32_t>> contained_cells;
145✔
1443

1444
  // if this is a material-filled cell it has no contained cells
1445
  if (this->type_ == Fill::MATERIAL)
145✔
1446
    return contained_cells;
48✔
1447

1448
  // find the pathway through the geometry to this cell
1449
  vector<ParentCell> parent_cells;
97✔
1450

1451
  // if a positional hint is provided, attempt to do a fast lookup
1452
  // of the parent cells
1453
  parent_cells = hint ? find_parent_cells(instance, *hint)
194✔
1454
                      : exhaustive_find_parent_cells(instance);
97✔
1455

1456
  // if this cell is filled w/ a material, it contains no other cells
1457
  if (type_ != Fill::MATERIAL) {
97✔
1458
    this->get_contained_cells_inner(contained_cells, parent_cells);
97✔
1459
  }
1460

1461
  return contained_cells;
97✔
1462
}
97✔
1463

1464
//! Get all cells within this cell
1465
void Cell::get_contained_cells_inner(
87,699✔
1466
  std::unordered_map<int32_t, vector<int32_t>>& contained_cells,
1467
  vector<ParentCell>& parent_cells) const
1468
{
1469

1470
  // filled by material, determine instance based on parent cells
1471
  if (type_ == Fill::MATERIAL) {
87,699✔
1472
    int instance = 0;
87,090✔
1473
    if (this->distribcell_index_ >= 0) {
87,090✔
1474
      for (auto& parent_cell : parent_cells) {
261,268✔
1475
        auto& cell = model::cells[parent_cell.cell_index];
174,178✔
1476
        if (cell->type_ == Fill::UNIVERSE) {
174,178✔
1477
          instance += cell->offset_[distribcell_index_];
85,490✔
1478
        } else if (cell->type_ == Fill::LATTICE) {
88,688✔
1479
          auto& lattice = model::lattices[cell->fill_];
88,688✔
1480
          instance += lattice->offset(
177,376✔
1481
            this->distribcell_index_, parent_cell.lattice_index);
88,688✔
1482
        }
1483
      }
1484
    }
1485
    // add entry to contained cells
1486
    contained_cells[model::cell_map[id_]].push_back(instance);
87,090✔
1487
    // filled with universe, add the containing cell to the parent cells
1488
    // and recurse
1489
  } else if (type_ == Fill::UNIVERSE) {
609✔
1490
    parent_cells.push_back({model::cell_map[id_], -1});
513✔
1491
    auto& univ = model::universes[fill_];
513✔
1492
    for (auto cell_index : univ->cells_) {
3,107✔
1493
      auto& cell = model::cells[cell_index];
2,594✔
1494
      cell->get_contained_cells_inner(contained_cells, parent_cells);
2,594✔
1495
    }
1496
    parent_cells.pop_back();
513✔
1497
    // filled with a lattice, visit each universe in the lattice
1498
    // with a recursive call to collect the cell instances
1499
  } else if (type_ == Fill::LATTICE) {
96✔
1500
    auto& lattice = model::lattices[fill_];
96✔
1501
    for (auto i = lattice->begin(); i != lattice->end(); ++i) {
84,768✔
1502
      auto& univ = model::universes[*i];
84,672✔
1503
      parent_cells.push_back({model::cell_map[id_], i.indx_});
84,672✔
1504
      for (auto cell_index : univ->cells_) {
169,680✔
1505
        auto& cell = model::cells[cell_index];
85,008✔
1506
        cell->get_contained_cells_inner(contained_cells, parent_cells);
85,008✔
1507
      }
1508
      parent_cells.pop_back();
84,672✔
1509
    }
1510
  }
1511
}
87,699✔
1512

1513
//! Return the index in the cells array of a cell with a given ID
1514
extern "C" int openmc_get_cell_index(int32_t id, int32_t* index)
424✔
1515
{
1516
  auto it = model::cell_map.find(id);
424✔
1517
  if (it != model::cell_map.end()) {
424✔
1518
    *index = it->second;
413✔
1519
    return 0;
413✔
1520
  } else {
1521
    set_errmsg("No cell exists with ID=" + std::to_string(id) + ".");
11✔
1522
    return OPENMC_E_INVALID_ID;
11✔
1523
  }
1524
}
1525

1526
//! Return the ID of a cell
1527
extern "C" int openmc_cell_get_id(int32_t index, int32_t* id)
600,908✔
1528
{
1529
  if (index >= 0 && index < model::cells.size()) {
600,908✔
1530
    *id = model::cells[index]->id_;
600,908✔
1531
    return 0;
600,908✔
1532
  } else {
UNCOV
1533
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1534
    return OPENMC_E_OUT_OF_BOUNDS;
×
1535
  }
1536
}
1537

1538
//! Set the ID of a cell
1539
extern "C" int openmc_cell_set_id(int32_t index, int32_t id)
22✔
1540
{
1541
  if (index >= 0 && index < model::cells.size()) {
22✔
1542
    model::cells[index]->id_ = id;
22✔
1543
    model::cell_map[id] = index;
22✔
1544
    return 0;
22✔
1545
  } else {
UNCOV
1546
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1547
    return OPENMC_E_OUT_OF_BOUNDS;
×
1548
  }
1549
}
1550

1551
//! Return the translation vector of a cell
1552
extern "C" int openmc_cell_get_translation(int32_t index, double xyz[])
55✔
1553
{
1554
  if (index >= 0 && index < model::cells.size()) {
55✔
1555
    auto& cell = model::cells[index];
55✔
1556
    xyz[0] = cell->translation_.x;
55✔
1557
    xyz[1] = cell->translation_.y;
55✔
1558
    xyz[2] = cell->translation_.z;
55✔
1559
    return 0;
55✔
1560
  } else {
UNCOV
1561
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1562
    return OPENMC_E_OUT_OF_BOUNDS;
×
1563
  }
1564
}
1565

1566
//! Set the translation vector of a cell
1567
extern "C" int openmc_cell_set_translation(int32_t index, const double xyz[])
33✔
1568
{
1569
  if (index >= 0 && index < model::cells.size()) {
33✔
1570
    if (model::cells[index]->fill_ == C_NONE) {
33✔
1571
      set_errmsg(fmt::format("Cannot apply a translation to cell {}"
11✔
1572
                             " because it is not filled with another universe",
1573
        index));
1574
      return OPENMC_E_GEOMETRY;
11✔
1575
    }
1576
    model::cells[index]->translation_ = Position(xyz);
22✔
1577
    return 0;
22✔
1578
  } else {
UNCOV
1579
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1580
    return OPENMC_E_OUT_OF_BOUNDS;
×
1581
  }
1582
}
1583

1584
//! Return the rotation matrix of a cell
1585
extern "C" int openmc_cell_get_rotation(int32_t index, double rot[], size_t* n)
55✔
1586
{
1587
  if (index >= 0 && index < model::cells.size()) {
55✔
1588
    auto& cell = model::cells[index];
55✔
1589
    *n = cell->rotation_.size();
55✔
1590
    std::memcpy(rot, cell->rotation_.data(), *n * sizeof(cell->rotation_[0]));
55✔
1591
    return 0;
55✔
1592
  } else {
UNCOV
1593
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1594
    return OPENMC_E_OUT_OF_BOUNDS;
×
1595
  }
1596
}
1597

1598
//! Set the flattened rotation matrix of a cell
1599
extern "C" int openmc_cell_set_rotation(
44✔
1600
  int32_t index, const double rot[], size_t rot_len)
1601
{
1602
  if (index >= 0 && index < model::cells.size()) {
44✔
1603
    if (model::cells[index]->fill_ == C_NONE) {
44✔
1604
      set_errmsg(fmt::format("Cannot apply a rotation to cell {}"
11✔
1605
                             " because it is not filled with another universe",
1606
        index));
1607
      return OPENMC_E_GEOMETRY;
11✔
1608
    }
1609
    std::vector<double> vec_rot(rot, rot + rot_len);
33✔
1610
    model::cells[index]->set_rotation(vec_rot);
33✔
1611
    return 0;
33✔
1612
  } else {
33✔
UNCOV
1613
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1614
    return OPENMC_E_OUT_OF_BOUNDS;
×
1615
  }
1616
}
1617

1618
//! Get the number of instances of the requested cell
1619
extern "C" int openmc_cell_get_num_instances(
11✔
1620
  int32_t index, int32_t* num_instances)
1621
{
1622
  if (index < 0 || index >= model::cells.size()) {
11✔
UNCOV
1623
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1624
    return OPENMC_E_OUT_OF_BOUNDS;
×
1625
  }
1626
  *num_instances = model::cells[index]->n_instances();
11✔
1627
  return 0;
11✔
1628
}
1629

1630
//! Extend the cells array by n elements
1631
extern "C" int openmc_extend_cells(
22✔
1632
  int32_t n, int32_t* index_start, int32_t* index_end)
1633
{
1634
  if (index_start)
22✔
1635
    *index_start = model::cells.size();
22✔
1636
  if (index_end)
22✔
UNCOV
1637
    *index_end = model::cells.size() + n - 1;
×
1638
  for (int32_t i = 0; i < n; i++) {
44✔
1639
    model::cells.push_back(make_unique<CSGCell>());
22✔
1640
  }
1641
  return 0;
22✔
1642
}
1643

1644
extern "C" int cells_size()
44✔
1645
{
1646
  return model::cells.size();
44✔
1647
}
1648

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