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

openmc-dev / openmc / 22556083755

02 Mar 2026 12:05AM UTC coverage: 81.414% (-0.09%) from 81.508%
22556083755

Pull #3843

github

web-flow
Merge a9263a317 into 83a7b36ad
Pull Request #3843: Implement cell importance variance reduction scheme.

17521 of 25285 branches covered (69.29%)

Branch coverage included in aggregate %.

60 of 125 new or added lines in 7 files covered. (48.0%)

1 existing line in 1 file now uncovered.

57731 of 67146 relevant lines covered (85.98%)

45338148.88 hits per line

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

72.25
/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/simulation.h"
26
#include "openmc/xml_interface.h"
27

28
namespace openmc {
29

30
//==============================================================================
31
// Global variables
32
//==============================================================================
33

34
namespace model {
35
std::unordered_map<int32_t, int32_t> cell_map;
36
vector<unique_ptr<Cell>> cells;
37

38
} // namespace model
39

40
//==============================================================================
41
// Cell implementation
42
//==============================================================================
43

44
int32_t Cell::n_instances() const
5,584✔
45
{
46
  return model::universes[universe_]->n_instances_;
5,584✔
47
}
48

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

57
  if (rot.size() != 3 && rot.size() != 9) {
423!
58
    fatal_error(fmt::format("Non-3D rotation vector applied to cell {}", id_));
×
59
  }
60

61
  // Compute and store the inverse rotation matrix for the angles given.
62
  rotation_.clear();
423✔
63
  rotation_.reserve(rot.size() == 9 ? 9 : 12);
846!
64
  if (rot.size() == 3) {
423!
65
    double phi = -rot[0] * PI / 180.0;
423✔
66
    double theta = -rot[1] * PI / 180.0;
423✔
67
    double psi = -rot[2] * PI / 180.0;
423✔
68
    rotation_.push_back(std::cos(theta) * std::cos(psi));
423✔
69
    rotation_.push_back(-std::cos(phi) * std::sin(psi) +
423✔
70
                        std::sin(phi) * std::sin(theta) * std::cos(psi));
423✔
71
    rotation_.push_back(std::sin(phi) * std::sin(psi) +
423✔
72
                        std::cos(phi) * std::sin(theta) * std::cos(psi));
423✔
73
    rotation_.push_back(std::cos(theta) * std::sin(psi));
423✔
74
    rotation_.push_back(std::cos(phi) * std::cos(psi) +
423✔
75
                        std::sin(phi) * std::sin(theta) * std::sin(psi));
423✔
76
    rotation_.push_back(-std::sin(phi) * std::cos(psi) +
423✔
77
                        std::cos(phi) * std::sin(theta) * std::sin(psi));
423✔
78
    rotation_.push_back(-std::sin(theta));
423✔
79
    rotation_.push_back(std::sin(phi) * std::cos(theta));
423✔
80
    rotation_.push_back(std::cos(phi) * std::cos(theta));
423✔
81

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

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

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

105
double Cell::importance(int index, int32_t instance) const
874,543,618✔
106
{
107
  if (instance >= 0) {
874,543,618!
108
    return importance_[index][importance_[index].size() > 1 ? instance : 0];
874,543,618!
109
  } else {
NEW
110
    return importance_[index][0];
×
111
  }
112
}
113

114
double Cell::density_mult(int32_t instance) const
2,147,483,647✔
115
{
116
  if (instance >= 0) {
2,147,483,647✔
117
    return density_mult_.size() == 1 ? density_mult_.at(0)
2,147,483,647✔
118
                                     : density_mult_.at(instance);
5,034,975✔
119
  } else {
120
    return density_mult_[0];
77✔
121
  }
122
}
123

124
double Cell::density(int32_t instance) const
132✔
125
{
126
  const int32_t mat_index = material(instance);
132!
127
  if (mat_index == MATERIAL_VOID)
132!
128
    return 0.0;
129

130
  return density_mult(instance) * model::materials[mat_index]->density_gpcc();
264!
131
}
132

133
void Cell::set_temperature(double T, int32_t instance, bool set_contained)
475✔
134
{
135
  if (settings::temperature_method == TemperatureMethod::INTERPOLATION) {
475!
136
    if (T < (data::temperature_min - settings::temperature_tolerance)) {
×
137
      throw std::runtime_error {
×
138
        fmt::format("Temperature of {} K is below minimum temperature at "
×
139
                    "which data is available of {} K.",
140
          T, data::temperature_min)};
×
141
    } else if (T > (data::temperature_max + settings::temperature_tolerance)) {
×
142
      throw std::runtime_error {
×
143
        fmt::format("Temperature of {} K is above maximum temperature at "
×
144
                    "which data is available of {} K.",
145
          T, data::temperature_max)};
×
146
    }
147
  }
148

149
  if (type_ == Fill::MATERIAL) {
475✔
150
    if (instance >= 0) {
445✔
151
      // If temperature vector is not big enough, resize it first
152
      if (sqrtkT_.size() != n_instances())
368✔
153
        sqrtkT_.resize(n_instances(), sqrtkT_[0]);
45✔
154

155
      // Set temperature for the corresponding instance
156
      sqrtkT_.at(instance) = std::sqrt(K_BOLTZMANN * T);
368✔
157
    } else {
158
      // Set temperature for all instances
159
      for (auto& T_ : sqrtkT_) {
154✔
160
        T_ = std::sqrt(K_BOLTZMANN * T);
77✔
161
      }
162
    }
163
  } else {
164
    if (!set_contained) {
30!
165
      throw std::runtime_error {
×
166
        fmt::format("Attempted to set the temperature of cell {} "
×
167
                    "which is not filled by a material.",
168
          id_)};
×
169
    }
170

171
    auto contained_cells = this->get_contained_cells(instance);
30✔
172
    for (const auto& entry : contained_cells) {
120✔
173
      auto& cell = model::cells[entry.first];
90!
174
      assert(cell->type_ == Fill::MATERIAL);
90!
175
      auto& instances = entry.second;
90✔
176
      for (auto instance : instances) {
315✔
177
        cell->set_temperature(T, instance);
225✔
178
      }
179
    }
180
  }
30✔
181
}
475✔
182

183
void Cell::set_density(double density, int32_t instance, bool set_contained)
280✔
184
{
185
  if (type_ != Fill::MATERIAL && !set_contained) {
280!
186
    fatal_error(
×
187
      fmt::format("Attempted to set the density multiplier of cell {} "
×
188
                  "which is not filled by a material.",
189
        id_));
×
190
  }
191

192
  if (type_ == Fill::MATERIAL) {
280✔
193
    const int32_t mat_index = material(instance);
265!
194
    if (mat_index == MATERIAL_VOID)
265!
195
      return;
196

197
    if (instance >= 0) {
265✔
198
      // If density multiplier vector is not big enough, resize it first
199
      if (density_mult_.size() != n_instances())
188✔
200
        density_mult_.resize(n_instances(), density_mult_[0]);
45✔
201

202
      // Set density multiplier for the corresponding instance
203
      density_mult_.at(instance) =
188✔
204
        density / model::materials[mat_index]->density_gpcc();
376!
205
    } else {
206
      // Set density multiplier for all instances
207
      for (auto& x : density_mult_) {
154✔
208
        x = density / model::materials[mat_index]->density_gpcc();
154!
209
      }
210
    }
211
  } else {
212
    auto contained_cells = this->get_contained_cells(instance);
15✔
213
    for (const auto& entry : contained_cells) {
60✔
214
      auto& cell = model::cells[entry.first];
45!
215
      assert(cell->type_ == Fill::MATERIAL);
45!
216
      auto& instances = entry.second;
45✔
217
      for (auto instance : instances) {
90✔
218
        cell->set_density(density, instance);
45✔
219
      }
220
    }
221
  }
15✔
222
}
223

224
void Cell::export_properties_hdf5(hid_t group) const
154✔
225
{
226
  // Create a group for this cell.
227
  auto cell_group = create_group(group, fmt::format("cell {}", id_));
154✔
228

229
  // Write temperature in [K] for one or more cell instances
230
  vector<double> temps;
154✔
231
  for (auto sqrtkT_val : sqrtkT_)
286✔
232
    temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
132✔
233
  write_dataset(cell_group, "temperature", temps);
154✔
234

235
  // Write density for one or more cell instances
236
  if (type_ == Fill::MATERIAL && material_.size() > 0) {
154!
237
    vector<double> density;
132✔
238
    for (int32_t i = 0; i < density_mult_.size(); ++i)
264✔
239
      density.push_back(this->density(i));
132✔
240

241
    write_dataset(cell_group, "density", density);
132✔
242
  }
132✔
243

244
  close_group(cell_group);
154✔
245
}
154✔
246

247
void Cell::import_properties_hdf5(hid_t group)
176✔
248
{
249
  auto cell_group = open_group(group, fmt::format("cell {}", id_));
176✔
250

251
  // Read temperatures from file
252
  vector<double> temps;
176✔
253
  read_dataset(cell_group, "temperature", temps);
176✔
254

255
  // Ensure number of temperatures makes sense
256
  auto n_temps = temps.size();
176!
257
  if (n_temps > 1 && n_temps != n_instances()) {
176!
258
    fatal_error(fmt::format(
×
259
      "Number of temperatures for cell {} doesn't match number of instances",
260
      id_));
×
261
  }
262

263
  // Modify temperatures for the cell
264
  sqrtkT_.clear();
176✔
265
  sqrtkT_.resize(temps.size());
176✔
266
  for (int64_t i = 0; i < temps.size(); ++i) {
308✔
267
    this->set_temperature(temps[i], i);
132✔
268
  }
269

270
  // Read densities
271
  if (object_exists(cell_group, "density")) {
176✔
272
    vector<double> density;
132✔
273
    read_dataset(cell_group, "density", density);
132✔
274

275
    // Ensure number of densities makes sense
276
    auto n_density = density.size();
132!
277
    if (n_density > 1 && n_density != n_instances()) {
132!
278
      fatal_error(fmt::format("Number of densities for cell {} "
×
279
                              "doesn't match number of instances",
280
        id_));
×
281
    }
282

283
    // Set densities.
284
    for (int32_t i = 0; i < n_density; ++i) {
264✔
285
      this->set_density(density[i], i);
132✔
286
    }
287
  }
132✔
288

289
  close_group(cell_group);
176✔
290
}
176✔
291

292
void Cell::to_hdf5(hid_t cell_group) const
28,414✔
293
{
294

295
  // Create a group for this cell.
296
  auto group = create_group(cell_group, fmt::format("cell {}", id_));
28,414✔
297

298
  if (!name_.empty()) {
28,414✔
299
    write_string(group, "name", name_, false);
6,122✔
300
  }
301

302
  write_dataset(group, "universe", model::universes[universe_]->id_);
28,414✔
303

304
  to_hdf5_inner(group);
28,414✔
305

306
  // Write fill information.
307
  if (type_ == Fill::MATERIAL) {
28,414✔
308
    write_dataset(group, "fill_type", "material");
23,010✔
309
    std::vector<int32_t> mat_ids;
23,010✔
310
    for (auto i_mat : material_) {
47,313✔
311
      if (i_mat != MATERIAL_VOID) {
24,303✔
312
        mat_ids.push_back(model::materials[i_mat]->id_);
15,999✔
313
      } else {
314
        mat_ids.push_back(MATERIAL_VOID);
8,304✔
315
      }
316
    }
317
    if (mat_ids.size() == 1) {
23,010✔
318
      write_dataset(group, "material", mat_ids[0]);
22,819✔
319
    } else {
320
      write_dataset(group, "material", mat_ids);
191✔
321
    }
322

323
    std::vector<double> temps;
23,010✔
324
    for (auto sqrtkT_val : sqrtkT_)
48,195✔
325
      temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
25,185✔
326
    write_dataset(group, "temperature", temps);
23,010✔
327

328
    write_dataset(group, "density_mult", density_mult_);
23,010✔
329

330
  } else if (type_ == Fill::UNIVERSE) {
28,414✔
331
    write_dataset(group, "fill_type", "universe");
3,939✔
332
    write_dataset(group, "fill", model::universes[fill_]->id_);
3,939✔
333
    if (translation_ != Position(0, 0, 0)) {
3,939✔
334
      write_dataset(group, "translation", translation_);
1,837✔
335
    }
336
    if (!rotation_.empty()) {
3,939✔
337
      if (rotation_.size() == 12) {
264!
338
        std::array<double, 3> rot {rotation_[9], rotation_[10], rotation_[11]};
264✔
339
        write_dataset(group, "rotation", rot);
264✔
340
      } else {
341
        write_dataset(group, "rotation", rotation_);
×
342
      }
343
    }
344

345
  } else if (type_ == Fill::LATTICE) {
1,465!
346
    write_dataset(group, "fill_type", "lattice");
1,465✔
347
    write_dataset(group, "lattice", model::lattices[fill_]->id_);
1,465✔
348
  }
349

350
  if (importance_[0].size() == 1) {
28,414!
351
    if (importance_[0][0] != 1.0)
28,414!
NEW
352
      write_dataset(group, "importance_neutron", importance_[0][0]);
×
353
  } else {
NEW
354
    write_dataset(group, "importance_neutron", importance_[0]);
×
355
  }
356
  if (importance_[1].size() == 1) {
28,414!
357
    if (importance_[1][0] != 1.0)
28,414!
NEW
358
      write_dataset(group, "importance_photon", importance_[1][0]);
×
359
  } else {
NEW
360
    write_dataset(group, "importance_photon", importance_[1]);
×
361
  }
362
  close_group(group);
28,414✔
363
}
28,414✔
364

365
//==============================================================================
366
// CSGCell implementation
367
//==============================================================================
368

369
CSGCell::CSGCell(pugi::xml_node cell_node)
33,932✔
370
{
371
  if (check_for_node(cell_node, "id")) {
33,932!
372
    id_ = std::stoi(get_node_value(cell_node, "id"));
67,864✔
373
  } else {
374
    fatal_error("Must specify id of cell in geometry XML file.");
×
375
  }
376

377
  if (check_for_node(cell_node, "name")) {
33,932✔
378
    name_ = get_node_value(cell_node, "name");
8,009✔
379
  }
380

381
  if (check_for_node(cell_node, "universe")) {
33,932✔
382
    universe_ = std::stoi(get_node_value(cell_node, "universe"));
65,594✔
383
  } else {
384
    universe_ = 0;
1,135✔
385
  }
386

387
  // Make sure that either material or fill was specified, but not both.
388
  bool fill_present = check_for_node(cell_node, "fill");
33,932✔
389
  bool material_present = check_for_node(cell_node, "material");
33,932✔
390
  if (!(fill_present || material_present)) {
33,932!
391
    fatal_error(
×
392
      fmt::format("Neither material nor fill was specified for cell {}", id_));
×
393
  }
394
  if (fill_present && material_present) {
33,932!
395
    fatal_error(fmt::format("Cell {} has both a material and a fill specified; "
×
396
                            "only one can be specified per cell",
397
      id_));
×
398
  }
399

400
  if (fill_present) {
33,932✔
401
    fill_ = std::stoi(get_node_value(cell_node, "fill"));
14,174✔
402
    if (fill_ == universe_) {
7,087!
403
      fatal_error(fmt::format("Cell {} is filled with the same universe that "
×
404
                              "it is contained in.",
405
        id_));
×
406
    }
407
  } else {
408
    fill_ = C_NONE;
26,845✔
409
  }
410

411
  // Read the material element.  There can be zero materials (filled with a
412
  // universe), more than one material (distribmats), and some materials may
413
  // be "void".
414
  if (material_present) {
33,932✔
415
    vector<std::string> mats {
26,845✔
416
      get_node_array<std::string>(cell_node, "material", true)};
26,845✔
417
    if (mats.size() > 0) {
26,845!
418
      material_.reserve(mats.size());
26,845✔
419
      for (std::string mat : mats) {
55,010✔
420
        if (mat.compare("void") == 0) {
28,165✔
421
          material_.push_back(MATERIAL_VOID);
8,623✔
422
        } else {
423
          material_.push_back(std::stoi(mat));
19,542✔
424
        }
425
      }
28,165✔
426
    } else {
427
      fatal_error(fmt::format(
×
428
        "An empty material element was specified for cell {}", id_));
×
429
    }
430
  }
26,845✔
431

432
  // Read the temperature element which may be distributed like materials.
433
  if (check_for_node(cell_node, "temperature")) {
33,932✔
434
    sqrtkT_ = get_node_array<double>(cell_node, "temperature");
386✔
435
    sqrtkT_.shrink_to_fit();
386✔
436

437
    // Make sure this is a material-filled cell.
438
    if (material_.size() == 0) {
386!
439
      fatal_error(fmt::format(
×
440
        "Cell {} was specified with a temperature but no material. Temperature"
441
        "specification is only valid for cells filled with a material.",
442
        id_));
×
443
    }
444

445
    // Make sure all temperatures are non-negative.
446
    for (auto T : sqrtkT_) {
1,852✔
447
      if (T < 0) {
1,466!
448
        fatal_error(fmt::format(
×
449
          "Cell {} was specified with a negative temperature", id_));
×
450
      }
451
    }
452

453
    // Convert to sqrt(k*T).
454
    for (auto& T : sqrtkT_) {
1,852✔
455
      T = std::sqrt(K_BOLTZMANN * T);
1,466✔
456
    }
457
  }
458

459
  // Read the density element which can be distributed similar to temperature.
460
  // These get assigned to the density multiplier, requiring a division by
461
  // the material density.
462
  // Note: calculating the actual density multiplier is deferred until materials
463
  // are finalized. density_mult_ contains the true density in the meantime.
464
  if (check_for_node(cell_node, "density")) {
33,932✔
465
    density_mult_ = get_node_array<double>(cell_node, "density");
75✔
466
    density_mult_.shrink_to_fit();
75✔
467

468
    // Make sure this is a material-filled cell.
469
    if (material_.size() == 0) {
75!
470
      fatal_error(fmt::format(
×
471
        "Cell {} was specified with a density but no material. Density"
472
        "specification is only valid for cells filled with a material.",
473
        id_));
×
474
    }
475

476
    // Make sure this is a non-void material.
477
    for (auto mat_id : material_) {
150✔
478
      if (mat_id == MATERIAL_VOID) {
75!
479
        fatal_error(fmt::format(
×
480
          "Cell {} was specified with a density, but contains a void "
481
          "material. Density specification is only valid for cells "
482
          "filled with a non-void material.",
483
          id_));
×
484
      }
485
    }
486

487
    // Make sure all densities are non-negative and greater than zero.
488
    for (auto rho : density_mult_) {
1,230✔
489
      if (rho <= 0) {
1,155!
490
        fatal_error(fmt::format(
×
491
          "Cell {} was specified with a density less than or equal to zero",
492
          id_));
×
493
      }
494
    }
495
  }
496

497
  if (check_for_node(cell_node, "importance_neutron")) {
33,932!
NEW
498
    importance_[0] = get_node_array<double>(cell_node, "importance_neutron");
×
NEW
499
    importance_[0].shrink_to_fit();
×
500

501
    // Make sure all importancs are non-negative and greater than zero.
NEW
502
    for (auto importance : importance_[0]) {
×
NEW
503
      if (importance < 0)
×
NEW
504
        fatal_error(fmt::format(
×
NEW
505
          "Cell {} was specified with a negative neutron importance.", id_));
×
NEW
506
      if (!settings::weight_windows_on) {
×
NEW
507
        if (importance != 1.0 && importance > 0.0)
×
NEW
508
          simulation::cell_importances = true;
×
509
      }
510
    }
511
  }
512

513
  if (check_for_node(cell_node, "importance_photon")) {
33,932!
NEW
514
    importance_[1] = get_node_array<double>(cell_node, "importance_photon");
×
NEW
515
    importance_[1].shrink_to_fit();
×
516

517
    // Make sure all importancs are non-negative and greater than zero.
NEW
518
    for (auto importance : importance_[1]) {
×
NEW
519
      if (importance < 0)
×
NEW
520
        fatal_error(fmt::format(
×
NEW
521
          "Cell {} was specified with a negative photon importance.", id_));
×
NEW
522
      if (!settings::weight_windows_on) {
×
NEW
523
        if (importance != 1.0 && importance > 0.0)
×
NEW
524
          simulation::cell_importances = true;
×
525
      }
526
    }
527
  }
528

529
  // Read the region specification.
530
  std::string region_spec;
33,932✔
531
  if (check_for_node(cell_node, "region")) {
33,932✔
532
    region_spec = get_node_value(cell_node, "region");
24,953✔
533
  }
534

535
  // Get a tokenized representation of the region specification and apply De
536
  // Morgans law
537
  Region region(region_spec, id_);
33,932✔
538
  region_ = region;
33,932✔
539

540
  // Read the translation vector.
541
  if (check_for_node(cell_node, "translation")) {
33,932✔
542
    if (fill_ == C_NONE) {
2,557!
543
      fatal_error(fmt::format("Cannot apply a translation to cell {}"
×
544
                              " because it is not filled with another universe",
545
        id_));
×
546
    }
547

548
    auto xyz {get_node_array<double>(cell_node, "translation")};
2,557✔
549
    if (xyz.size() != 3) {
2,557!
550
      fatal_error(
×
551
        fmt::format("Non-3D translation vector applied to cell {}", id_));
×
552
    }
553
    translation_ = xyz;
2,557✔
554
  }
2,557✔
555

556
  // Read the rotation transform.
557
  if (check_for_node(cell_node, "rotation")) {
33,932✔
558
    auto rot {get_node_array<double>(cell_node, "rotation")};
390✔
559
    set_rotation(rot);
390✔
560
  }
390✔
561
}
33,932✔
562

563
//==============================================================================
564

565
void CSGCell::to_hdf5_inner(hid_t group_id) const
28,250✔
566
{
567
  write_string(group_id, "geom_type", "csg", false);
28,250✔
568
  write_string(group_id, "region", region_.str(), false);
28,250✔
569
}
28,250✔
570

571
//==============================================================================
572

573
vector<int32_t>::iterator CSGCell::find_left_parenthesis(
×
574
  vector<int32_t>::iterator start, const vector<int32_t>& infix)
575
{
576
  // start search at zero
577
  int parenthesis_level = 0;
×
578
  auto it = start;
×
579
  while (it != infix.begin()) {
×
580
    // look at two tokens at a time
581
    int32_t one = *it;
×
582
    int32_t two = *(it - 1);
×
583

584
    // decrement parenthesis level if there are two adjacent surfaces
585
    if (one < OP_UNION && two < OP_UNION) {
×
586
      parenthesis_level--;
×
587
      // increment if there are two adjacent operators
588
    } else if (one >= OP_UNION && two >= OP_UNION) {
×
589
      parenthesis_level++;
×
590
    }
591

592
    // if the level gets to zero, return the position
593
    if (parenthesis_level == 0) {
×
594
      // move the iterator back one before leaving the loop
595
      // so that all tokens in the parenthesis block are included
596
      it--;
×
597
      break;
598
    }
599

600
    // continue loop, one token at a time
601
    it--;
602
  }
603
  return it;
×
604
}
605

606
//==============================================================================
607
// Region implementation
608
//==============================================================================
609

610
Region::Region(std::string region_spec, int32_t cell_id)
34,031✔
611
{
612
  // Check if region_spec is not empty.
613
  if (!region_spec.empty()) {
34,031✔
614
    // Parse all halfspaces and operators except for intersection (whitespace).
615
    for (int i = 0; i < region_spec.size();) {
146,712✔
616
      if (region_spec[i] == '(') {
121,660✔
617
        expression_.push_back(OP_LEFT_PAREN);
1,290✔
618
        i++;
1,290✔
619

620
      } else if (region_spec[i] == ')') {
120,370✔
621
        expression_.push_back(OP_RIGHT_PAREN);
1,290✔
622
        i++;
1,290✔
623

624
      } else if (region_spec[i] == '|') {
119,080✔
625
        expression_.push_back(OP_UNION);
3,823✔
626
        i++;
3,823✔
627

628
      } else if (region_spec[i] == '~') {
115,257✔
629
        expression_.push_back(OP_COMPLEMENT);
30✔
630
        i++;
30✔
631

632
      } else if (region_spec[i] == '-' || region_spec[i] == '+' ||
194,452!
633
                 std::isdigit(region_spec[i])) {
79,225✔
634
        // This is the start of a halfspace specification.  Iterate j until we
635
        // find the end, then push-back everything between i and j.
636
        int j = i + 1;
67,482✔
637
        while (j < region_spec.size() && std::isdigit(region_spec[j])) {
137,318✔
638
          j++;
69,836✔
639
        }
640
        expression_.push_back(std::stoi(region_spec.substr(i, j - i)));
134,964✔
641
        i = j;
67,482✔
642

643
      } else if (std::isspace(region_spec[i])) {
47,745!
644
        i++;
47,745✔
645

646
      } else {
647
        auto err_msg =
×
648
          fmt::format("Region specification contains invalid character, \"{}\"",
649
            region_spec[i]);
×
650
        fatal_error(err_msg);
×
651
      }
×
652
    }
653

654
    // Add in intersection operators where a missing operator is needed.
655
    int i = 0;
656
    while (i < expression_.size() - 1) {
112,522✔
657
      bool left_compat {
87,470✔
658
        (expression_[i] < OP_UNION) || (expression_[i] == OP_RIGHT_PAREN)};
87,470✔
659
      bool right_compat {(expression_[i + 1] < OP_UNION) ||
87,470✔
660
                         (expression_[i + 1] == OP_LEFT_PAREN) ||
87,470✔
661
                         (expression_[i + 1] == OP_COMPLEMENT)};
5,173✔
662
      if (left_compat && right_compat) {
87,470✔
663
        expression_.insert(expression_.begin() + i + 1, OP_INTERSECTION);
38,607✔
664
      }
665
      i++;
666
    }
667

668
    // Remove complement operators using DeMorgan's laws
669
    auto it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
25,052✔
670
    while (it != expression_.end()) {
25,082✔
671
      // Erase complement
672
      expression_.erase(it);
30✔
673

674
      // Define stop given left parenthesis or not
675
      auto stop = it;
30✔
676
      if (*it == OP_LEFT_PAREN) {
30!
677
        int depth = 1;
678
        do {
240✔
679
          stop++;
240✔
680
          if (*stop > OP_COMPLEMENT) {
240✔
681
            if (*stop == OP_RIGHT_PAREN) {
30!
682
              depth--;
30✔
683
            } else {
684
              depth++;
×
685
            }
686
          }
687
        } while (depth > 0);
240✔
688
        it++;
30✔
689
      }
690

691
      // apply DeMorgan's law to any surfaces/operators between these
692
      // positions in the RPN
693
      apply_demorgan(it, stop);
30✔
694
      // update iterator position
695
      it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
30✔
696
    }
697

698
    // Convert user IDs to surface indices.
699
    for (auto& r : expression_) {
137,544✔
700
      if (r < OP_UNION) {
112,492✔
701
        const auto& it {model::surface_map.find(abs(r))};
67,482!
702
        if (it == model::surface_map.end()) {
67,482!
703
          throw std::runtime_error {
×
704
            "Invalid surface ID " + std::to_string(abs(r)) +
×
705
            " specified in region for cell " + std::to_string(cell_id) + "."};
×
706
        }
707
        r = (r > 0) ? it->second + 1 : -(it->second + 1);
67,482✔
708
      }
709
    }
710

711
    // Check if this is a simple cell.
712
    simple_ = true;
25,052✔
713
    for (int32_t token : expression_) {
124,514✔
714
      if (token == OP_UNION) {
100,650✔
715
        simple_ = false;
1,188✔
716
        // Ensure intersections have precedence over unions
717
        enforce_precedence();
1,188✔
718
        break;
719
      }
720
    }
721

722
    // If this cell is simple, remove all the superfluous operator tokens.
723
    if (simple_) {
25,052✔
724
      for (auto it = expression_.begin(); it != expression_.end(); it++) {
116,968✔
725
        if (*it == OP_INTERSECTION || *it > OP_COMPLEMENT) {
93,104!
726
          expression_.erase(it);
34,620✔
727
          it--;
93,104✔
728
        }
729
      }
730
    }
731
    expression_.shrink_to_fit();
25,052✔
732

733
  } else {
734
    simple_ = true;
8,979✔
735
  }
736
}
34,031✔
737

738
//==============================================================================
739

740
void Region::apply_demorgan(
30✔
741
  vector<int32_t>::iterator start, vector<int32_t>::iterator stop)
742
{
743
  do {
210✔
744
    if (*start < OP_UNION) {
210✔
745
      *start *= -1;
120✔
746
    } else if (*start == OP_UNION) {
90!
747
      *start = OP_INTERSECTION;
×
748
    } else if (*start == OP_INTERSECTION) {
90!
749
      *start = OP_UNION;
90✔
750
    }
751
    start++;
210✔
752
  } while (start < stop);
210✔
753
}
30✔
754

755
//==============================================================================
756
//! Add precedence for infix regions so intersections have higher
757
//! precedence than unions using parentheses.
758
//==============================================================================
759

760
void Region::add_parentheses(int64_t start)
96✔
761
{
762
  int32_t start_token = expression_[start];
96!
763
  // Add left parenthesis and set new position to be after parenthesis
764
  if (start_token == OP_UNION) {
96!
765
    start += 2;
×
766
  }
767
  expression_.insert(expression_.begin() + start - 1, OP_LEFT_PAREN);
96✔
768

769
  // Add right parenthesis
770
  // While the start iterator is within the bounds of infix
771
  while (start + 1 < expression_.size()) {
430✔
772
    start++;
408✔
773

774
    // If the current token is an operator and is different than the start token
775
    if (expression_[start] >= OP_UNION && expression_[start] != start_token) {
408✔
776
      // Skip wrapped regions but save iterator position to check precedence and
777
      // add right parenthesis, right parenthesis position depends on the
778
      // operator, when the operator is a union then do not include the operator
779
      // in the region, when the operator is an intersection then include the
780
      // operator and next surface
781
      if (expression_[start] == OP_LEFT_PAREN) {
85✔
782
        int depth = 1;
783
        do {
44✔
784
          start++;
44✔
785
          if (expression_[start] > OP_COMPLEMENT) {
44✔
786
            if (expression_[start] == OP_RIGHT_PAREN) {
11!
787
              depth--;
11✔
788
            } else {
789
              depth++;
×
790
            }
791
          }
792
        } while (depth > 0);
44✔
793
      } else {
794
        if (start_token == OP_UNION) {
74!
795
          --start;
×
796
        }
797
        expression_.insert(expression_.begin() + start, OP_RIGHT_PAREN);
74✔
798
        return;
74✔
799
      }
800
    }
801
  }
802
  // If we get here a right parenthesis hasn't been placed
803
  expression_.push_back(OP_RIGHT_PAREN);
22✔
804
}
805

806
//==============================================================================
807
//! Add parentheses to enforce operator precedence in region expressions
808
//!
809
//! This function ensures that intersection operators have higher precedence
810
//! than union operators by adding parentheses where needed. For example:
811
//!   "1 2 | 3" becomes "(1 2) | 3"
812
//!   "1 | 2 3" becomes "1 | (2 3)"
813
//!
814
//! The algorithm uses stacks to track the current operator type and its
815
//! position at each parenthesis depth level. When it encounters a different
816
//! operator at the same depth, it adds parentheses to group the
817
//! higher-precedence operations.
818
//==============================================================================
819

820
void Region::enforce_precedence()
1,188✔
821
{
822
  // Stack tracking the operator type at each depth (0 = no operator seen yet)
823
  vector<int32_t> op_stack = {0};
1,188✔
824

825
  // Stack tracking where the operator sequence started at each depth
826
  vector<std::size_t> pos_stack = {0};
1,188✔
827

828
  for (int64_t i = 0; i < expression_.size(); ++i) {
21,593✔
829
    int32_t token = expression_[i];
20,405✔
830

831
    if (token == OP_LEFT_PAREN) {
20,405✔
832
      // Entering a new parenthesis level - push new tracking state
833
      op_stack.push_back(0);
1,486✔
834
      pos_stack.push_back(0);
1,486✔
835
      continue;
1,486✔
836
    } else if (token == OP_RIGHT_PAREN) {
18,919✔
837
      // Exiting a parenthesis level - pop tracking state (keep at least one)
838
      if (op_stack.size() > 1) {
1,445!
839
        op_stack.pop_back();
1,445✔
840
        pos_stack.pop_back();
1,445✔
841
      }
842
      continue;
1,445✔
843
    }
844

845
    if (token == OP_UNION || token == OP_INTERSECTION) {
17,474✔
846
      if (op_stack.back() == 0) {
8,143✔
847
        // First operator at this depth - record it and its position
848
        op_stack.back() = token;
2,718✔
849
        pos_stack.back() = i;
2,718✔
850
      } else if (token != op_stack.back()) {
5,425✔
851
        // Encountered a different operator at the same depth - need to add
852
        // parentheses to enforce precedence. Intersection has higher
853
        // precedence, so we parenthesize the intersection terms.
854
        if (op_stack.back() == OP_INTERSECTION) {
96✔
855
          add_parentheses(pos_stack.back());
48✔
856
        } else {
857
          add_parentheses(i);
48✔
858
        }
859

860
        // Restart the scan since we modified the expression
861
        i = -1; // Will be incremented to 0 by the for loop
96✔
862
        op_stack = {0};
96✔
863
        pos_stack = {0};
96✔
864
      }
865
    }
866
  }
867
}
1,188✔
868

869
//==============================================================================
870
//! Convert infix region specification to Reverse Polish Notation (RPN)
871
//!
872
//! This function uses the shunting-yard algorithm.
873
//==============================================================================
874

875
vector<int32_t> Region::generate_postfix(int32_t cell_id) const
44✔
876
{
877
  vector<int32_t> rpn;
44✔
878
  vector<int32_t> stack;
44✔
879

880
  for (int32_t token : expression_) {
990✔
881
    if (token < OP_UNION) {
946✔
882
      // If token is not an operator, add it to output
883
      rpn.push_back(token);
396✔
884
    } else if (token < OP_RIGHT_PAREN) {
550✔
885
      // Regular operators union, intersection, complement
886
      while (stack.size() > 0) {
561✔
887
        int32_t op = stack.back();
462✔
888

889
        if (op < OP_RIGHT_PAREN && ((token == OP_COMPLEMENT && token < op) ||
462!
890
                                     (token != OP_COMPLEMENT && token <= op))) {
209!
891
          // While there is an operator, op, on top of the stack, if the token
892
          // is left-associative and its precedence is less than or equal to
893
          // that of op or if the token is right-associative and its precedence
894
          // is less than that of op, move op to the output queue and push the
895
          // token on to the stack. Note that only complement is
896
          // right-associative.
897
          rpn.push_back(op);
209✔
898
          stack.pop_back();
209✔
899
        } else {
900
          break;
901
        }
902
      }
903

904
      stack.push_back(token);
352✔
905

906
    } else if (token == OP_LEFT_PAREN) {
198✔
907
      // If the token is a left parenthesis, push it onto the stack
908
      stack.push_back(token);
99✔
909

910
    } else {
911
      // If the token is a right parenthesis, move operators from the stack to
912
      // the output queue until reaching the left parenthesis.
913
      for (auto it = stack.rbegin(); *it != OP_LEFT_PAREN; it++) {
198✔
914
        // If we run out of operators without finding a left parenthesis, it
915
        // means there are mismatched parentheses.
916
        if (it == stack.rend()) {
99!
917
          fatal_error(fmt::format(
×
918
            "Mismatched parentheses in region specification for cell {}",
919
            cell_id));
920
        }
921
        rpn.push_back(stack.back());
99✔
922
        stack.pop_back();
99✔
923
      }
924

925
      // Pop the left parenthesis.
926
      stack.pop_back();
946✔
927
    }
928
  }
929

930
  while (stack.size() > 0) {
44✔
931
    int32_t op = stack.back();
44!
932

933
    // If the operator is a parenthesis it is mismatched.
934
    if (op >= OP_RIGHT_PAREN) {
44!
935
      fatal_error(fmt::format(
×
936
        "Mismatched parentheses in region specification for cell {}", cell_id));
937
    }
938

939
    rpn.push_back(stack.back());
44✔
940
    stack.pop_back();
88✔
941
  }
942

943
  return rpn;
44✔
944
}
44✔
945

946
//==============================================================================
947

948
std::string Region::str() const
28,349✔
949
{
950
  std::stringstream region_spec {};
28,349✔
951
  if (!expression_.empty()) {
28,349✔
952
    for (int32_t token : expression_) {
83,955✔
953
      if (token == OP_LEFT_PAREN) {
63,902✔
954
        region_spec << " (";
1,218✔
955
      } else if (token == OP_RIGHT_PAREN) {
62,684✔
956
        region_spec << " )";
1,218✔
957
      } else if (token == OP_COMPLEMENT) {
61,466!
958
        region_spec << " ~";
×
959
      } else if (token == OP_INTERSECTION) {
61,466✔
960
      } else if (token == OP_UNION) {
58,125✔
961
        region_spec << " |";
3,417✔
962
      } else {
963
        // Note the off-by-one indexing
964
        auto surf_id = model::surfaces[abs(token) - 1]->id_;
54,708✔
965
        region_spec << " " << ((token > 0) ? surf_id : -surf_id);
54,708✔
966
      }
967
    }
968
  }
969
  return region_spec.str();
56,698✔
970
}
28,349✔
971

972
//==============================================================================
973

974
std::pair<double, int32_t> Region::distance(
2,147,483,647✔
975
  Position r, Direction u, int32_t on_surface) const
976
{
977
  double min_dist {INFTY};
2,147,483,647✔
978
  int32_t i_surf {std::numeric_limits<int32_t>::max()};
2,147,483,647✔
979

980
  for (int32_t token : expression_) {
2,147,483,647✔
981
    // Ignore this token if it corresponds to an operator rather than a region.
982
    if (token >= OP_UNION)
2,147,483,647✔
983
      continue;
174,174,736✔
984

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

990
    // Check if this distance is the new minimum.
991
    if (d < min_dist) {
2,147,483,647✔
992
      if (min_dist - d >= FP_PRECISION * min_dist) {
2,147,483,647!
993
        min_dist = d;
2,147,483,647✔
994
        i_surf = -token;
2,147,483,647✔
995
      }
996
    }
997
  }
998

999
  return {min_dist, i_surf};
2,147,483,647✔
1000
}
1001

1002
//==============================================================================
1003

1004
bool Region::contains(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
1005
{
1006
  if (simple_) {
2,147,483,647✔
1007
    return contains_simple(r, u, on_surface);
2,147,483,647✔
1008
  } else {
1009
    return contains_complex(r, u, on_surface);
7,414,435✔
1010
  }
1011
}
1012

1013
//==============================================================================
1014

1015
bool Region::contains_simple(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
1016
{
1017
  for (int32_t token : expression_) {
2,147,483,647✔
1018
    // Assume that no tokens are operators. Evaluate the sense of particle with
1019
    // respect to the surface and see if the token matches the sense. If the
1020
    // particle's surface attribute is set and matches the token, that
1021
    // overrides the determination based on sense().
1022
    if (token == on_surface) {
2,147,483,647✔
1023
    } else if (-token == on_surface) {
2,147,483,647✔
1024
      return false;
1025
    } else {
1026
      // Note the off-by-one indexing
1027
      bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
2,147,483,647✔
1028
      if (sense != (token > 0)) {
2,147,483,647✔
1029
        return false;
1030
      }
1031
    }
1032
  }
1033
  return true;
1034
}
1035

1036
//==============================================================================
1037

1038
bool Region::contains_complex(Position r, Direction u, int32_t on_surface) const
7,414,435✔
1039
{
1040
  bool in_cell = true;
7,414,435✔
1041
  int total_depth = 0;
7,414,435✔
1042

1043
  // For each token
1044
  for (auto it = expression_.begin(); it != expression_.end(); it++) {
116,312,718✔
1045
    int32_t token = *it;
110,848,660✔
1046

1047
    // If the token is a surface evaluate the sense
1048
    // If the token is a union or intersection check to
1049
    // short circuit
1050
    if (token < OP_UNION) {
110,848,660✔
1051
      if (token == on_surface) {
48,461,210✔
1052
        in_cell = true;
1053
      } else if (-token == on_surface) {
44,800,992✔
1054
        in_cell = false;
1055
      } else {
1056
        // Note the off-by-one indexing
1057
        bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
44,085,528✔
1058
        in_cell = (sense == (token > 0));
44,085,528✔
1059
      }
1060
    } else if ((token == OP_UNION && in_cell == true) ||
62,387,450✔
1061
               (token == OP_INTERSECTION && in_cell == false)) {
30,047,977✔
1062
      // If the total depth is zero return
1063
      if (total_depth == 0) {
6,675,338✔
1064
        return in_cell;
1,950,377✔
1065
      }
1066

1067
      total_depth--;
4,724,961✔
1068

1069
      // While the iterator is within the bounds of the vector
1070
      int depth = 1;
4,724,961✔
1071
      do {
29,479,698✔
1072
        // Get next token
1073
        it++;
29,479,698✔
1074
        int32_t next_token = *it;
29,479,698✔
1075

1076
        // If the token is an a parenthesis
1077
        if (next_token > OP_COMPLEMENT) {
29,479,698✔
1078
          // Adjust depth accordingly
1079
          if (next_token == OP_RIGHT_PAREN) {
4,914,165✔
1080
            depth--;
4,819,563✔
1081
          } else {
1082
            depth++;
94,602✔
1083
          }
1084
        }
1085
      } while (depth > 0);
29,479,698✔
1086
    } else if (token == OP_LEFT_PAREN) {
55,712,112✔
1087
      total_depth++;
9,695,149✔
1088
    } else if (token == OP_RIGHT_PAREN) {
46,016,963✔
1089
      total_depth--;
4,970,188✔
1090
    }
1091
  }
1092
  return in_cell;
1093
}
1094

1095
//==============================================================================
1096

1097
BoundingBox Region::bounding_box(int32_t cell_id) const
88✔
1098
{
1099
  if (simple_) {
88✔
1100
    return bounding_box_simple();
44✔
1101
  } else {
1102
    auto postfix = generate_postfix(cell_id);
44✔
1103
    return bounding_box_complex(postfix);
88✔
1104
  }
44✔
1105
}
1106

1107
//==============================================================================
1108

1109
BoundingBox Region::bounding_box_simple() const
44✔
1110
{
1111
  BoundingBox bbox;
44✔
1112
  for (int32_t token : expression_) {
176✔
1113
    bbox &= model::surfaces[abs(token) - 1]->bounding_box(token > 0);
132✔
1114
  }
1115
  return bbox;
44✔
1116
}
1117

1118
//==============================================================================
1119

1120
BoundingBox Region::bounding_box_complex(vector<int32_t> postfix) const
44✔
1121
{
1122
  vector<BoundingBox> stack(postfix.size());
44✔
1123
  int i_stack = -1;
44✔
1124

1125
  for (auto& token : postfix) {
792✔
1126
    if (token == OP_UNION) {
748✔
1127
      stack[i_stack - 1] = stack[i_stack - 1] | stack[i_stack];
154✔
1128
      i_stack--;
154✔
1129
    } else if (token == OP_INTERSECTION) {
594✔
1130
      stack[i_stack - 1] = stack[i_stack - 1] & stack[i_stack];
198✔
1131
      i_stack--;
198✔
1132
    } else {
1133
      i_stack++;
396✔
1134
      stack[i_stack] = model::surfaces[abs(token) - 1]->bounding_box(token > 0);
396✔
1135
    }
1136
  }
1137

1138
  assert(i_stack == 0);
44!
1139
  return stack.front();
44✔
1140
}
44✔
1141

1142
//==============================================================================
1143

1144
vector<int32_t> Region::surfaces() const
5,270✔
1145
{
1146
  if (simple_) {
5,270✔
1147
    return expression_;
5,250✔
1148
  }
1149

1150
  vector<int32_t> surfaces = expression_;
20✔
1151

1152
  auto it = std::find_if(surfaces.begin(), surfaces.end(),
20✔
1153
    [&](const auto& value) { return value >= OP_UNION; });
20!
1154

1155
  while (it != surfaces.end()) {
60✔
1156
    surfaces.erase(it);
40✔
1157

1158
    it = std::find_if(surfaces.begin(), surfaces.end(),
40✔
1159
      [&](const auto& value) { return value >= OP_UNION; });
80!
1160
  }
1161

1162
  return surfaces;
20✔
1163
}
5,270✔
1164

1165
//==============================================================================
1166
// Non-method functions
1167
//==============================================================================
1168

1169
void read_cells(pugi::xml_node node)
8,079✔
1170
{
1171
  simulation::cell_importances = false;
8,079✔
1172
  // Count the number of cells.
1173
  int n_cells = 0;
8,079✔
1174
  for (pugi::xml_node cell_node : node.children("cell")) {
42,011✔
1175
    n_cells++;
33,932✔
1176
  }
1177

1178
  // Loop over XML cell elements and populate the array.
1179
  model::cells.reserve(n_cells);
8,079✔
1180
  for (pugi::xml_node cell_node : node.children("cell")) {
42,011✔
1181
    model::cells.push_back(make_unique<CSGCell>(cell_node));
33,932✔
1182
  }
1183

1184
  // Fill the cell map.
1185
  for (int i = 0; i < model::cells.size(); i++) {
42,011✔
1186
    int32_t id = model::cells[i]->id_;
33,932!
1187
    auto search = model::cell_map.find(id);
33,932!
1188
    if (search == model::cell_map.end()) {
33,932!
1189
      model::cell_map[id] = i;
33,932✔
1190
    } else {
1191
      fatal_error(
×
1192
        fmt::format("Two or more cells use the same unique ID: {}", id));
×
1193
    }
1194
  }
1195

1196
  read_dagmc_universes(node);
8,079✔
1197

1198
  populate_universes();
8,077✔
1199

1200
  // Allocate the cell overlap count if necessary.
1201
  if (settings::check_overlaps) {
8,077✔
1202
    model::overlap_check_count.resize(model::cells.size(), 0);
119✔
1203
  }
1204

1205
  if (model::cells.size() == 0) {
8,077!
1206
    fatal_error("No cells were found in the geometry.xml file");
×
1207
  }
1208
}
8,077✔
1209

1210
void populate_universes()
8,079✔
1211
{
1212
  // Used to map universe index to the index of an implicit complement cell for
1213
  // DAGMC universes
1214
  std::unordered_map<int, int> implicit_comp_cells;
8,079✔
1215

1216
  // Populate the Universe vector and map.
1217
  for (int index_cell = 0; index_cell < model::cells.size(); index_cell++) {
42,227✔
1218
    int32_t uid = model::cells[index_cell]->universe_;
34,148✔
1219
    auto it = model::universe_map.find(uid);
34,148✔
1220
    if (it == model::universe_map.end()) {
34,148✔
1221
      model::universes.push_back(make_unique<Universe>());
39,230✔
1222
      model::universes.back()->id_ = uid;
19,615✔
1223
      model::universes.back()->cells_.push_back(index_cell);
19,615✔
1224
      model::universe_map[uid] = model::universes.size() - 1;
19,615✔
1225
    } else {
1226
#ifdef OPENMC_DAGMC_ENABLED
1227
      // Skip implicit complement cells for now
1228
      Universe* univ = model::universes[it->second].get();
1,970!
1229
      DAGUniverse* dag_univ = dynamic_cast<DAGUniverse*>(univ);
1,970!
1230
      if (dag_univ && (dag_univ->implicit_complement_idx() == index_cell)) {
1,970✔
1231
        implicit_comp_cells[it->second] = index_cell;
46✔
1232
        continue;
46✔
1233
      }
1234
#endif
1235

1236
      model::universes[it->second]->cells_.push_back(index_cell);
14,487✔
1237
    }
1238
  }
1239

1240
  // Add DAGUniverse implicit complement cells last
1241
  for (const auto& it : implicit_comp_cells) {
8,125✔
1242
    int index_univ = it.first;
46✔
1243
    int index_cell = it.second;
46✔
1244
    model::universes[index_univ]->cells_.push_back(index_cell);
46!
1245
  }
1246

1247
  model::universes.shrink_to_fit();
8,079✔
1248
}
8,079✔
1249

1250
//==============================================================================
1251
// C-API functions
1252
//==============================================================================
1253

1254
extern "C" int openmc_cell_get_fill(
182✔
1255
  int32_t index, int* type, int32_t** indices, int32_t* n)
1256
{
1257
  if (index >= 0 && index < model::cells.size()) {
182!
1258
    Cell& c {*model::cells[index]};
182!
1259
    *type = static_cast<int>(c.type_);
182✔
1260
    if (c.type_ == Fill::MATERIAL) {
182!
1261
      *indices = c.material_.data();
182✔
1262
      *n = c.material_.size();
182✔
1263
    } else {
1264
      *indices = &c.fill_;
×
1265
      *n = 1;
×
1266
    }
1267
  } else {
1268
    set_errmsg("Index in cells array is out of bounds.");
×
1269
    return OPENMC_E_OUT_OF_BOUNDS;
×
1270
  }
1271
  return 0;
1272
}
1273

1274
extern "C" int openmc_cell_set_fill(
11✔
1275
  int32_t index, int type, int32_t n, const int32_t* indices)
1276
{
1277
  Fill filltype = static_cast<Fill>(type);
11✔
1278
  if (index >= 0 && index < model::cells.size()) {
11!
1279
    Cell& c {*model::cells[index]};
11!
1280
    if (filltype == Fill::MATERIAL) {
11!
1281
      c.type_ = Fill::MATERIAL;
11✔
1282
      c.material_.clear();
11!
1283
      for (int i = 0; i < n; i++) {
22✔
1284
        int i_mat = indices[i];
11✔
1285
        if (i_mat == MATERIAL_VOID) {
11!
1286
          c.material_.push_back(MATERIAL_VOID);
×
1287
        } else if (i_mat >= 0 && i_mat < model::materials.size()) {
11!
1288
          c.material_.push_back(i_mat);
11✔
1289
        } else {
1290
          set_errmsg("Index in materials array is out of bounds.");
×
1291
          return OPENMC_E_OUT_OF_BOUNDS;
×
1292
        }
1293
      }
1294
      c.material_.shrink_to_fit();
11✔
1295
    } else if (filltype == Fill::UNIVERSE) {
×
1296
      c.type_ = Fill::UNIVERSE;
×
1297
    } else {
1298
      c.type_ = Fill::LATTICE;
×
1299
    }
1300
  } else {
1301
    set_errmsg("Index in cells array is out of bounds.");
×
1302
    return OPENMC_E_OUT_OF_BOUNDS;
×
1303
  }
1304
  return 0;
1305
}
1306

1307
extern "C" int openmc_cell_set_temperature(
88✔
1308
  int32_t index, double T, const int32_t* instance, bool set_contained)
1309
{
1310
  if (index < 0 || index >= model::cells.size()) {
88!
1311
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
1312
    return OPENMC_E_OUT_OF_BOUNDS;
×
1313
  }
1314

1315
  int32_t instance_index = instance ? *instance : -1;
88✔
1316
  try {
88✔
1317
    model::cells[index]->set_temperature(T, instance_index, set_contained);
88✔
1318
  } catch (const std::exception& e) {
×
1319
    set_errmsg(e.what());
×
1320
    return OPENMC_E_UNASSIGNED;
×
1321
  }
×
1322
  return 0;
1323
}
1324

1325
extern "C" int openmc_cell_set_density(
88✔
1326
  int32_t index, double density, const int32_t* instance, bool set_contained)
1327
{
1328
  if (index < 0 || index >= model::cells.size()) {
88!
1329
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
1330
    return OPENMC_E_OUT_OF_BOUNDS;
×
1331
  }
1332

1333
  int32_t instance_index = instance ? *instance : -1;
88✔
1334
  try {
88✔
1335
    model::cells[index]->set_density(density, instance_index, set_contained);
88✔
1336
  } catch (const std::exception& e) {
×
1337
    set_errmsg(e.what());
×
1338
    return OPENMC_E_UNASSIGNED;
×
1339
  }
×
1340
  return 0;
1341
}
1342

1343
extern "C" int openmc_cell_get_temperature(
91✔
1344
  int32_t index, const int32_t* instance, double* T)
1345
{
1346
  if (index < 0 || index >= model::cells.size()) {
91!
1347
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
1348
    return OPENMC_E_OUT_OF_BOUNDS;
×
1349
  }
1350

1351
  int32_t instance_index = instance ? *instance : -1;
91✔
1352
  try {
91✔
1353
    *T = model::cells[index]->temperature(instance_index);
91✔
1354
  } catch (const std::exception& e) {
×
1355
    set_errmsg(e.what());
×
1356
    return OPENMC_E_UNASSIGNED;
×
1357
  }
×
1358
  return 0;
91✔
1359
}
1360

1361
extern "C" int openmc_cell_get_density(
88✔
1362
  int32_t index, const int32_t* instance, double* density)
1363
{
1364
  if (index < 0 || index >= model::cells.size()) {
88!
1365
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
1366
    return OPENMC_E_OUT_OF_BOUNDS;
×
1367
  }
1368

1369
  int32_t instance_index = instance ? *instance : -1;
88✔
1370
  try {
88✔
1371
    if (model::cells[index]->type_ != Fill::MATERIAL) {
88!
1372
      fatal_error(
×
1373
        fmt::format("Cell {}, instance {} is not filled with a material.",
×
1374
          model::cells[index]->id_, instance_index));
×
1375
    }
1376

1377
    int32_t mat_index = model::cells[index]->material(instance_index);
88!
1378
    if (mat_index == MATERIAL_VOID) {
88!
1379
      *density = 0.0;
×
1380
    } else {
1381
      *density = model::cells[index]->density_mult(instance_index) *
88✔
1382
                 model::materials[mat_index]->density_gpcc();
176!
1383
    }
1384
  } catch (const std::exception& e) {
×
1385
    set_errmsg(e.what());
×
1386
    return OPENMC_E_UNASSIGNED;
×
1387
  }
×
1388
  return 0;
1389
}
1390

1391
//! Get the bounding box of a cell
1392
extern "C" int openmc_cell_bounding_box(
55✔
1393
  const int32_t index, double* llc, double* urc)
1394
{
1395

1396
  BoundingBox bbox;
55✔
1397

1398
  const auto& c = model::cells[index];
55✔
1399
  bbox = c->bounding_box();
55✔
1400

1401
  // set lower left corner values
1402
  llc[0] = bbox.min.x;
55✔
1403
  llc[1] = bbox.min.y;
55✔
1404
  llc[2] = bbox.min.z;
55✔
1405

1406
  // set upper right corner values
1407
  urc[0] = bbox.max.x;
55✔
1408
  urc[1] = bbox.max.y;
55✔
1409
  urc[2] = bbox.max.z;
55✔
1410

1411
  return 0;
55✔
1412
}
1413

1414
//! Get the name of a cell
1415
extern "C" int openmc_cell_get_name(int32_t index, const char** name)
22✔
1416
{
1417
  if (index < 0 || index >= model::cells.size()) {
22!
1418
    set_errmsg("Index in cells array is out of bounds.");
×
1419
    return OPENMC_E_OUT_OF_BOUNDS;
×
1420
  }
1421

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

1424
  return 0;
22✔
1425
}
1426

1427
//! Set the name of a cell
1428
extern "C" int openmc_cell_set_name(int32_t index, const char* name)
11✔
1429
{
1430
  if (index < 0 || index >= model::cells.size()) {
11!
1431
    set_errmsg("Index in cells array is out of bounds.");
×
1432
    return OPENMC_E_OUT_OF_BOUNDS;
×
1433
  }
1434

1435
  model::cells[index]->set_name(name);
22✔
1436

1437
  return 0;
11✔
1438
}
1439

1440
//==============================================================================
1441
//! Define a containing (parent) cell
1442
//==============================================================================
1443

1444
//! Used to locate a universe fill in the geometry
1445
struct ParentCell {
1446
  bool operator==(const ParentCell& other) const
135✔
1447
  {
1448
    return cell_index == other.cell_index &&
135!
1449
           lattice_index == other.lattice_index;
135!
1450
  }
1451

1452
  bool operator<(const ParentCell& other) const
1453
  {
1454
    return cell_index < other.cell_index ||
1455
           (cell_index == other.cell_index &&
1456
             lattice_index < other.lattice_index);
1457
  }
1458

1459
  int64_t cell_index;
1460
  int64_t lattice_index;
1461
};
1462

1463
//! Structure used to insert ParentCell into hashed STL data structures
1464
struct ParentCellHash {
1465
  std::size_t operator()(const ParentCell& p) const
631✔
1466
  {
1467
    return 4096 * p.cell_index + p.lattice_index;
631!
1468
  }
1469
};
1470

1471
//! Used to manage a traversal stack when locating parent cells of a cell
1472
//! instance in the model
1473
struct ParentCellStack {
106✔
1474

1475
  //! push method that adds to the parent_cells visited cells for this search
1476
  //! universe
1477
  void push(int32_t search_universe, const ParentCell& pc)
105✔
1478
  {
1479
    parent_cells_.push_back(pc);
105✔
1480
    // add parent cell to the set of cells we've visited for this search
1481
    // universe
1482
    visited_cells_[search_universe].insert(pc);
105✔
1483
  }
105✔
1484

1485
  //! removes the last parent_cell and clears the visited cells for the popped
1486
  //! cell's universe
1487
  void pop()
75✔
1488
  {
1489
    visited_cells_[this->current_univ()].clear();
75✔
1490
    parent_cells_.pop_back();
75✔
1491
  }
75✔
1492

1493
  //! checks whether or not the parent cell has been visited already for this
1494
  //! search universe
1495
  bool visited(int32_t search_universe, const ParentCell& parent_cell)
526✔
1496
  {
1497
    return visited_cells_[search_universe].count(parent_cell) != 0;
526✔
1498
  }
1499

1500
  //! return the next universe to search for a parent cell
1501
  int32_t current_univ() const
75✔
1502
  {
1503
    return model::cells[parent_cells_.back().cell_index]->universe_;
75✔
1504
  }
1505

1506
  //! indicates whether nor not parent cells are present on the stack
1507
  bool empty() const { return parent_cells_.empty(); }
75✔
1508

1509
  //! compute an instance for the provided distribcell index
1510
  int32_t compute_instance(int32_t distribcell_index) const
181✔
1511
  {
1512
    if (distribcell_index == C_NONE)
181✔
1513
      return 0;
1514

1515
    int32_t instance = 0;
120✔
1516
    for (const auto& parent_cell : this->parent_cells_) {
225✔
1517
      auto& cell = model::cells[parent_cell.cell_index];
105!
1518
      if (cell->type_ == Fill::UNIVERSE) {
105!
1519
        instance += cell->offset_[distribcell_index];
×
1520
      } else if (cell->type_ == Fill::LATTICE) {
105!
1521
        auto& lattice = model::lattices[cell->fill_];
105✔
1522
        instance +=
105✔
1523
          lattice->offset(distribcell_index, parent_cell.lattice_index);
105✔
1524
      }
1525
    }
1526
    return instance;
1527
  }
1528

1529
  // Accessors
1530
  vector<ParentCell>& parent_cells() { return parent_cells_; }
106✔
1531
  const vector<ParentCell>& parent_cells() const { return parent_cells_; }
1532

1533
  // Data Members
1534
  vector<ParentCell> parent_cells_;
1535
  std::unordered_map<int32_t, std::unordered_set<ParentCell, ParentCellHash>>
1536
    visited_cells_;
1537
};
1538

1539
vector<ParentCell> Cell::find_parent_cells(
×
1540
  int32_t instance, const Position& r) const
1541
{
1542

1543
  // create a temporary particle
1544
  GeometryState dummy_particle {};
×
1545
  dummy_particle.r() = r;
×
1546
  dummy_particle.u() = {0., 0., 1.};
×
1547

1548
  return find_parent_cells(instance, dummy_particle);
×
1549
}
×
1550

1551
vector<ParentCell> Cell::find_parent_cells(
×
1552
  int32_t instance, GeometryState& p) const
1553
{
1554
  // look up the particle's location
1555
  exhaustive_find_cell(p);
×
1556
  const auto& coords = p.coord();
×
1557

1558
  // build a parent cell stack from the particle coordinates
1559
  ParentCellStack stack;
×
1560
  bool cell_found = false;
×
1561
  for (auto it = coords.begin(); it != coords.end(); it++) {
×
1562
    const auto& coord = *it;
×
1563
    const auto& cell = model::cells[coord.cell()];
×
1564
    // if the cell at this level matches the current cell, stop adding to the
1565
    // stack
1566
    if (coord.cell() == model::cell_map[this->id_]) {
×
1567
      cell_found = true;
1568
      break;
1569
    }
1570

1571
    // if filled with a lattice, get the lattice index from the next
1572
    // level in the coordinates to push to the stack
1573
    int lattice_idx = C_NONE;
×
1574
    if (cell->type_ == Fill::LATTICE) {
×
1575
      const auto& next_coord = *(it + 1);
×
1576
      lattice_idx = model::lattices[next_coord.lattice()]->get_flat_index(
×
1577
        next_coord.lattice_index());
1578
    }
1579
    stack.push(coord.universe(), {coord.cell(), lattice_idx});
×
1580
  }
1581

1582
  // if this loop finished because the cell was found and
1583
  // the instance matches the one requested in the call
1584
  // we have the correct path and can return the stack
1585
  if (cell_found &&
×
1586
      stack.compute_instance(this->distribcell_index_) == instance) {
×
1587
    return stack.parent_cells();
×
1588
  }
1589

1590
  // fall back on an exhaustive search for the cell's parents
1591
  return exhaustive_find_parent_cells(instance);
×
1592
}
×
1593

1594
vector<ParentCell> Cell::exhaustive_find_parent_cells(int32_t instance) const
106✔
1595
{
1596
  ParentCellStack stack;
106✔
1597
  // start with this cell's universe
1598
  int32_t prev_univ_idx;
106✔
1599
  int32_t univ_idx = this->universe_;
106✔
1600

1601
  while (true) {
181✔
1602
    const auto& univ = model::universes[univ_idx];
181✔
1603
    prev_univ_idx = univ_idx;
181✔
1604

1605
    // search for a cell that is filled w/ this universe
1606
    for (const auto& cell : model::cells) {
1,159✔
1607
      // if this is a material-filled cell, move on
1608
      if (cell->type_ == Fill::MATERIAL)
1,083✔
1609
        continue;
602✔
1610

1611
      if (cell->type_ == Fill::UNIVERSE) {
481✔
1612
        // if this is in the set of cells previously visited for this universe,
1613
        // move on
1614
        if (stack.visited(univ_idx, {model::cell_map[cell->id_], C_NONE}))
286!
1615
          continue;
×
1616

1617
        // if this cell contains the universe we're searching for, add it to the
1618
        // stack
1619
        if (cell->fill_ == univ_idx) {
286!
1620
          stack.push(univ_idx, {model::cell_map[cell->id_], C_NONE});
×
1621
          univ_idx = cell->universe_;
×
1622
        }
1623
      } else if (cell->type_ == Fill::LATTICE) {
195!
1624
        // retrieve the lattice and lattice universes
1625
        const auto& lattice = model::lattices[cell->fill_];
195✔
1626
        const auto& lattice_univs = lattice->universes_;
195✔
1627

1628
        // start search for universe
1629
        auto lat_it = lattice_univs.begin();
195✔
1630
        while (true) {
465✔
1631
          // find the next lattice cell with this universe
1632
          lat_it = std::find(lat_it, lattice_univs.end(), univ_idx);
330✔
1633
          if (lat_it == lattice_univs.end())
330✔
1634
            break;
1635

1636
          int lattice_idx = lat_it - lattice_univs.begin();
240✔
1637

1638
          // move iterator forward one to avoid finding the same entry
1639
          lat_it++;
240✔
1640
          if (stack.visited(
480✔
1641
                univ_idx, {model::cell_map[cell->id_], lattice_idx}))
240✔
1642
            continue;
135✔
1643

1644
          // add this cell and lattice index to the stack and exit loop
1645
          stack.push(univ_idx, {model::cell_map[cell->id_], lattice_idx});
105✔
1646
          univ_idx = cell->universe_;
105✔
1647
          break;
105✔
1648
        }
135✔
1649
      }
1650
      // if we've updated the universe, break
1651
      if (prev_univ_idx != univ_idx)
481✔
1652
        break;
1653
    } // end cell loop search for universe
1654

1655
    // if we're at the top of the geometry and the instance matches, we're done
1656
    if (univ_idx == model::root_universe &&
217!
1657
        stack.compute_instance(this->distribcell_index_) == instance)
181✔
1658
      break;
1659

1660
    // if there is no match on the original cell's universe, report an error
1661
    if (univ_idx == this->universe_) {
75!
1662
      fatal_error(
×
1663
        fmt::format("Could not find the parent cells for cell {}, instance {}.",
×
1664
          this->id_, instance));
×
1665
    }
1666

1667
    // if we don't find a suitable update, adjust the stack and continue
1668
    if (univ_idx == model::root_universe || univ_idx == prev_univ_idx) {
75!
1669
      stack.pop();
75✔
1670
      univ_idx = stack.empty() ? this->universe_ : stack.current_univ();
75!
1671
    }
1672

1673
  } // end while
1674

1675
  // reverse the stack so the highest cell comes first
1676
  std::reverse(stack.parent_cells().begin(), stack.parent_cells().end());
106✔
1677
  return stack.parent_cells();
212✔
1678
}
106✔
1679

1680
std::unordered_map<int32_t, vector<int32_t>> Cell::get_contained_cells(
151✔
1681
  int32_t instance, Position* hint) const
1682
{
1683
  std::unordered_map<int32_t, vector<int32_t>> contained_cells;
151✔
1684

1685
  // if this is a material-filled cell it has no contained cells
1686
  if (this->type_ == Fill::MATERIAL)
151✔
1687
    return contained_cells;
1688

1689
  // find the pathway through the geometry to this cell
1690
  vector<ParentCell> parent_cells;
106!
1691

1692
  // if a positional hint is provided, attempt to do a fast lookup
1693
  // of the parent cells
1694
  parent_cells = hint ? find_parent_cells(instance, *hint)
106!
1695
                      : exhaustive_find_parent_cells(instance);
106✔
1696

1697
  // if this cell is filled w/ a material, it contains no other cells
1698
  if (type_ != Fill::MATERIAL) {
106!
1699
    this->get_contained_cells_inner(contained_cells, parent_cells);
106✔
1700
  }
1701

1702
  return contained_cells;
106✔
1703
}
151✔
1704

1705
//! Get all cells within this cell
1706
void Cell::get_contained_cells_inner(
82,278✔
1707
  std::unordered_map<int32_t, vector<int32_t>>& contained_cells,
1708
  vector<ParentCell>& parent_cells) const
1709
{
1710

1711
  // filled by material, determine instance based on parent cells
1712
  if (type_ == Fill::MATERIAL) {
82,278✔
1713
    int instance = 0;
81,692✔
1714
    if (this->distribcell_index_ >= 0) {
81,692!
1715
      for (auto& parent_cell : parent_cells) {
245,074✔
1716
        auto& cell = model::cells[parent_cell.cell_index];
163,382✔
1717
        if (cell->type_ == Fill::UNIVERSE) {
163,382✔
1718
          instance += cell->offset_[distribcell_index_];
80,192✔
1719
        } else if (cell->type_ == Fill::LATTICE) {
83,190!
1720
          auto& lattice = model::lattices[cell->fill_];
83,190✔
1721
          instance += lattice->offset(
83,190✔
1722
            this->distribcell_index_, parent_cell.lattice_index);
83,190✔
1723
        }
1724
      }
1725
    }
1726
    // add entry to contained cells
1727
    contained_cells[model::cell_map[id_]].push_back(instance);
81,692✔
1728
    // filled with universe, add the containing cell to the parent cells
1729
    // and recurse
1730
  } else if (type_ == Fill::UNIVERSE) {
586✔
1731
    parent_cells.push_back({model::cell_map[id_], -1});
496✔
1732
    auto& univ = model::universes[fill_];
496✔
1733
    for (auto cell_index : univ->cells_) {
2,973✔
1734
      auto& cell = model::cells[cell_index];
2,477✔
1735
      cell->get_contained_cells_inner(contained_cells, parent_cells);
2,477✔
1736
    }
1737
    parent_cells.pop_back();
496✔
1738
    // filled with a lattice, visit each universe in the lattice
1739
    // with a recursive call to collect the cell instances
1740
  } else if (type_ == Fill::LATTICE) {
90!
1741
    auto& lattice = model::lattices[fill_];
90✔
1742
    for (auto i = lattice->begin(); i != lattice->end(); ++i) {
79,470✔
1743
      auto& univ = model::universes[*i];
79,380✔
1744
      parent_cells.push_back({model::cell_map[id_], i.indx_});
79,380✔
1745
      for (auto cell_index : univ->cells_) {
159,075✔
1746
        auto& cell = model::cells[cell_index];
79,695✔
1747
        cell->get_contained_cells_inner(contained_cells, parent_cells);
79,695✔
1748
      }
1749
      parent_cells.pop_back();
79,380✔
1750
    }
1751
  }
1752
}
82,278✔
1753

1754
//! Return the index in the cells array of a cell with a given ID
1755
extern "C" int openmc_get_cell_index(int32_t id, int32_t* index)
492✔
1756
{
1757
  auto it = model::cell_map.find(id);
492✔
1758
  if (it != model::cell_map.end()) {
492✔
1759
    *index = it->second;
481✔
1760
    return 0;
481✔
1761
  } else {
1762
    set_errmsg("No cell exists with ID=" + std::to_string(id) + ".");
11✔
1763
    return OPENMC_E_INVALID_ID;
11✔
1764
  }
1765
}
1766

1767
//! Return the ID of a cell
1768
extern "C" int openmc_cell_get_id(int32_t index, int32_t* id)
600,976✔
1769
{
1770
  if (index >= 0 && index < model::cells.size()) {
600,976!
1771
    *id = model::cells[index]->id_;
600,976✔
1772
    return 0;
600,976✔
1773
  } else {
1774
    set_errmsg("Index in cells array is out of bounds.");
×
1775
    return OPENMC_E_OUT_OF_BOUNDS;
×
1776
  }
1777
}
1778

1779
//! Set the ID of a cell
1780
extern "C" int openmc_cell_set_id(int32_t index, int32_t id)
22✔
1781
{
1782
  if (index >= 0 && index < model::cells.size()) {
22!
1783
    model::cells[index]->id_ = id;
22✔
1784
    model::cell_map[id] = index;
22✔
1785
    return 0;
22✔
1786
  } else {
1787
    set_errmsg("Index in cells array is out of bounds.");
×
1788
    return OPENMC_E_OUT_OF_BOUNDS;
×
1789
  }
1790
}
1791

1792
//! Return the translation vector of a cell
1793
extern "C" int openmc_cell_get_translation(int32_t index, double xyz[])
55✔
1794
{
1795
  if (index >= 0 && index < model::cells.size()) {
55!
1796
    auto& cell = model::cells[index];
55✔
1797
    xyz[0] = cell->translation_.x;
55✔
1798
    xyz[1] = cell->translation_.y;
55✔
1799
    xyz[2] = cell->translation_.z;
55✔
1800
    return 0;
55✔
1801
  } else {
1802
    set_errmsg("Index in cells array is out of bounds.");
×
1803
    return OPENMC_E_OUT_OF_BOUNDS;
×
1804
  }
1805
}
1806

1807
//! Set the translation vector of a cell
1808
extern "C" int openmc_cell_set_translation(int32_t index, const double xyz[])
33✔
1809
{
1810
  if (index >= 0 && index < model::cells.size()) {
33!
1811
    if (model::cells[index]->fill_ == C_NONE) {
33✔
1812
      set_errmsg(fmt::format("Cannot apply a translation to cell {}"
11✔
1813
                             " because it is not filled with another universe",
1814
        index));
1815
      return OPENMC_E_GEOMETRY;
11✔
1816
    }
1817
    model::cells[index]->translation_ = Position(xyz);
22✔
1818
    return 0;
22✔
1819
  } else {
1820
    set_errmsg("Index in cells array is out of bounds.");
×
1821
    return OPENMC_E_OUT_OF_BOUNDS;
×
1822
  }
1823
}
1824

1825
//! Return the rotation matrix of a cell
1826
extern "C" int openmc_cell_get_rotation(int32_t index, double rot[], size_t* n)
55✔
1827
{
1828
  if (index >= 0 && index < model::cells.size()) {
55!
1829
    auto& cell = model::cells[index];
55✔
1830
    *n = cell->rotation_.size();
55✔
1831
    std::memcpy(rot, cell->rotation_.data(), *n * sizeof(cell->rotation_[0]));
55✔
1832
    return 0;
55✔
1833
  } else {
1834
    set_errmsg("Index in cells array is out of bounds.");
×
1835
    return OPENMC_E_OUT_OF_BOUNDS;
×
1836
  }
1837
}
1838

1839
//! Set the flattened rotation matrix of a cell
1840
extern "C" int openmc_cell_set_rotation(
44✔
1841
  int32_t index, const double rot[], size_t rot_len)
1842
{
1843
  if (index >= 0 && index < model::cells.size()) {
44!
1844
    if (model::cells[index]->fill_ == C_NONE) {
44✔
1845
      set_errmsg(fmt::format("Cannot apply a rotation to cell {}"
11✔
1846
                             " because it is not filled with another universe",
1847
        index));
1848
      return OPENMC_E_GEOMETRY;
11✔
1849
    }
1850
    std::vector<double> vec_rot(rot, rot + rot_len);
33✔
1851
    model::cells[index]->set_rotation(vec_rot);
33✔
1852
    return 0;
33✔
1853
  } else {
44✔
1854
    set_errmsg("Index in cells array is out of bounds.");
×
1855
    return OPENMC_E_OUT_OF_BOUNDS;
×
1856
  }
1857
}
1858

1859
//! Get the number of instances of the requested cell
1860
extern "C" int openmc_cell_get_num_instances(
11✔
1861
  int32_t index, int32_t* num_instances)
1862
{
1863
  if (index < 0 || index >= model::cells.size()) {
11!
1864
    set_errmsg("Index in cells array is out of bounds.");
×
1865
    return OPENMC_E_OUT_OF_BOUNDS;
×
1866
  }
1867
  *num_instances = model::cells[index]->n_instances();
11✔
1868
  return 0;
11✔
1869
}
1870

1871
//! Extend the cells array by n elements
1872
extern "C" int openmc_extend_cells(
22✔
1873
  int32_t n, int32_t* index_start, int32_t* index_end)
1874
{
1875
  if (index_start)
22!
1876
    *index_start = model::cells.size();
22✔
1877
  if (index_end)
22!
1878
    *index_end = model::cells.size() + n - 1;
×
1879
  for (int32_t i = 0; i < n; i++) {
44✔
1880
    model::cells.push_back(make_unique<CSGCell>());
22✔
1881
  }
1882
  return 0;
22✔
1883
}
1884

1885
extern "C" int cells_size()
44✔
1886
{
1887
  return model::cells.size();
44✔
1888
}
1889

1890
} // namespace openmc
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc