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

openmc-dev / openmc / 17681453486

12 Sep 2025 05:21PM UTC coverage: 85.189% (-0.02%) from 85.206%
17681453486

Pull #3546

github

web-flow
Merge 454c67301 into c7175289e
Pull Request #3546: Add distributed cell density multipliers

160 of 200 new or added lines in 13 files covered. (80.0%)

153 existing lines in 7 files now uncovered.

53094 of 62325 relevant lines covered (85.19%)

39134622.1 hits per line

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

78.65
/src/cell.cpp
1

2
#include "openmc/cell.h"
3

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

13
#include <fmt/core.h>
14

15
#include "openmc/capi.h"
16
#include "openmc/constants.h"
17
#include "openmc/dagmc.h"
18
#include "openmc/error.h"
19
#include "openmc/geometry.h"
20
#include "openmc/hdf5_interface.h"
21
#include "openmc/lattice.h"
22
#include "openmc/material.h"
23
#include "openmc/nuclide.h"
24
#include "openmc/settings.h"
25
#include "openmc/xml_interface.h"
26

27
namespace openmc {
28

29
//==============================================================================
30
// Global variables
31
//==============================================================================
32

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

37
} // namespace model
38

39
//==============================================================================
40
// Cell implementation
41
//==============================================================================
42

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

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

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

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

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

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

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

104
double Cell::density_mult(int32_t instance) const
2,147,483,647✔
105
{
106
  if (instance >= 0) {
2,147,483,647✔
107
    double rho =
108
      rho_mult_.size() == 1 ? rho_mult_.at(0) : rho_mult_.at(instance);
2,147,483,647✔
109
    return rho;
2,147,483,647✔
110
  } else {
111
    return rho_mult_[0];
70✔
112
  }
113
}
114

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

121
  double rho_mult = density_mult(instance);
168✔
122
  return rho_mult * model::materials[mat_index]->density_gpcc();
168✔
123
}
124

125
void Cell::set_temperature(double T, int32_t instance, bool set_contained)
552✔
126
{
127
  if (settings::temperature_method == TemperatureMethod::INTERPOLATION) {
552✔
128
    if (T < (data::temperature_min - settings::temperature_tolerance)) {
×
UNCOV
129
      throw std::runtime_error {
×
130
        fmt::format("Temperature of {} K is below minimum temperature at "
×
131
                    "which data is available of {} K.",
132
          T, data::temperature_min)};
×
133
    } else if (T > (data::temperature_max + settings::temperature_tolerance)) {
×
UNCOV
134
      throw std::runtime_error {
×
135
        fmt::format("Temperature of {} K is above maximum temperature at "
×
136
                    "which data is available of {} K.",
UNCOV
137
          T, data::temperature_max)};
×
138
    }
139
  }
140

141
  if (type_ == Fill::MATERIAL) {
552✔
142
    if (instance >= 0) {
520✔
143
      // If temperature vector is not big enough, resize it first
144
      if (sqrtkT_.size() != n_instances())
422✔
145
        sqrtkT_.resize(n_instances(), sqrtkT_[0]);
48✔
146

147
      // Set temperature for the corresponding instance
148
      sqrtkT_.at(instance) = std::sqrt(K_BOLTZMANN * T);
422✔
149
    } else {
150
      // Set temperature for all instances
151
      for (auto& T_ : sqrtkT_) {
196✔
152
        T_ = std::sqrt(K_BOLTZMANN * T);
98✔
153
      }
154
    }
155
  } else {
156
    if (!set_contained) {
32✔
UNCOV
157
      throw std::runtime_error {
×
158
        fmt::format("Attempted to set the temperature of cell {} "
×
159
                    "which is not filled by a material.",
UNCOV
160
          id_)};
×
161
    }
162

163
    auto contained_cells = this->get_contained_cells(instance);
32✔
164
    for (const auto& entry : contained_cells) {
128✔
165
      auto& cell = model::cells[entry.first];
96✔
166
      assert(cell->type_ == Fill::MATERIAL);
78✔
167
      auto& instances = entry.second;
96✔
168
      for (auto instance : instances) {
336✔
169
        cell->set_temperature(T, instance);
240✔
170
      }
171
    }
172
  }
32✔
173
}
552✔
174

175
void Cell::set_density(double rho, int32_t instance, bool set_contained)
330✔
176
{
177
  if (type_ != Fill::MATERIAL && !set_contained) {
330✔
NEW
178
    fatal_error(
×
NEW
179
      fmt::format("Attempted to set the density multiplier of cell {} "
×
180
                  "which is not filled by a material.",
NEW
181
        id_));
×
182
  }
183

184
  if (type_ == Fill::MATERIAL) {
330✔
185
    const int32_t mat_index = material(instance);
314✔
186
    if (mat_index == MATERIAL_VOID)
314✔
NEW
187
      return;
×
188

189
    if (instance >= 0) {
314✔
190
      // If density multiplier vector is not big enough, resize it first
191
      if (rho_mult_.size() != n_instances())
230✔
192
        rho_mult_.resize(n_instances(), rho_mult_[0]);
48✔
193

194
      // Set density multiplier for the corresponding instance
195
      rho_mult_.at(instance) =
230✔
196
        rho / model::materials[mat_index]->density_gpcc();
230✔
197
    } else {
198
      // Set density multiplier for all instances
199
      for (auto& Rho_ : rho_mult_) {
168✔
200
        Rho_ = rho / model::materials[mat_index]->density_gpcc();
84✔
201
      }
202
    }
203
  } else {
204
    auto contained_cells = this->get_contained_cells(instance);
16✔
205
    for (const auto& entry : contained_cells) {
64✔
206
      auto& cell = model::cells[entry.first];
48✔
207
      assert(cell->type_ == Fill::MATERIAL);
39✔
208
      auto& instances = entry.second;
48✔
209
      for (auto instance : instances) {
96✔
210
        cell->set_density(rho, instance);
48✔
211
      }
212
    }
213
  }
16✔
214
}
215

216
void Cell::export_properties_hdf5(hid_t group) const
196✔
217
{
218
  // Create a group for this cell.
219
  auto cell_group = create_group(group, fmt::format("cell {}", id_));
392✔
220

221
  // Write temperature in [K] for one or more cell instances
222
  vector<double> temps;
196✔
223
  for (auto sqrtkT_val : sqrtkT_)
364✔
224
    temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
168✔
225
  write_dataset(cell_group, "temperature", temps);
196✔
226

227
  // Write density for one or more cell instances
228
  if (type_ == Fill::MATERIAL && material_.size() > 0) {
196✔
229
    vector<double> rho;
168✔
230
    for (int32_t i = 0; i < rho_mult_.size(); ++i)
336✔
231
      rho.push_back(density(i));
168✔
232

233
    write_dataset(cell_group, "density", rho);
168✔
234
  }
168✔
235

236
  close_group(cell_group);
196✔
237
}
196✔
238

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

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

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

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

