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

openmc-dev / openmc / 16335500661

17 Jul 2025 03:29AM UTC coverage: 85.101% (-0.008%) from 85.109%
16335500661

push

github

web-flow
Add accessor methods for LocalCoord (#3494)

Co-authored-by: Paul Romano <paul.k.romano@gmail.com>

121 of 144 new or added lines in 19 files covered. (84.03%)

1 existing line in 1 file now uncovered.

52630 of 61844 relevant lines covered (85.1%)

36281885.49 hits per line

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

79.61
/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
void Cell::set_rotation(const vector<double>& rot)
449✔
44
{
45
  if (fill_ == C_NONE) {
449✔
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) {
449✔
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();
449✔
57
  rotation_.reserve(rot.size() == 9 ? 9 : 12);
449✔
58
  if (rot.size() == 3) {
449✔
59
    double phi = -rot[0] * PI / 180.0;
449✔
60
    double theta = -rot[1] * PI / 180.0;
449✔
61
    double psi = -rot[2] * PI / 180.0;
449✔
62
    rotation_.push_back(std::cos(theta) * std::cos(psi));
449✔
63
    rotation_.push_back(-std::cos(phi) * std::sin(psi) +
×
64
                        std::sin(phi) * std::sin(theta) * std::cos(psi));
449✔
65
    rotation_.push_back(std::sin(phi) * std::sin(psi) +
×
66
                        std::cos(phi) * std::sin(theta) * std::cos(psi));
449✔
67
    rotation_.push_back(std::cos(theta) * std::sin(psi));
449✔
68
    rotation_.push_back(std::cos(phi) * std::cos(psi) +
×
69
                        std::sin(phi) * std::sin(theta) * std::sin(psi));
449✔
70
    rotation_.push_back(-std::sin(phi) * std::cos(psi) +
×
71
                        std::cos(phi) * std::sin(theta) * std::sin(psi));
449✔
72
    rotation_.push_back(-std::sin(theta));
449✔
73
    rotation_.push_back(std::sin(phi) * std::cos(theta));
449✔
74
    rotation_.push_back(std::cos(phi) * std::cos(theta));
449✔
75

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

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

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

99
void Cell::set_temperature(double T, int32_t instance, bool set_contained)
459✔
100
{
101
  if (settings::temperature_method == TemperatureMethod::INTERPOLATION) {
459✔
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) {
459✔
116
    if (instance >= 0) {
427✔
117
      // If temperature vector is not big enough, resize it first
118
      if (sqrtkT_.size() != n_instances_)
350✔
119
        sqrtkT_.resize(n_instances_, sqrtkT_[0]);
48✔
120

121
      // Set temperature for the corresponding instance
122
      sqrtkT_.at(instance) = std::sqrt(K_BOLTZMANN * T);
350✔
123
    } else {
124
      // Set temperature for all instances
125
      for (auto& T_ : sqrtkT_) {
154✔
126
        T_ = std::sqrt(K_BOLTZMANN * T);
77✔
127
      }
128
    }
129
  } else {
130
    if (!set_contained) {
32✔
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);
32✔
138
    for (const auto& entry : contained_cells) {
128✔
139
      auto& cell = model::cells[entry.first];
96✔
140
      assert(cell->type_ == Fill::MATERIAL);
78✔
141
      auto& instances = entry.second;
96✔
142
      for (auto instance : instances) {
336✔
143
        cell->set_temperature(T, instance);
240✔
144
      }
145
    }
146
  }
32✔
147
}
459✔
148

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

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

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

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

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

171
  // Ensure number of temperatures makes sense
172
  auto n_temps = temps.size();
143✔
173
  if (n_temps > 1 && n_temps != n_instances_) {
143✔
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();
143✔
181
  sqrtkT_.resize(temps.size());
143✔
182
  for (int64_t i = 0; i < temps.size(); ++i) {
242✔
183
    this->set_temperature(temps[i], i);
99✔
184
  }
185

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

189
void Cell::to_hdf5(hid_t cell_group) const
24,988✔
190
{
191

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

195
  if (!name_.empty()) {
24,988✔
196
    write_string(group, "name", name_, false);
4,839✔
197
  }
198

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

201
  to_hdf5_inner(group);
24,988✔
202

203
  // Write fill information.
204
  if (type_ == Fill::MATERIAL) {
24,988✔
205
    write_dataset(group, "fill_type", "material");
20,235✔
206
    std::vector<int32_t> mat_ids;
20,235✔
207
    for (auto i_mat : material_) {
41,953✔
208
      if (i_mat != MATERIAL_VOID) {
21,718✔
209
        mat_ids.push_back(model::materials[i_mat]->id_);
13,878✔
210
      } else {
211
        mat_ids.push_back(MATERIAL_VOID);
7,840✔
212
      }
213
    }
214
    if (mat_ids.size() == 1) {
20,235✔
215
      write_dataset(group, "material", mat_ids[0]);
20,034✔
216
    } else {
217
      write_dataset(group, "material", mat_ids);
201✔
218
    }
219

220
    std::vector<double> temps;
20,235✔
221
    for (auto sqrtkT_val : sqrtkT_)
42,076✔
222
      temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
21,841✔
223
    write_dataset(group, "temperature", temps);
20,235✔
224

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

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

245
  close_group(group);
24,988✔
246
}
24,988✔
247

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

252
CSGCell::CSGCell(pugi::xml_node cell_node)
31,215✔
253
{
254
  if (check_for_node(cell_node, "id")) {
31,215✔
255
    id_ = std::stoi(get_node_value(cell_node, "id"));
31,215✔
256
  } else {
257
    fatal_error("Must specify id of cell in geometry XML file.");
×
258
  }
259

260
  if (check_for_node(cell_node, "name")) {
31,215✔
261
    name_ = get_node_value(cell_node, "name");
6,858✔
262
  }
263

264
  if (check_for_node(cell_node, "universe")) {
31,215✔
265
    universe_ = std::stoi(get_node_value(cell_node, "universe"));
29,992✔
266
  } else {
267
    universe_ = 0;
1,223✔
268
  }
269

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

283
  if (fill_present) {
31,215✔
284
    fill_ = std::stoi(get_node_value(cell_node, "fill"));
6,643✔
285
    if (fill_ == universe_) {
6,643✔
286
      fatal_error(fmt::format("Cell {} is filled with the same universe that "
×
287
                              "it is contained in.",
288
        id_));
×
289
    }
290
  } else {
291
    fill_ = C_NONE;
24,572✔
292
  }
293

294
  // Read the material element.  There can be zero materials (filled with a
295
  // universe), more than one material (distribmats), and some materials may
296
  // be "void".
297
  if (material_present) {
31,215✔
298
    vector<std::string> mats {
299
      get_node_array<std::string>(cell_node, "material", true)};
24,572✔
300
    if (mats.size() > 0) {
24,572✔
301
      material_.reserve(mats.size());
24,572✔
302
      for (std::string mat : mats) {
50,633✔
303
        if (mat.compare("void") == 0) {
26,061✔
304
          material_.push_back(MATERIAL_VOID);
8,241✔
305
        } else {
306
          material_.push_back(std::stoi(mat));
17,820✔
307
        }
308
      }
26,061✔
309
    } else {
310
      fatal_error(fmt::format(
×
311
        "An empty material element was specified for cell {}", id_));
×
312
    }
313
  }
24,572✔
314

315
  // Read the temperature element which may be distributed like materials.
316
  if (check_for_node(cell_node, "temperature")) {
31,215✔
317
    sqrtkT_ = get_node_array<double>(cell_node, "temperature");
315✔
318
    sqrtkT_.shrink_to_fit();
315✔
319

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

328
    // Make sure all temperatures are non-negative.
329
    for (auto T : sqrtkT_) {
678✔
330
      if (T < 0) {
363✔
331
        fatal_error(fmt::format(
×
332
          "Cell {} was specified with a negative temperature", id_));
×
333
      }
334
    }
335

336
    // Convert to sqrt(k*T).
337
    for (auto& T : sqrtkT_) {
678✔
338
      T = std::sqrt(K_BOLTZMANN * T);
363✔
339
    }
340
  }
341

342
  // Read the region specification.
343
  std::string region_spec;
31,215✔
344
  if (check_for_node(cell_node, "region")) {
31,215✔
345
    region_spec = get_node_value(cell_node, "region");
22,454✔
346
  }
347

348
  // Get a tokenized representation of the region specification and apply De
349
  // Morgans law
350
  Region region(region_spec, id_);
31,215✔
351
  region_ = region;
31,215✔
352

353
  // Read the translation vector.
354
  if (check_for_node(cell_node, "translation")) {
31,215✔
355
    if (fill_ == C_NONE) {
2,726✔
356
      fatal_error(fmt::format("Cannot apply a translation to cell {}"
×
357
                              " because it is not filled with another universe",
358
        id_));
×
359
    }
360

361
    auto xyz {get_node_array<double>(cell_node, "translation")};
2,726✔
362
    if (xyz.size() != 3) {
2,726✔
363
      fatal_error(
×
364
        fmt::format("Non-3D translation vector applied to cell {}", id_));
×
365
    }
366
    translation_ = xyz;
2,726✔
367
  }
2,726✔
368

369
  // Read the rotation transform.
370
  if (check_for_node(cell_node, "rotation")) {
31,215✔
371
    auto rot {get_node_array<double>(cell_node, "rotation")};
416✔
372
    set_rotation(rot);
416✔
373
  }
416✔
374
}
31,215✔
375

376
//==============================================================================
377

378
void CSGCell::to_hdf5_inner(hid_t group_id) const
24,847✔
379
{
380
  write_string(group_id, "geom_type", "csg", false);
24,847✔
381
  write_string(group_id, "region", region_.str(), false);
24,847✔
382
}
24,847✔
383

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

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

397
    // decrement parenthesis level if there are two adjacent surfaces
398
    if (one < OP_UNION && two < OP_UNION) {
×
399
      parenthesis_level--;
×
400
      // increment if there are two adjacent operators
401
    } else if (one >= OP_UNION && two >= OP_UNION) {
×
402
      parenthesis_level++;
×
403
    }
404

405
    // if the level gets to zero, return the position
406
    if (parenthesis_level == 0) {
×
407
      // move the iterator back one before leaving the loop
408
      // so that all tokens in the parenthesis block are included
409
      it--;
×
410
      break;
×
411
    }
412

413
    // continue loop, one token at a time
414
    it--;
×
415
  }
416
  return it;
×
417
}
418

419
//==============================================================================
420
// Region implementation
421
//==============================================================================
422

423
Region::Region(std::string region_spec, int32_t cell_id)
31,215✔
424
{
425
  // Check if region_spec is not empty.
426
  if (!region_spec.empty()) {
31,215✔
427
    // Parse all halfspaces and operators except for intersection (whitespace).
428
    for (int i = 0; i < region_spec.size();) {
126,740✔
429
      if (region_spec[i] == '(') {
104,286✔
430
        expression_.push_back(OP_LEFT_PAREN);
1,054✔
431
        i++;
1,054✔
432

433
      } else if (region_spec[i] == ')') {
103,232✔
434
        expression_.push_back(OP_RIGHT_PAREN);
1,054✔
435
        i++;
1,054✔
436

437
      } else if (region_spec[i] == '|') {
102,178✔
438
        expression_.push_back(OP_UNION);
2,825✔
439
        i++;
2,825✔
440

441
      } else if (region_spec[i] == '~') {
99,353✔
442
        expression_.push_back(OP_COMPLEMENT);
32✔
443
        i++;
32✔
444

445
      } else if (region_spec[i] == '-' || region_spec[i] == '+' ||
167,207✔
446
                 std::isdigit(region_spec[i])) {
67,886✔
447
        // This is the start of a halfspace specification.  Iterate j until we
448
        // find the end, then push-back everything between i and j.
449
        int j = i + 1;
58,680✔
450
        while (j < region_spec.size() && std::isdigit(region_spec[j])) {
120,023✔
451
          j++;
61,343✔
452
        }
453
        expression_.push_back(std::stoi(region_spec.substr(i, j - i)));
58,680✔
454
        i = j;
58,680✔
455

456
      } else if (std::isspace(region_spec[i])) {
40,641✔
457
        i++;
40,641✔
458

459
      } else {
460
        auto err_msg =
461
          fmt::format("Region specification contains invalid character, \"{}\"",
462
            region_spec[i]);
×
463
        fatal_error(err_msg);
×
464
      }
×
465
    }
466

467
    // Add in intersection operators where a missing operator is needed.
468
    int i = 0;
22,454✔
469
    while (i < expression_.size() - 1) {
97,046✔
470
      bool left_compat {
471
        (expression_[i] < OP_UNION) || (expression_[i] == OP_RIGHT_PAREN)};
74,592✔
472
      bool right_compat {(expression_[i + 1] < OP_UNION) ||
74,592✔
473
                         (expression_[i + 1] == OP_LEFT_PAREN) ||
78,535✔
474
                         (expression_[i + 1] == OP_COMPLEMENT)};
3,943✔
475
      if (left_compat && right_compat) {
74,592✔
476
        expression_.insert(expression_.begin() + i + 1, OP_INTERSECTION);
33,401✔
477
      }
478
      i++;
74,592✔
479
    }
480

481
    // Remove complement operators using DeMorgan's laws
482
    auto it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
22,454✔
483
    while (it != expression_.end()) {
22,486✔
484
      // Erase complement
485
      expression_.erase(it);
32✔
486

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

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

511
    // Convert user IDs to surface indices.
512
    for (auto& r : expression_) {
119,468✔
513
      if (r < OP_UNION) {
97,014✔
514
        const auto& it {model::surface_map.find(abs(r))};
58,680✔
515
        if (it == model::surface_map.end()) {
58,680✔
516
          throw std::runtime_error {
×
517
            "Invalid surface ID " + std::to_string(abs(r)) +
×
518
            " specified in region for cell " + std::to_string(cell_id) + "."};
×
519
        }
520
        r = (r > 0) ? it->second + 1 : -(it->second + 1);
58,680✔
521
      }
522
    }
523

524
    // Check if this is a simple cell.
525
    simple_ = true;
22,454✔
526
    for (int32_t token : expression_) {
108,974✔
527
      if (token == OP_UNION) {
87,369✔
528
        simple_ = false;
849✔
529
        // Ensure intersections have precedence over unions
530
        add_precedence();
849✔
531
        break;
849✔
532
      }
533
    }
534

535
    // If this cell is simple, remove all the superfluous operator tokens.
536
    if (simple_) {
22,454✔
537
      for (auto it = expression_.begin(); it != expression_.end(); it++) {
103,830✔
538
        if (*it == OP_INTERSECTION || *it > OP_COMPLEMENT) {
82,225✔
539
          expression_.erase(it);
30,310✔
540
          it--;
30,310✔
541
        }
542
      }
543
    }
544
    expression_.shrink_to_fit();
22,454✔
545

546
  } else {
547
    simple_ = true;
8,761✔
548
  }
549
}
31,215✔
550

551
//==============================================================================
552

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

568
//==============================================================================
569
//! Add precedence for infix regions so intersections have higher
570
//! precedence than unions using parentheses.
571
//==============================================================================
572

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

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

590
  // Add right parenthesis
591
  // While the start iterator is within the bounds of infix
592
  while (start + 1 < expression_.size()) {
224✔
593
    start++;
224✔
594

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

638
//==============================================================================
639

640
void Region::add_precedence()
849✔
641
{
642
  int32_t current_op = 0;
849✔
643
  std::size_t current_dist = 0;
849✔
644

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

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

672
//==============================================================================
673
//! Convert infix region specification to Reverse Polish Notation (RPN)
674
//!
675
//! This function uses the shunting-yard algorithm.
676
//==============================================================================
677

678
vector<int32_t> Region::generate_postfix(int32_t cell_id) const
44✔
679
{
680
  vector<int32_t> rpn;
44✔
681
  vector<int32_t> stack;
44✔
682

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

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

707
      stack.push_back(token);
352✔
708

709
    } else if (token == OP_LEFT_PAREN) {
198✔
710
      // If the token is a left parenthesis, push it onto the stack
711
      stack.push_back(token);
99✔
712

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

728
      // Pop the left parenthesis.
729
      stack.pop_back();
99✔
730
    }
731
  }
732

733
  while (stack.size() > 0) {
88✔
734
    int32_t op = stack.back();
44✔
735

736
    // If the operator is a parenthesis it is mismatched.
737
    if (op >= OP_RIGHT_PAREN) {
44✔
738
      fatal_error(fmt::format(
×
739
        "Mismatched parentheses in region specification for cell {}", cell_id));
740
    }
741

742
    rpn.push_back(stack.back());
44✔
743
    stack.pop_back();
44✔
744
  }
745

746
  return rpn;
88✔
747
}
44✔
748

749
//==============================================================================
750

751
std::string Region::str() const
24,847✔
752
{
753
  std::stringstream region_spec {};
24,847✔
754
  if (!expression_.empty()) {
24,847✔
755
    for (int32_t token : expression_) {
69,141✔
756
      if (token == OP_LEFT_PAREN) {
52,274✔
757
        region_spec << " (";
940✔
758
      } else if (token == OP_RIGHT_PAREN) {
51,334✔
759
        region_spec << " )";
940✔
760
      } else if (token == OP_COMPLEMENT) {
50,394✔
761
        region_spec << " ~";
×
762
      } else if (token == OP_INTERSECTION) {
50,394✔
763
      } else if (token == OP_UNION) {
47,807✔
764
        region_spec << " |";
2,611✔
765
      } else {
766
        // Note the off-by-one indexing
767
        auto surf_id = model::surfaces[abs(token) - 1]->id_;
45,196✔
768
        region_spec << " " << ((token > 0) ? surf_id : -surf_id);
45,196✔
769
      }
770
    }
771
  }
772
  return region_spec.str();
49,694✔
773
}
24,847✔
774

775
//==============================================================================
776

777
std::pair<double, int32_t> Region::distance(
2,147,483,647✔
778
  Position r, Direction u, int32_t on_surface) const
779
{
780
  double min_dist {INFTY};
2,147,483,647✔
781
  int32_t i_surf {std::numeric_limits<int32_t>::max()};
2,147,483,647✔
782

783
  for (int32_t token : expression_) {
2,147,483,647✔
784
    // Ignore this token if it corresponds to an operator rather than a region.
785
    if (token >= OP_UNION)
2,147,483,647✔
786
      continue;
169,611,236✔
787

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

793
    // Check if this distance is the new minimum.
794
    if (d < min_dist) {
2,147,483,647✔
795
      if (min_dist - d >= FP_PRECISION * min_dist) {
2,147,483,647✔
796
        min_dist = d;
2,147,483,647✔
797
        i_surf = -token;
2,147,483,647✔
798
      }
799
    }
800
  }
801

802
  return {min_dist, i_surf};
2,147,483,647✔
803
}
804

805
//==============================================================================
806

807
bool Region::contains(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
808
{
809
  if (simple_) {
2,147,483,647✔
810
    return contains_simple(r, u, on_surface);
2,147,483,647✔
811
  } else {
812
    return contains_complex(r, u, on_surface);
6,692,038✔
813
  }
814
}
815

816
//==============================================================================
817

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

839
//==============================================================================
840

841
bool Region::contains_complex(Position r, Direction u, int32_t on_surface) const
6,692,038✔
842
{
843
  bool in_cell = true;
6,692,038✔
844
  int total_depth = 0;
6,692,038✔
845

846
  // For each token
847
  for (auto it = expression_.begin(); it != expression_.end(); it++) {
106,339,134✔
848
    int32_t token = *it;
101,504,586✔
849

850
    // If the token is a surface evaluate the sense
851
    // If the token is a union or intersection check to
852
    // short circuit
853
    if (token < OP_UNION) {
101,504,586✔
854
      if (token == on_surface) {
44,350,726✔
855
        in_cell = true;
3,307,529✔
856
      } else if (-token == on_surface) {
41,043,197✔
857
        in_cell = false;
666,759✔
858
      } else {
859
        // Note the off-by-one indexing
860
        bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
40,376,438✔
861
        in_cell = (sense == (token > 0));
40,376,438✔
862
      }
863
    } else if ((token == OP_UNION && in_cell == true) ||
57,153,860✔
864
               (token == OP_INTERSECTION && in_cell == false)) {
27,914,164✔
865
      // If the total depth is zero return
866
      if (total_depth == 0) {
6,234,784✔
867
        return in_cell;
1,857,490✔
868
      }
869

870
      total_depth--;
4,377,294✔
871

872
      // While the iterator is within the bounds of the vector
873
      int depth = 1;
4,377,294✔
874
      do {
875
        // Get next token
876
        it++;
27,672,404✔
877
        int32_t next_token = *it;
27,672,404✔
878

879
        // If the token is an a parenthesis
880
        if (next_token > OP_COMPLEMENT) {
27,672,404✔
881
          // Adjust depth accordingly
882
          if (next_token == OP_RIGHT_PAREN) {
4,565,838✔
883
            depth--;
4,471,566✔
884
          } else {
885
            depth++;
94,272✔
886
          }
887
        }
888
      } while (depth > 0);
27,672,404✔
889
    } else if (token == OP_LEFT_PAREN) {
55,296,370✔
890
      total_depth++;
8,818,841✔
891
    } else if (token == OP_RIGHT_PAREN) {
42,100,235✔
892
      total_depth--;
4,441,547✔
893
    }
894
  }
895
  return in_cell;
4,834,548✔
896
}
897

898
//==============================================================================
899

900
BoundingBox Region::bounding_box(int32_t cell_id) const
88✔
901
{
902
  if (simple_) {
88✔
903
    return bounding_box_simple();
44✔
904
  } else {
905
    auto postfix = generate_postfix(cell_id);
44✔
906
    return bounding_box_complex(postfix);
44✔
907
  }
44✔
908
}
909

910
//==============================================================================
911

912
BoundingBox Region::bounding_box_simple() const
44✔
913
{
914
  BoundingBox bbox;
44✔
915
  for (int32_t token : expression_) {
176✔
916
    bbox &= model::surfaces[abs(token) - 1]->bounding_box(token > 0);
132✔
917
  }
918
  return bbox;
44✔
919
}
920

921
//==============================================================================
922

923
BoundingBox Region::bounding_box_complex(vector<int32_t> postfix) const
44✔
924
{
925
  vector<BoundingBox> stack(postfix.size());
44✔
926
  int i_stack = -1;
44✔
927

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

941
  assert(i_stack == 0);
36✔
942
  return stack.front();
88✔
943
}
44✔
944

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

947
vector<int32_t> Region::surfaces() const
5,378✔
948
{
949
  if (simple_) {
5,378✔
950
    return expression_;
5,378✔
951
  }
952

953
  vector<int32_t> surfaces = expression_;
×
954

955
  auto it = std::find_if(surfaces.begin(), surfaces.end(),
×
956
    [&](const auto& value) { return value >= OP_UNION; });
×
957

958
  while (it != surfaces.end()) {
×
959
    surfaces.erase(it);
×
960

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

965
  return surfaces;
×
966
}
967

968
//==============================================================================
969
// Non-method functions
970
//==============================================================================
971

972
void read_cells(pugi::xml_node node)
6,770✔
973
{
974
  // Count the number of cells.
975
  int n_cells = 0;
6,770✔
976
  for (pugi::xml_node cell_node : node.children("cell")) {
37,985✔
977
    n_cells++;
31,215✔
978
  }
979

980
  // Loop over XML cell elements and populate the array.
981
  model::cells.reserve(n_cells);
6,770✔
982
  for (pugi::xml_node cell_node : node.children("cell")) {
37,985✔
983
    model::cells.push_back(make_unique<CSGCell>(cell_node));
31,215✔
984
  }
985

986
  // Fill the cell map.
987
  for (int i = 0; i < model::cells.size(); i++) {
37,985✔
988
    int32_t id = model::cells[i]->id_;
31,215✔
989
    auto search = model::cell_map.find(id);
31,215✔
990
    if (search == model::cell_map.end()) {
31,215✔
991
      model::cell_map[id] = i;
31,215✔
992
    } else {
993
      fatal_error(
×
994
        fmt::format("Two or more cells use the same unique ID: {}", id));
×
995
    }
996
  }
997

998
  read_dagmc_universes(node);
6,770✔
999

1000
  populate_universes();
6,768✔
1001

1002
  // Allocate the cell overlap count if necessary.
1003
  if (settings::check_overlaps) {
6,768✔
1004
    model::overlap_check_count.resize(model::cells.size(), 0);
276✔
1005
  }
1006

1007
  if (model::cells.size() == 0) {
6,768✔
1008
    fatal_error("No cells were found in the geometry.xml file");
×
1009
  }
1010
}
6,768✔
1011

1012
void populate_universes()
6,770✔
1013
{
1014
  // Used to map universe index to the index of an implicit complement cell for
1015
  // DAGMC universes
1016
  std::unordered_map<int, int> implicit_comp_cells;
6,770✔
1017

1018
  // Populate the Universe vector and map.
1019
  for (int index_cell = 0; index_cell < model::cells.size(); index_cell++) {
38,192✔
1020
    int32_t uid = model::cells[index_cell]->universe_;
31,422✔
1021
    auto it = model::universe_map.find(uid);
31,422✔
1022
    if (it == model::universe_map.end()) {
31,422✔
1023
      model::universes.push_back(make_unique<Universe>());
17,939✔
1024
      model::universes.back()->id_ = uid;
17,939✔
1025
      model::universes.back()->cells_.push_back(index_cell);
17,939✔
1026
      model::universe_map[uid] = model::universes.size() - 1;
17,939✔
1027
    } else {
1028
#ifdef DAGMC
1029
      // Skip implicit complement cells for now
1030
      Universe* univ = model::universes[it->second].get();
1,771✔
1031
      DAGUniverse* dag_univ = dynamic_cast<DAGUniverse*>(univ);
1,771✔
1032
      if (dag_univ && (dag_univ->implicit_complement_idx() == index_cell)) {
1,771✔
1033
        implicit_comp_cells[it->second] = index_cell;
41✔
1034
        continue;
41✔
1035
      }
1036
#endif
1037

1038
      model::universes[it->second]->cells_.push_back(index_cell);
13,442✔
1039
    }
1040
  }
1041

1042
  // Add DAGUniverse implicit complement cells last
1043
  for (const auto& it : implicit_comp_cells) {
6,811✔
1044
    int index_univ = it.first;
41✔
1045
    int index_cell = it.second;
41✔
1046
    model::universes[index_univ]->cells_.push_back(index_cell);
41✔
1047
  }
1048

1049
  model::universes.shrink_to_fit();
6,770✔
1050
}
6,770✔
1051

1052
//==============================================================================
1053
// C-API functions
1054
//==============================================================================
1055

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

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

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

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

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

1135
  int32_t instance_index = instance ? *instance : -1;
91✔
1136
  try {
1137
    *T = model::cells[index]->temperature(instance_index);
91✔
1138
  } catch (const std::exception& e) {
×
1139
    set_errmsg(e.what());
×
1140
    return OPENMC_E_UNASSIGNED;
×
1141
  }
×
1142
  return 0;
91✔
1143
}
1144

1145
//! Get the bounding box of a cell
1146
extern "C" int openmc_cell_bounding_box(
55✔
1147
  const int32_t index, double* llc, double* urc)
1148
{
1149

1150
  BoundingBox bbox;
55✔
1151

1152
  const auto& c = model::cells[index];
55✔
1153
  bbox = c->bounding_box();
55✔
1154

1155
  // set lower left corner values
1156
  llc[0] = bbox.xmin;
55✔
1157
  llc[1] = bbox.ymin;
55✔
1158
  llc[2] = bbox.zmin;
55✔
1159

1160
  // set upper right corner values
1161
  urc[0] = bbox.xmax;
55✔
1162
  urc[1] = bbox.ymax;
55✔
1163
  urc[2] = bbox.zmax;
55✔
1164

1165
  return 0;
55✔
1166
}
1167

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

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

1178
  return 0;
22✔
1179
}
1180

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

1189
  model::cells[index]->set_name(name);
11✔
1190

1191
  return 0;
11✔
1192
}
1193

1194
//==============================================================================
1195
//! Define a containing (parent) cell
1196
//==============================================================================
1197

1198
//! Used to locate a universe fill in the geometry
1199
struct ParentCell {
1200
  bool operator==(const ParentCell& other) const
96✔
1201
  {
1202
    return cell_index == other.cell_index &&
192✔
1203
           lattice_index == other.lattice_index;
192✔
1204
  }
1205

1206
  bool operator<(const ParentCell& other) const
1207
  {
1208
    return cell_index < other.cell_index ||
1209
           (cell_index == other.cell_index &&
1210
             lattice_index < other.lattice_index);
1211
  }
1212

1213
  int64_t cell_index;
1214
  int64_t lattice_index;
1215
};
1216

1217
//! Structure used to insert ParentCell into hashed STL data structures
1218
struct ParentCellHash {
1219
  std::size_t operator()(const ParentCell& p) const
480✔
1220
  {
1221
    return 4096 * p.cell_index + p.lattice_index;
480✔
1222
  }
1223
};
1224

1225
//! Used to manage a traversal stack when locating parent cells of a cell
1226
//! instance in the model
1227
struct ParentCellStack {
1228

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

1239
  //! removes the last parent_cell and clears the visited cells for the popped
1240
  //! cell's universe
1241
  void pop()
48✔
1242
  {
1243
    visited_cells_[this->current_univ()].clear();
48✔
1244
    parent_cells_.pop_back();
48✔
1245
  }
48✔
1246

1247
  //! checks whether or not the parent cell has been visited already for this
1248
  //! search universe
1249
  bool visited(int32_t search_universe, const ParentCell& parent_cell)
416✔
1250
  {
1251
    return visited_cells_[search_universe].count(parent_cell) != 0;
416✔
1252
  }
1253

1254
  //! return the next universe to search for a parent cell
1255
  int32_t current_univ() const
48✔
1256
  {
1257
    return model::cells[parent_cells_.back().cell_index]->universe_;
48✔
1258
  }
1259

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

1263
  //! compute an instance for the provided distribcell index
1264
  int32_t compute_instance(int32_t distribcell_index) const
144✔
1265
  {
1266
    if (distribcell_index == C_NONE)
144✔
1267
      return 0;
64✔
1268

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

1283
  // Accessors
1284
  vector<ParentCell>& parent_cells() { return parent_cells_; }
288✔
1285
  const vector<ParentCell>& parent_cells() const { return parent_cells_; }
1286

1287
  // Data Members
1288
  vector<ParentCell> parent_cells_;
1289
  std::unordered_map<int32_t, std::unordered_set<ParentCell, ParentCellHash>>
1290
    visited_cells_;
1291
};
1292

1293
vector<ParentCell> Cell::find_parent_cells(
×
1294
  int32_t instance, const Position& r) const
1295
{
1296

1297
  // create a temporary particle
1298
  GeometryState dummy_particle {};
×
1299
  dummy_particle.r() = r;
×
1300
  dummy_particle.u() = {0., 0., 1.};
×
1301

1302
  return find_parent_cells(instance, dummy_particle);
×
1303
}
1304

1305
vector<ParentCell> Cell::find_parent_cells(
×
1306
  int32_t instance, GeometryState& p) const
1307
{
1308
  // look up the particle's location
1309
  exhaustive_find_cell(p);
×
1310
  const auto& coords = p.coord();
×
1311

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

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

1336
  // if this loop finished because the cell was found and
1337
  // the instance matches the one requested in the call
1338
  // we have the correct path and can return the stack
1339
  if (cell_found &&
×
1340
      stack.compute_instance(this->distribcell_index_) == instance) {
×
1341
    return stack.parent_cells();
×
1342
  }
1343

1344
  // fall back on an exhaustive search for the cell's parents
1345
  return exhaustive_find_parent_cells(instance);
×
1346
}
1347

1348
vector<ParentCell> Cell::exhaustive_find_parent_cells(int32_t instance) const
96✔
1349
{
1350
  ParentCellStack stack;
96✔
1351
  // start with this cell's universe
1352
  int32_t prev_univ_idx;
1353
  int32_t univ_idx = this->universe_;
96✔
1354

1355
  while (true) {
1356
    const auto& univ = model::universes[univ_idx];
144✔
1357
    prev_univ_idx = univ_idx;
144✔
1358

1359
    // search for a cell that is filled w/ this universe
1360
    for (const auto& cell : model::cells) {
992✔
1361
      // if this is a material-filled cell, move on
1362
      if (cell->type_ == Fill::MATERIAL)
912✔
1363
        continue;
496✔
1364

1365
      if (cell->type_ == Fill::UNIVERSE) {
416✔
1366
        // if this is in the set of cells previously visited for this universe,
1367
        // move on
1368
        if (stack.visited(univ_idx, {model::cell_map[cell->id_], C_NONE}))
256✔
1369
          continue;
×
1370

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

1382
        // start search for universe
1383
        auto lat_it = lattice_univs.begin();
160✔
1384
        while (true) {
1385
          // find the next lattice cell with this universe
1386
          lat_it = std::find(lat_it, lattice_univs.end(), univ_idx);
256✔
1387
          if (lat_it == lattice_univs.end())
256✔
1388
            break;
96✔
1389

1390
          int lattice_idx = lat_it - lattice_univs.begin();
160✔
1391

1392
          // move iterator forward one to avoid finding the same entry
1393
          lat_it++;
160✔
1394
          if (stack.visited(
160✔
1395
                univ_idx, {model::cell_map[cell->id_], lattice_idx}))
160✔
1396
            continue;
96✔
1397

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

1409
    // if we're at the top of the geometry and the instance matches, we're done
1410
    if (univ_idx == model::root_universe &&
288✔
1411
        stack.compute_instance(this->distribcell_index_) == instance)
144✔
1412
      break;
96✔
1413

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

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

1427
  } // end while
48✔
1428

1429
  // reverse the stack so the highest cell comes first
1430
  std::reverse(stack.parent_cells().begin(), stack.parent_cells().end());
96✔
1431
  return stack.parent_cells();
192✔
1432
}
96✔
1433

1434
std::unordered_map<int32_t, vector<int32_t>> Cell::get_contained_cells(
144✔
1435
  int32_t instance, Position* hint) const
1436
{
1437
  std::unordered_map<int32_t, vector<int32_t>> contained_cells;
144✔
1438

1439
  // if this is a material-filled cell it has no contained cells
1440
  if (this->type_ == Fill::MATERIAL)
144✔
1441
    return contained_cells;
48✔
1442

1443
  // find the pathway through the geometry to this cell
1444
  vector<ParentCell> parent_cells;
96✔
1445

1446
  // if a positional hint is provided, attempt to do a fast lookup
1447
  // of the parent cells
1448
  parent_cells = hint ? find_parent_cells(instance, *hint)
192✔
1449
                      : exhaustive_find_parent_cells(instance);
96✔
1450

1451
  // if this cell is filled w/ a material, it contains no other cells
1452
  if (type_ != Fill::MATERIAL) {
96✔
1453
    this->get_contained_cells_inner(contained_cells, parent_cells);
96✔
1454
  }
1455

1456
  return contained_cells;
96✔
1457
}
96✔
1458

1459
//! Get all cells within this cell
1460
void Cell::get_contained_cells_inner(
87,696✔
1461
  std::unordered_map<int32_t, vector<int32_t>>& contained_cells,
1462
  vector<ParentCell>& parent_cells) const
1463
{
1464

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

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

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

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

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

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

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

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

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

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

1639
extern "C" int cells_size()
44✔
1640
{
1641
  return model::cells.size();
44✔
1642
}
1643

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