262
  // Read densities
263
  if (object_exists(cell_group, "density")) {
224✔
264
    vector<double> rho;
168✔
265
    read_dataset(cell_group, "density", rho);
168✔
266

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

275
    // Set densities.
276
    for (int32_t i = 0; i < n_rho; ++i)
336✔
277
      set_density(rho[i], i);
168✔
278
  }
168✔
279

280
  close_group(cell_group);
224✔
281
}
224✔
282

283
void Cell::to_hdf5(hid_t cell_group) const
27,166✔
284
{
285

286
  // Create a group for this cell.
287
  auto group = create_group(cell_group, fmt::format("cell {}", id_));
54,332✔
288

289
  if (!name_.empty()) {
27,166✔
290
    write_string(group, "name", name_, false);
5,061✔
291
  }
292

293
  write_dataset(group, "universe", model::universes[universe_]->id_);
27,166✔
294

295
  to_hdf5_inner(group);
27,166✔
296

297
  // Write fill information.
298
  if (type_ == Fill::MATERIAL) {
27,166✔
299
    write_dataset(group, "fill_type", "material");
22,109✔
300
    std::vector<int32_t> mat_ids;
22,109✔
301
    for (auto i_mat : material_) {
45,876✔
302
      if (i_mat != MATERIAL_VOID) {
23,767✔
303
        mat_ids.push_back(model::materials[i_mat]->id_);
15,677✔
304
      } else {
305
        mat_ids.push_back(MATERIAL_VOID);
8,090✔
306
      }
307
    }
308
    if (mat_ids.size() == 1) {
22,109✔
309
      write_dataset(group, "material", mat_ids[0]);
21,863✔
310
    } else {
311
      write_dataset(group, "material", mat_ids);
246✔
312
    }
313

314
    std::vector<double> temps;
22,109✔
315
    for (auto sqrtkT_val : sqrtkT_)
45,963✔
316
      temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
23,854✔
317
    write_dataset(group, "temperature", temps);
22,109✔
318

319
    write_dataset(group, "density_mult", rho_mult_);
22,109✔
320

321
  } else if (type_ == Fill::UNIVERSE) {
27,166✔
322
    write_dataset(group, "fill_type", "universe");
3,595✔
323
    write_dataset(group, "fill", model::universes[fill_]->id_);
3,595✔
324
    if (translation_ != Position(0, 0, 0)) {
3,595✔
325
      write_dataset(group, "translation", translation_);
1,837✔
326
    }
327
    if (!rotation_.empty()) {
3,595✔
328
      if (rotation_.size() == 12) {
264✔
329
        std::array<double, 3> rot {rotation_[9], rotation_[10], rotation_[11]};
264✔
330
        write_dataset(group, "rotation", rot);
264✔
331
      } else {
UNCOV
332
        write_dataset(group, "rotation", rotation_);
×
333
      }
334
    }
335

336
  } else if (type_ == Fill::LATTICE) {
1,462✔
337
    write_dataset(group, "fill_type", "lattice");
1,462✔
338
    write_dataset(group, "lattice", model::lattices[fill_]->id_);
1,462✔
339
  }
340

341
  close_group(group);
27,166✔
342
}
27,166✔
343

344
//==============================================================================
345
// CSGCell implementation
346
//==============================================================================
347

348
CSGCell::CSGCell(pugi::xml_node cell_node)
33,317✔
349
{
350
  if (check_for_node(cell_node, "id")) {
33,317✔
351
    id_ = std::stoi(get_node_value(cell_node, "id"));
33,317✔
352
  } else {
UNCOV
353
    fatal_error("Must specify id of cell in geometry XML file.");
×
354
  }
355

356
  if (check_for_node(cell_node, "name")) {
33,317✔
357
    name_ = get_node_value(cell_node, "name");
7,080✔
358
  }
359

360
  if (check_for_node(cell_node, "universe")) {
33,317✔
361
    universe_ = std::stoi(get_node_value(cell_node, "universe"));
32,088✔
362
  } else {
363
    universe_ = 0;
1,229✔
364
  }
365

366
  // Make sure that either material or fill was specified, but not both.
367
  bool fill_present = check_for_node(cell_node, "fill");
33,317✔
368
  bool material_present = check_for_node(cell_node, "material");
33,317✔
369
  if (!(fill_present || material_present)) {
33,317✔
370
    fatal_error(
×
UNCOV
371
      fmt::format("Neither material nor fill was specified for cell {}", id_));
×
372
  }
373
  if (fill_present && material_present) {
33,317✔
UNCOV
374
    fatal_error(fmt::format("Cell {} has both a material and a fill specified; "
×
375
                            "only one can be specified per cell",
UNCOV
376
      id_));
×
377
  }
378

379
  if (fill_present) {
33,317✔
380
    fill_ = std::stoi(get_node_value(cell_node, "fill"));
7,003✔
381
    if (fill_ == universe_) {
7,003✔
UNCOV
382
      fatal_error(fmt::format("Cell {} is filled with the same universe that "
×
383
                              "it is contained in.",
UNCOV
384
        id_));
×
385
    }
386
  } else {
387
    fill_ = C_NONE;
26,314✔
388
  }
389

390
  // Read the material element.  There can be zero materials (filled with a
391
  // universe), more than one material (distribmats), and some materials may
392
  // be "void".
393
  if (material_present) {
33,317✔
394
    vector<std::string> mats {
395
      get_node_array<std::string>(cell_node, "material", true)};
26,314✔
396
    if (mats.size() > 0) {
26,314✔
397
      material_.reserve(mats.size());
26,314✔
398
      for (std::string mat : mats) {
54,286✔
399
        if (mat.compare("void") == 0) {
27,972✔
400
          material_.push_back(MATERIAL_VOID);
8,487✔
401
        } else {
402
          material_.push_back(std::stoi(mat));
19,485✔
403
        }
404
      }
27,972✔
405
    } else {
406
      fatal_error(fmt::format(
×
UNCOV
407
        "An empty material element was specified for cell {}", id_));
×
408
    }
409
  }
26,314✔
410

411
  // Read the temperature element which may be distributed like materials.
412
  if (check_for_node(cell_node, "temperature")) {
33,317✔
413
    sqrtkT_ = get_node_array<double>(cell_node, "temperature");
315✔
414
    sqrtkT_.shrink_to_fit();
315✔
415

416
    // Make sure this is a material-filled cell.
417
    if (material_.size() == 0) {
315✔
UNCOV
418
      fatal_error(fmt::format(
×
419
        "Cell {} was specified with a temperature but no material. Temperature"
420
        "specification is only valid for cells filled with a material.",
UNCOV
421
        id_));
×
422
    }
423

424
    // Make sure all temperatures are non-negative.
425
    for (auto T : sqrtkT_) {
678✔
426
      if (T < 0) {
363✔
427
        fatal_error(fmt::format(
×
UNCOV
428
          "Cell {} was specified with a negative temperature", id_));
×
429
      }
430
    }
431

432
    // Convert to sqrt(k*T).
433
    for (auto& T : sqrtkT_) {
678✔
434
      T = std::sqrt(K_BOLTZMANN * T);
363✔
435
    }
436
  }
437

438
  // Read the density element which can be distributed similar to temperature.
439
  // These get assigned to the density multiplier, requiring a division by
440
  // the material density.
441
  // Note: calculating the actual density multiplier is deferred until materials
442
  // are finalized. rho_mult_ contains the true density in the meantime.
443
  if (check_for_node(cell_node, "density")) {
33,317✔
444
    rho_mult_ = get_node_array<double>(cell_node, "density");
16✔
445
    rho_mult_.shrink_to_fit();
16✔
446
    xml_set_density_ = true;
16✔
447

448
    // Make sure this is a material-filled cell.
449
    if (material_.size() == 0) {
16✔
NEW
450
      fatal_error(fmt::format(
×
451
        "Cell {} was specified with a density but no material. Density"
452
        "specification is only valid for cells filled with a material.",
NEW
453
        id_));
×
454
    }
455

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

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

477
  // Read the region specification.
478
  std::string region_spec;
33,317✔
479
  if (check_for_node(cell_node, "region")) {
33,317✔
480
    region_spec = get_node_value(cell_node, "region");
24,507✔
481
  }
482

483
  // Get a tokenized representation of the region specification and apply De
484
  // Morgans law
485
  Region region(region_spec, id_);
33,317✔
486
  region_ = region;
33,317✔
487

488
  // Read the translation vector.
489
  if (check_for_node(cell_node, "translation")) {
33,317✔
490
    if (fill_ == C_NONE) {
2,746✔
491
      fatal_error(fmt::format("Cannot apply a translation to cell {}"
×
492
                              " because it is not filled with another universe",
UNCOV
493
        id_));
×
494
    }
495

496
    auto xyz {get_node_array<double>(cell_node, "translation")};
2,746✔
497
    if (xyz.size() != 3) {
2,746✔
UNCOV
498
      fatal_error(
×
UNCOV
499
        fmt::format("Non-3D translation vector applied to cell {}", id_));
×
500
    }
501
    translation_ = xyz;
2,746✔
502
  }
2,746✔
503

504
  // Read the rotation transform.
505
  if (check_for_node(cell_node, "rotation")) {
33,317✔
506
    auto rot {get_node_array<double>(cell_node, "rotation")};
416✔
507
    set_rotation(rot);
416✔
508
  }
416✔
509
}
33,317✔
510

511
//==============================================================================
512

513
void CSGCell::to_hdf5_inner(hid_t group_id) const
26,761✔
514
{
515
  write_string(group_id, "geom_type", "csg", false);
26,761✔
516
  write_string(group_id, "region", region_.str(), false);
26,761✔
517
}
26,761✔
518

519
//==============================================================================
520

UNCOV
521
vector<int32_t>::iterator CSGCell::find_left_parenthesis(
×
522
  vector<int32_t>::iterator start, const vector<int32_t>& infix)
523
{
524
  // start search at zero
525
  int parenthesis_level = 0;
×
UNCOV
526
  auto it = start;
×
527
  while (it != infix.begin()) {
×
528
    // look at two tokens at a time
UNCOV
529
    int32_t one = *it;
×
UNCOV
530
    int32_t two = *(it - 1);
×
531

532
    // decrement parenthesis level if there are two adjacent surfaces
UNCOV
533
    if (one < OP_UNION && two < OP_UNION) {
×
534
      parenthesis_level--;
×
535
      // increment if there are two adjacent operators
UNCOV
536
    } else if (one >= OP_UNION && two >= OP_UNION) {
×
UNCOV
537
      parenthesis_level++;
×
538
    }
539

540
    // if the level gets to zero, return the position
UNCOV
541
    if (parenthesis_level == 0) {
×
542
      // move the iterator back one before leaving the loop
543
      // so that all tokens in the parenthesis block are included
UNCOV
544
      it--;
×
UNCOV
545
      break;
×
546
    }
547

548
    // continue loop, one token at a time
549
    it--;
×
550
  }
UNCOV
551
  return it;
×
552
}
553

554
//==============================================================================
555
// Region implementation
556
//==============================================================================
557

558
Region::Region(std::string region_spec, int32_t cell_id)
33,317✔
559
{
560
  // Check if region_spec is not empty.
561
  if (!region_spec.empty()) {
33,317✔
562
    // Parse all halfspaces and operators except for intersection (whitespace).
563
    for (int i = 0; i < region_spec.size();) {
147,454✔
564
      if (region_spec[i] == '(') {
122,947✔
565
        expression_.push_back(OP_LEFT_PAREN);
1,957✔
566
        i++;
1,957✔
567

568
      } else if (region_spec[i] == ')') {
120,990✔
569
        expression_.push_back(OP_RIGHT_PAREN);
1,957✔
570
        i++;
1,957✔
571

572
      } else if (region_spec[i] == '|') {
119,033✔
573
        expression_.push_back(OP_UNION);
4,267✔
574
        i++;
4,267✔
575

576
      } else if (region_spec[i] == '~') {
114,766✔
577
        expression_.push_back(OP_COMPLEMENT);
32✔
578
        i++;
32✔
579

580
      } else if (region_spec[i] == '-' || region_spec[i] == '+' ||
193,836✔
581
                 std::isdigit(region_spec[i])) {
79,102✔
582
        // This is the start of a halfspace specification.  Iterate j until we
583
        // find the end, then push-back everything between i and j.
584
        int j = i + 1;
66,692✔
585
        while (j < region_spec.size() && std::isdigit(region_spec[j])) {
135,484✔
586
          j++;
68,792✔
587
        }
588
        expression_.push_back(std::stoi(region_spec.substr(i, j - i)));
66,692✔
589
        i = j;
66,692✔
590

591
      } else if (std::isspace(region_spec[i])) {
48,042✔
592
        i++;
48,042✔
593

594
      } else {
595
        auto err_msg =
596
          fmt::format("Region specification contains invalid character, \"{}\"",
597
            region_spec[i]);
×
UNCOV
598
        fatal_error(err_msg);
×
UNCOV
599
      }
×
600
    }
601

602
    // Add in intersection operators where a missing operator is needed.
603
    int i = 0;
24,507✔
604
    while (i < expression_.size() - 1) {
112,823✔
605
      bool left_compat {
606
        (expression_[i] < OP_UNION) || (expression_[i] == OP_RIGHT_PAREN)};
88,316✔
607
      bool right_compat {(expression_[i + 1] < OP_UNION) ||
88,316✔
608
                         (expression_[i + 1] == OP_LEFT_PAREN) ||
94,604✔
609
                         (expression_[i + 1] == OP_COMPLEMENT)};
6,288✔
610
      if (left_compat && right_compat) {
88,316✔
611
        expression_.insert(expression_.begin() + i + 1, OP_INTERSECTION);
37,918✔
612
      }
613
      i++;
88,316✔
614
    }
615

616
    // Remove complement operators using DeMorgan's laws
617
    auto it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
24,507✔
618
    while (it != expression_.end()) {
24,539✔
619
      // Erase complement
620
      expression_.erase(it);
32✔
621

622
      // Define stop given left parenthesis or not
623
      auto stop = it;
32✔
624
      if (*it == OP_LEFT_PAREN) {
32✔
625
        int depth = 1;
32✔
626
        do {
627
          stop++;
256✔
628
          if (*stop > OP_COMPLEMENT) {
256✔
629
            if (*stop == OP_RIGHT_PAREN) {
32✔
630
              depth--;
32✔
631
            } else {
UNCOV
632
              depth++;
×
633
            }
634
          }
635
        } while (depth > 0);
256✔
636
        it++;
32✔
637
      }
638

639
      // apply DeMorgan's law to any surfaces/operators between these
640
      // positions in the RPN
641
      apply_demorgan(it, stop);
32✔
642
      // update iterator position
643
      it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT);
32✔
644
    }
645

646
    // Convert user IDs to surface indices.
647
    for (auto& r : expression_) {
137,298✔
648
      if (r < OP_UNION) {
112,791✔
649
        const auto& it {model::surface_map.find(abs(r))};
66,692✔
650
        if (it == model::surface_map.end()) {
66,692✔
651
          throw std::runtime_error {
×
UNCOV
652
            "Invalid surface ID " + std::to_string(abs(r)) +
×
UNCOV
653
            " specified in region for cell " + std::to_string(cell_id) + "."};
×
654
        }
655
        r = (r > 0) ? it->second + 1 : -(it->second + 1);
66,692✔
656
      }
657
    }
658

659
    // Check if this is a simple cell.
660
    simple_ = true;
24,507✔
661
    for (int32_t token : expression_) {
120,417✔
662
      if (token == OP_UNION) {
97,165✔
663
        simple_ = false;
1,255✔
664
        // Ensure intersections have precedence over unions
665
        add_precedence();
1,255✔
666
        break;
1,255✔
667
      }
668
    }
669

670
    // If this cell is simple, remove all the superfluous operator tokens.
671
    if (simple_) {
24,507✔
672
      for (auto it = expression_.begin(); it != expression_.end(); it++) {
112,552✔
673
        if (*it == OP_INTERSECTION || *it > OP_COMPLEMENT) {
89,300✔
674
          expression_.erase(it);
33,024✔
675
          it--;
33,024✔
676
        }
677
      }
678
    }
679
    expression_.shrink_to_fit();
24,507✔
680

681
  } else {
682
    simple_ = true;
8,810✔
683
  }
684
}
33,317✔
685

686
//==============================================================================
687

688
void Region::apply_demorgan(
224✔
689
  vector<int32_t>::iterator start, vector<int32_t>::iterator stop)
690
{
691
  do {
692
    if (*start < OP_UNION) {
224✔
693
      *start *= -1;
128✔
694
    } else if (*start == OP_UNION) {
96✔
UNCOV
695
      *start = OP_INTERSECTION;
×
696
    } else if (*start == OP_INTERSECTION) {
96✔
697
      *start = OP_UNION;
96✔
698
    }
699
    start++;
224✔
700
  } while (start < stop);
224✔
701
}
32✔
702

703
//==============================================================================
704
//! Add precedence for infix regions so intersections have higher
705
//! precedence than unions using parentheses.
706
//==============================================================================
707

708
int64_t Region::add_parentheses(int64_t start)
32✔
709
{
710
  int32_t start_token = expression_[start];
32✔
711
  // Add left parenthesis and set new position to be after parenthesis
712
  if (start_token == OP_UNION) {
32✔
UNCOV
713
    start += 2;
×
714
  }
715
  expression_.insert(expression_.begin() + start - 1, OP_LEFT_PAREN);
32✔
716

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

725
  // Add right parenthesis
726
  // While the start iterator is within the bounds of infix
727
  while (start + 1 < expression_.size()) {
224✔
728
    start++;
224✔
729

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

773
//==============================================================================
774

775
void Region::add_precedence()
1,255✔
776
{
777
  int32_t current_op = 0;
1,255✔
778
  std::size_t current_dist = 0;
1,255✔
779

780
  for (int64_t i = 0; i < expression_.size(); i++) {
24,714✔
781
    int32_t token = expression_[i];
23,459✔
782

783
    if (token == OP_UNION || token == OP_INTERSECTION) {
23,459✔
784
      if (current_op == 0) {
9,145✔
785
        // Set the current operator if is hasn't been set
786
        current_op = token;
3,292✔
787
        current_dist = i;
3,292✔
788
      } else if (token != current_op) {
5,853✔
789
        // If the current operator doesn't match the token, add parenthesis to
790
        // assert precedence
791
        if (current_op == OP_INTERSECTION) {
32✔
792
          i = add_parentheses(current_dist);
16✔
793
        } else {
794
          i = add_parentheses(i);
16✔
795
        }
796
        current_op = 0;
32✔
797
        current_dist = 0;
32✔
798
      }
799
    } else if (token > OP_COMPLEMENT) {
14,314✔
800
      // If the token is a parenthesis reset the current operator
801
      current_op = 0;
3,946✔
802
      current_dist = 0;
3,946✔
803
    }
804
  }
805
}
1,255✔
806

807
//==============================================================================
808
//! Convert infix region specification to Reverse Polish Notation (RPN)
809
//!
810
//! This function uses the shunting-yard algorithm.
811
//==============================================================================
812

813
vector<int32_t> Region::generate_postfix(int32_t cell_id) const
124✔
814
{
815
  vector<int32_t> rpn;
124✔
816
  vector<int32_t> stack;
124✔
817

818
  for (int32_t token : expression_) {
2,790✔
819
    if (token < OP_UNION) {
2,666✔
820
      // If token is not an operator, add it to output
821
      rpn.push_back(token);
1,116✔
822
    } else if (token < OP_RIGHT_PAREN) {
1,550✔
823
      // Regular operators union, intersection, complement
824
      while (stack.size() > 0) {
1,581✔
825
        int32_t op = stack.back();
1,302✔
826

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

842
      stack.push_back(token);
992✔
843

844
    } else if (token == OP_LEFT_PAREN) {
558✔
845
      // If the token is a left parenthesis, push it onto the stack
846
      stack.push_back(token);
279✔
847

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

863
      // Pop the left parenthesis.
864
      stack.pop_back();
279✔
865
    }
866
  }
867

868
  while (stack.size() > 0) {
248✔
869
    int32_t op = stack.back();
124✔
870

871
    // If the operator is a parenthesis it is mismatched.
872
    if (op >= OP_RIGHT_PAREN) {
124✔
UNCOV
873
      fatal_error(fmt::format(
×
874
        "Mismatched parentheses in region specification for cell {}", cell_id));
875
    }
876

877
    rpn.push_back(stack.back());
124✔
878
    stack.pop_back();
124✔
879
  }
880

881
  return rpn;
248✔
882
}
124✔
883

884
//==============================================================================
885

886
std::string Region::str() const
26,761✔
887
{
888
  std::stringstream region_spec {};
26,761✔
889
  if (!expression_.empty()) {
26,761✔
890
    for (int32_t token : expression_) {
83,696✔
891
      if (token == OP_LEFT_PAREN) {
64,964✔
892
        region_spec << " (";
1,843✔
893
      } else if (token == OP_RIGHT_PAREN) {
63,121✔
894
        region_spec << " )";
1,843✔
895
      } else if (token == OP_COMPLEMENT) {
61,278✔
UNCOV
896
        region_spec << " ~";
×
897
      } else if (token == OP_INTERSECTION) {
61,278✔
898
      } else if (token == OP_UNION) {
56,888✔
899
        region_spec << " |";
4,053✔
900
      } else {
901
        // Note the off-by-one indexing
902
        auto surf_id = model::surfaces[abs(token) - 1]->id_;
52,835✔
903
        region_spec << " " << ((token > 0) ? surf_id : -surf_id);
52,835✔
904
      }
905
    }
906
  }
907
  return region_spec.str();
53,522✔
908
}
26,761✔
909

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

912
std::pair<double, int32_t> Region::distance(
2,147,483,647✔
913
  Position r, Direction u, int32_t on_surface) const
914
{
915
  double min_dist {INFTY};
2,147,483,647✔
916
  int32_t i_surf {std::numeric_limits<int32_t>::max()};
2,147,483,647✔
917

918
  for (int32_t token : expression_) {
2,147,483,647✔
919
    // Ignore this token if it corresponds to an operator rather than a region.
920
    if (token >= OP_UNION)
2,147,483,647✔
921
      continue;
169,611,236✔
922

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

928
    // Check if this distance is the new minimum.
929
    if (d < min_dist) {
2,147,483,647✔
930
      if (min_dist - d >= FP_PRECISION * min_dist) {
2,147,483,647✔
931
        min_dist = d;
2,147,483,647✔
932
        i_surf = -token;
2,147,483,647✔
933
      }
934
    }
935
  }
936

937
  return {min_dist, i_surf};
2,147,483,647✔
938
}
939

940
//==============================================================================
941

942
bool Region::contains(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
943
{
944
  if (simple_) {
2,147,483,647✔
945
    return contains_simple(r, u, on_surface);
2,147,483,647✔
946
  } else {
947
    return contains_complex(r, u, on_surface);
6,769,327✔
948
  }
949
}
950

951
//==============================================================================
952

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

974
//==============================================================================
975

976
bool Region::contains_complex(Position r, Direction u, int32_t on_surface) const
6,769,327✔
977
{
978
  bool in_cell = true;
6,769,327✔
979
  int total_depth = 0;
6,769,327✔
980

981
  // For each token
982
  for (auto it = expression_.begin(); it != expression_.end(); it++) {
106,627,372✔
983
    int32_t token = *it;
101,781,743✔
984

985
    // If the token is a surface evaluate the sense
986
    // If the token is a union or intersection check to
987
    // short circuit
988
    if (token < OP_UNION) {
101,781,743✔
989
      if (token == on_surface) {
44,506,267✔
990
        in_cell = true;
3,306,883✔
991
      } else if (-token == on_surface) {
41,199,384✔
992
        in_cell = false;
670,194✔
993
      } else {
994
        // Note the off-by-one indexing
995
        bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
40,529,190✔
996
        in_cell = (sense == (token > 0));
40,529,190✔
997
      }
998
    } else if ((token == OP_UNION && in_cell == true) ||
57,275,476✔
999
               (token == OP_INTERSECTION && in_cell == false)) {
27,909,154✔
1000
      // If the total depth is zero return
1001
      if (total_depth == 0) {
6,296,332✔
1002
        return in_cell;
1,923,698✔
1003
      }
1004

1005
      total_depth--;
4,372,634✔
1006

1007
      // While the iterator is within the bounds of the vector
1008
      int depth = 1;
4,372,634✔
1009
      do {
1010
        // Get next token
1011
        it++;
27,631,976✔
1012
        int32_t next_token = *it;
27,631,976✔
1013

1014
        // If the token is an a parenthesis
1015
        if (next_token > OP_COMPLEMENT) {
27,631,976✔
1016
          // Adjust depth accordingly
1017
          if (next_token == OP_RIGHT_PAREN) {
4,559,078✔
1018
            depth--;
4,465,856✔
1019
          } else {
1020
            depth++;
93,222✔
1021
          }
1022
        }
1023
      } while (depth > 0);
27,631,976✔
1024
    } else if (token == OP_LEFT_PAREN) {
55,351,778✔
1025
      total_depth++;
8,807,419✔
1026
    } else if (token == OP_RIGHT_PAREN) {
42,171,725✔
1027
      total_depth--;
4,434,785✔
1028
    }
1029
  }
1030
  return in_cell;
4,845,629✔
1031
}
1032

1033
//==============================================================================
1034

1035
BoundingBox Region::bounding_box(int32_t cell_id) const
197✔
1036
{
1037
  if (simple_) {
197✔
1038
    return bounding_box_simple();
73✔
1039
  } else {
1040
    auto postfix = generate_postfix(cell_id);
124✔
1041
    return bounding_box_complex(postfix);
124✔
1042
  }
124✔
1043
}
1044

1045
//==============================================================================
1046

1047
BoundingBox Region::bounding_box_simple() const
73✔
1048
{
1049
  BoundingBox bbox;
73✔
1050
  for (int32_t token : expression_) {
309✔
1051
    bbox &= model::surfaces[abs(token) - 1]->bounding_box(token > 0);
236✔
1052
  }
1053
  return bbox;
73✔
1054
}
1055

1056
//==============================================================================
1057

1058
BoundingBox Region::bounding_box_complex(vector<int32_t> postfix) const
124✔
1059
{
1060
  vector<BoundingBox> stack(postfix.size());
124✔
1061
  int i_stack = -1;
124✔
1062

1063
  for (auto& token : postfix) {
2,232✔
1064
    if (token == OP_UNION) {
2,108✔
1065
      stack[i_stack - 1] = stack[i_stack - 1] | stack[i_stack];
434✔
1066
      i_stack--;
434✔
1067
    } else if (token == OP_INTERSECTION) {
1,674✔
1068
      stack[i_stack - 1] = stack[i_stack - 1] & stack[i_stack];
558✔
1069
      i_stack--;
558✔
1070
    } else {
1071
      i_stack++;
1,116✔
1072
      stack[i_stack] = model::surfaces[abs(token) - 1]->bounding_box(token > 0);
1,116✔
1073
    }
1074
  }
1075

1076
  assert(i_stack == 0);
104✔
1077
  return stack.front();
248✔
1078
}
124✔
1079

1080
//==============================================================================
1081

1082
vector<int32_t> Region::surfaces() const
5,389✔
1083
{
1084
  if (simple_) {
5,389✔
1085
    return expression_;
5,389✔
1086
  }
1087

1088
  vector<int32_t> surfaces = expression_;
×
1089

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

UNCOV
1093
  while (it != surfaces.end()) {
×
1094
    surfaces.erase(it);
×
1095

UNCOV
1096
    it = std::find_if(surfaces.begin(), surfaces.end(),
×
UNCOV
1097
      [&](const auto& value) { return value >= OP_UNION; });
×
1098
  }
1099

UNCOV
1100
  return surfaces;
×
1101
}
1102

1103
//==============================================================================
1104
// Non-method functions
1105
//==============================================================================
1106

1107
void read_cells(pugi::xml_node node)
7,516✔
1108
{
1109
  // Count the number of cells.
1110
  int n_cells = 0;
7,516✔
1111
  for (pugi::xml_node cell_node : node.children("cell")) {
40,833✔
1112
    n_cells++;
33,317✔
1113
  }
1114

1115
  // Loop over XML cell elements and populate the array.
1116
  model::cells.reserve(n_cells);
7,516✔
1117
  for (pugi::xml_node cell_node : node.children("cell")) {
40,833✔
1118
    model::cells.push_back(make_unique<CSGCell>(cell_node));
33,317✔
1119
  }
1120

1121
  // Fill the cell map.
1122
  for (int i = 0; i < model::cells.size(); i++) {
40,833✔
1123
    int32_t id = model::cells[i]->id_;
33,317✔
1124
    auto search = model::cell_map.find(id);
33,317✔
1125
    if (search == model::cell_map.end()) {
33,317✔
1126
      model::cell_map[id] = i;
33,317✔
1127
    } else {
UNCOV
1128
      fatal_error(
×
UNCOV
1129
        fmt::format("Two or more cells use the same unique ID: {}", id));
×
1130
    }
1131
  }
1132

1133
  read_dagmc_universes(node);
7,516✔
1134

1135
  populate_universes();
7,514✔
1136

1137
  // Allocate the cell overlap count if necessary.
1138
  if (settings::check_overlaps) {
7,514✔
1139
    model::overlap_check_count.resize(model::cells.size(), 0);
276✔
1140
  }
1141

1142
  if (model::cells.size() == 0) {
7,514✔
UNCOV
1143
    fatal_error("No cells were found in the geometry.xml file");
×
1144
  }
1145
}
7,514✔
1146

1147
void populate_universes()
7,516✔
1148
{
1149
  // Used to map universe index to the index of an implicit complement cell for
1150
  // DAGMC universes
1151
  std::unordered_map<int, int> implicit_comp_cells;
7,516✔
1152

1153
  // Populate the Universe vector and map.
1154
  for (int index_cell = 0; index_cell < model::cells.size(); index_cell++) {
41,306✔
1155
    int32_t uid = model::cells[index_cell]->universe_;
33,790✔
1156
    auto it = model::universe_map.find(uid);
33,790✔
1157
    if (it == model::universe_map.end()) {
33,790✔
1158
      model::universes.push_back(make_unique<Universe>());
18,861✔
1159
      model::universes.back()->id_ = uid;
18,861✔
1160
      model::universes.back()->cells_.push_back(index_cell);
18,861✔
1161
      model::universe_map[uid] = model::universes.size() - 1;
18,861✔
1162
    } else {
1163
#ifdef OPENMC_DAGMC_ENABLED
1164
      // Skip implicit complement cells for now
1165
      Universe* univ = model::universes[it->second].get();
2,169✔
1166
      DAGUniverse* dag_univ = dynamic_cast<DAGUniverse*>(univ);
2,169✔
1167
      if (dag_univ && (dag_univ->implicit_complement_idx() == index_cell)) {
2,169✔
1168
        implicit_comp_cells[it->second] = index_cell;
96✔
1169
        continue;
96✔
1170
      }
1171
#endif
1172

1173
      model::universes[it->second]->cells_.push_back(index_cell);
14,833✔
1174
    }
1175
  }
1176

1177
  // Add DAGUniverse implicit complement cells last
1178
  for (const auto& it : implicit_comp_cells) {
7,612✔
1179
    int index_univ = it.first;
96✔
1180
    int index_cell = it.second;
96✔
1181
    model::universes[index_univ]->cells_.push_back(index_cell);
96✔
1182
  }
1183

1184
  model::universes.shrink_to_fit();
7,516✔
1185
}
7,516✔
1186

1187
//==============================================================================
1188
// C-API functions
1189
//==============================================================================
1190

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

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

1244
extern "C" int openmc_cell_set_temperature(
112✔
1245
  int32_t index, double T, const int32_t* instance, bool set_contained)
1246
{
1247
  if (index < 0 || index >= model::cells.size()) {
112✔
UNCOV
1248
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
UNCOV
1249
    return OPENMC_E_OUT_OF_BOUNDS;
×
1250
  }
1251

1252
  int32_t instance_index = instance ? *instance : -1;
112✔
1253
  try {
1254
    model::cells[index]->set_temperature(T, instance_index, set_contained);
112✔
1255
  } catch (const std::exception& e) {
×
1256
    set_errmsg(e.what());
×
UNCOV
1257
    return OPENMC_E_UNASSIGNED;
×
UNCOV
1258
  }
×
1259
  return 0;
112✔
1260
}
1261

1262
extern "C" int openmc_cell_set_density(
98✔
1263
  int32_t index, double rho, const int32_t* instance, bool set_contained)
1264
{
1265
  if (index < 0 || index >= model::cells.size()) {
98✔
NEW
1266
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
NEW
1267
    return OPENMC_E_OUT_OF_BOUNDS;
×
1268
  }
1269

1270
  int32_t instance_index = instance ? *instance : -1;
98✔
1271
  try {
1272
    model::cells[index]->set_density(rho, instance_index, set_contained);
98✔
NEW
1273
  } catch (const std::exception& e) {
×
NEW
1274
    set_errmsg(e.what());
×
NEW
1275
    return OPENMC_E_UNASSIGNED;
×
NEW
1276
  }
×
1277
  return 0;
98✔
1278
}
1279

1280
extern "C" int openmc_cell_get_temperature(
127✔
1281
  int32_t index, const int32_t* instance, double* T)
1282
{
1283
  if (index < 0 || index >= model::cells.size()) {
127✔
UNCOV
1284
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
UNCOV
1285
    return OPENMC_E_OUT_OF_BOUNDS;
×
1286
  }
1287

1288
  int32_t instance_index = instance ? *instance : -1;
127✔
1289
  try {
1290
    *T = model::cells[index]->temperature(instance_index);
127✔
1291
  } catch (const std::exception& e) {
×
1292
    set_errmsg(e.what());
×
UNCOV
1293
    return OPENMC_E_UNASSIGNED;
×
UNCOV
1294
  }
×
1295
  return 0;
127✔
1296
}
1297

1298
extern "C" int openmc_cell_get_density(
84✔
1299
  int32_t index, const int32_t* instance, double* rho)
1300
{
1301
  if (index < 0 || index >= model::cells.size()) {
84✔
NEW
1302
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
NEW
1303
    return OPENMC_E_OUT_OF_BOUNDS;
×
1304
  }
1305

1306
  int32_t instance_index = instance ? *instance : -1;
84✔
1307
  try {
1308
    if (model::cells[index]->type_ != Fill::MATERIAL) {
84✔
NEW
1309
      fatal_error(
×
NEW
1310
        fmt::format("Cell {}, instance {} is not filled with a material.",
×
NEW
1311
          model::cells[index]->id_, instance_index));
×
1312
    }
1313

1314
    int32_t mat_index = model::cells[index]->material(instance_index);
84✔
1315
    if (mat_index == MATERIAL_VOID) {
84✔
NEW
1316
      *rho = 0.0;
×
1317
    } else {
1318
      *rho = model::cells[index]->density_mult(instance_index) *
168✔
1319
             model::materials[mat_index]->density_gpcc();
84✔
1320
    }
NEW
1321
  } catch (const std::exception& e) {
×
NEW
1322
    set_errmsg(e.what());
×
NEW
1323
    return OPENMC_E_UNASSIGNED;
×
NEW
1324
  }
×
1325
  return 0;
84✔
1326
}
1327

1328
//! Get the bounding box of a cell
1329
extern "C" int openmc_cell_bounding_box(
155✔
1330
  const int32_t index, double* llc, double* urc)
1331
{
1332

1333
  BoundingBox bbox;
155✔
1334

1335
  const auto& c = model::cells[index];
155✔
1336
  bbox = c->bounding_box();
155✔
1337

1338
  // set lower left corner values
1339
  llc[0] = bbox.xmin;
155✔
1340
  llc[1] = bbox.ymin;
155✔
1341
  llc[2] = bbox.zmin;
155✔
1342

1343
  // set upper right corner values
1344
  urc[0] = bbox.xmax;
155✔
1345
  urc[1] = bbox.ymax;
155✔
1346
  urc[2] = bbox.zmax;
155✔
1347

1348
  return 0;
155✔
1349
}
1350

1351
//! Get the name of a cell
1352
extern "C" int openmc_cell_get_name(int32_t index, const char** name)
28✔
1353
{
1354
  if (index < 0 || index >= model::cells.size()) {
28✔
UNCOV
1355
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1356
    return OPENMC_E_OUT_OF_BOUNDS;
×
1357
  }
1358

1359
  *name = model::cells[index]->name().data();
28✔
1360

1361
  return 0;
28✔
1362
}
1363

1364
//! Set the name of a cell
1365
extern "C" int openmc_cell_set_name(int32_t index, const char* name)
14✔
1366
{
1367
  if (index < 0 || index >= model::cells.size()) {
14✔
UNCOV
1368
    set_errmsg("Index in cells array is out of bounds.");
×
UNCOV
1369
    return OPENMC_E_OUT_OF_BOUNDS;
×
1370
  }
1371

1372
  model::cells[index]->set_name(name);
14✔
1373

1374
  return 0;
14✔
1375
}
1376

1377
//==============================================================================
1378
//! Define a containing (parent) cell
1379
//==============================================================================
1380

1381
//! Used to locate a universe fill in the geometry
1382
struct ParentCell {
1383
  bool operator==(const ParentCell& other) const
144✔
1384
  {
1385
    return cell_index == other.cell_index &&
288✔
1386
           lattice_index == other.lattice_index;
288✔
1387
  }
1388

1389
  bool operator<(const ParentCell& other) const
1390
  {
1391
    return cell_index < other.cell_index ||
1392
           (cell_index == other.cell_index &&
1393
             lattice_index < other.lattice_index);
1394
  }
1395

1396
  int64_t cell_index;
1397
  int64_t lattice_index;
1398
};
1399

1400
//! Structure used to insert ParentCell into hashed STL data structures
1401
struct ParentCellHash {
1402
  std::size_t operator()(const ParentCell& p) const
673✔
1403
  {
1404
    return 4096 * p.cell_index + p.lattice_index;
673✔
1405
  }
1406
};
1407

1408
//! Used to manage a traversal stack when locating parent cells of a cell
1409
//! instance in the model
1410
struct ParentCellStack {
1411

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

1422
  //! removes the last parent_cell and clears the visited cells for the popped
1423
  //! cell's universe
1424
  void pop()
80✔
1425
  {
1426
    visited_cells_[this->current_univ()].clear();
80✔
1427
    parent_cells_.pop_back();
80✔
1428
  }
80✔
1429

1430
  //! checks whether or not the parent cell has been visited already for this
1431
  //! search universe
1432
  bool visited(int32_t search_universe, const ParentCell& parent_cell)
561✔
1433
  {
1434
    return visited_cells_[search_universe].count(parent_cell) != 0;
561✔
1435
  }
1436

1437
  //! return the next universe to search for a parent cell
1438
  int32_t current_univ() const
80✔
1439
  {
1440
    return model::cells[parent_cells_.back().cell_index]->universe_;
80✔
1441
  }
1442

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

1446
  //! compute an instance for the provided distribcell index
1447
  int32_t compute_instance(int32_t distribcell_index) const
193✔
1448
  {
1449
    if (distribcell_index == C_NONE)
193✔
1450
      return 0;
65✔
1451

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

1466
  // Accessors
1467
  vector<ParentCell>& parent_cells() { return parent_cells_; }
339✔
1468
  const vector<ParentCell>& parent_cells() const { return parent_cells_; }
1469

1470
  // Data Members
1471
  vector<ParentCell> parent_cells_;
1472
  std::unordered_map<int32_t, std::unordered_set<ParentCell, ParentCellHash>>
1473
    visited_cells_;
1474
};
1475

UNCOV
1476
vector<ParentCell> Cell::find_parent_cells(
×
1477
  int32_t instance, const Position& r) const
1478
{
1479

1480
  // create a temporary particle
1481
  GeometryState dummy_particle {};
×
UNCOV
1482
  dummy_particle.r() = r;
×
1483
  dummy_particle.u() = {0., 0., 1.};
×
1484

UNCOV
1485
  return find_parent_cells(instance, dummy_particle);
×
1486
}
1487

UNCOV
1488
vector<ParentCell> Cell::find_parent_cells(
×
1489
  int32_t instance, GeometryState& p) const
1490
{
1491
  // look up the particle's location
UNCOV
1492
  exhaustive_find_cell(p);
×
UNCOV
1493
  const auto& coords = p.coord();
×
1494

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

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

1519
  // if this loop finished because the cell was found and
1520
  // the instance matches the one requested in the call
1521
  // we have the correct path and can return the stack
1522
  if (cell_found &&
×
UNCOV
1523
      stack.compute_instance(this->distribcell_index_) == instance) {
×
UNCOV
1524
    return stack.parent_cells();
×
1525
  }
1526

1527
  // fall back on an exhaustive search for the cell's parents
UNCOV
1528
  return exhaustive_find_parent_cells(instance);
×
1529
}
1530

1531
vector<ParentCell> Cell::exhaustive_find_parent_cells(int32_t instance) const
113✔
1532
{
1533
  ParentCellStack stack;
113✔
1534
  // start with this cell's universe
1535
  int32_t prev_univ_idx;
1536
  int32_t univ_idx = this->universe_;
113✔
1537

1538
  while (true) {
1539
    const auto& univ = model::universes[univ_idx];
193✔
1540
    prev_univ_idx = univ_idx;
193✔
1541

1542
    // search for a cell that is filled w/ this universe
1543
    for (const auto& cell : model::cells) {
1,236✔
1544
      // if this is a material-filled cell, move on
1545
      if (cell->type_ == Fill::MATERIAL)
1,155✔
1546
        continue;
642✔
1547

1548
      if (cell->type_ == Fill::UNIVERSE) {
513✔
1549
        // if this is in the set of cells previously visited for this universe,
1550
        // move on
1551
        if (stack.visited(univ_idx, {model::cell_map[cell->id_], C_NONE}))
305✔
UNCOV
1552
          continue;
×
1553

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

1565
        // start search for universe
1566
        auto lat_it = lattice_univs.begin();
208✔
1567
        while (true) {
1568
          // find the next lattice cell with this universe
1569
          lat_it = std::find(lat_it, lattice_univs.end(), univ_idx);
352✔
1570
          if (lat_it == lattice_univs.end())
352✔
1571
            break;
96✔
1572

1573
          int lattice_idx = lat_it - lattice_univs.begin();
256✔
1574

1575
          // move iterator forward one to avoid finding the same entry
1576
          lat_it++;
256✔
1577
          if (stack.visited(
256✔
1578
                univ_idx, {model::cell_map[cell->id_], lattice_idx}))
256✔
1579
            continue;
144✔
1580

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

1592
    // if we're at the top of the geometry and the instance matches, we're done
1593
    if (univ_idx == model::root_universe &&
386✔
1594
        stack.compute_instance(this->distribcell_index_) == instance)
193✔
1595
      break;
113✔
1596

1597
    // if there is no match on the original cell's universe, report an error
1598
    if (univ_idx == this->universe_) {
80✔
1599
      fatal_error(
×
UNCOV
1600
        fmt::format("Could not find the parent cells for cell {}, instance {}.",
×
UNCOV
1601
          this->id_, instance));
×
1602
    }
1603

1604
    // if we don't find a suitable update, adjust the stack and continue
1605
    if (univ_idx == model::root_universe || univ_idx == prev_univ_idx) {
80✔
1606
      stack.pop();
80✔
1607
      univ_idx = stack.empty() ? this->universe_ : stack.current_univ();
80✔
1608
    }
1609

1610
  } // end while
80✔
1611

1612
  // reverse the stack so the highest cell comes first
1613
  std::reverse(stack.parent_cells().begin(), stack.parent_cells().end());
113✔
1614
  return stack.parent_cells();
226✔
1615
}
113✔
1616

1617
std::unordered_map<int32_t, vector<int32_t>> Cell::get_contained_cells(
161✔
1618
  int32_t instance, Position* hint) const
1619
{
1620
  std::unordered_map<int32_t, vector<int32_t>> contained_cells;
161✔
1621

1622
  // if this is a material-filled cell it has no contained cells
1623
  if (this->type_ == Fill::MATERIAL)
161✔
1624
    return contained_cells;
48✔
1625

1626
  // find the pathway through the geometry to this cell
1627
  vector<ParentCell> parent_cells;
113✔
1628

1629
  // if a positional hint is provided, attempt to do a fast lookup
1630
  // of the parent cells
1631
  parent_cells = hint ? find_parent_cells(instance, *hint)
226✔
1632
                      : exhaustive_find_parent_cells(instance);
113✔
1633

1634
  // if this cell is filled w/ a material, it contains no other cells
1635
  if (type_ != Fill::MATERIAL) {
113✔
1636
    this->get_contained_cells_inner(contained_cells, parent_cells);
113✔
1637
  }
1638

1639
  return contained_cells;
113✔
1640
}
113✔
1641

1642
//! Get all cells within this cell
1643
void Cell::get_contained_cells_inner(
87,763✔
1644
  std::unordered_map<int32_t, vector<int32_t>>& contained_cells,
1645
  vector<ParentCell>& parent_cells) const
1646
{
1647

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

1691
//! Return the index in the cells array of a cell with a given ID
1692
extern "C" int openmc_get_cell_index(int32_t id, int32_t* index)
887✔
1693
{
1694
  auto it = model::cell_map.find(id);
887✔
1695
  if (it != model::cell_map.end()) {
887✔
1696
    *index = it->second;
873✔
1697
    return 0;
873✔
1698
  } else {
1699
    set_errmsg("No cell exists with ID=" + std::to_string(id) + ".");
14✔
1700
    return OPENMC_E_INVALID_ID;
14✔
1701
  }
1702
}
1703

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

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

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

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

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

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

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

1808
//! Extend the cells array by n elements
1809
extern "C" int openmc_extend_cells(
28✔
1810
  int32_t n, int32_t* index_start, int32_t* index_end)
1811
{
1812
  if (index_start)
28✔
1813
    *index_start = model::cells.size();
28✔
1814
  if (index_end)
28✔
UNCOV
1815
    *index_end = model::cells.size() + n - 1;
×
1816
  for (int32_t i = 0; i < n; i++) {
56✔
1817
    model::cells.push_back(make_unique<CSGCell>());
28✔
1818
  }
1819
  return 0;
28✔
1820
}
1821

1822
extern "C" int cells_size()
56✔
1823
{
1824
  return model::cells.size();
56✔
1825
}
1826

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