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

openmc-dev / openmc / 17637711185

11 Sep 2025 07:44AM UTC coverage: 85.193% (-0.02%) from 85.209%
17637711185

Pull #3566

github

web-flow
Merge 03f6b5b26 into ca4295748
Pull Request #3566: Resolve merge conflicts for PR #3516 - Implement Virtual Lattice Method for Efficient Simulation of Dispersed Fuels

268 of 326 new or added lines in 9 files covered. (82.21%)

175 existing lines in 3 files now uncovered.

53205 of 62452 relevant lines covered (85.19%)

37768560.15 hits per line

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

81.7
/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
vector<vector<int32_t>> generate_triso_distribution(vector<int> lattice_shape,
16✔
40
  vector<double> lattice_pitch, vector<double> lattice_lower_left,
41
  vector<std::int32_t> cell_rpn, int id)
42
{
43
  vector<vector<int32_t>> triso_distribution(
44
    lattice_shape[0] * lattice_shape[1] * lattice_shape[2]);
16✔
45
  vector<double> mesh_center(3);
16✔
46
  vector<int> mesh_ind(3);
16✔
47

48
  for (int32_t token : cell_rpn) {
1,616✔
49
    if (token >= OP_UNION)
1,600✔
NEW
50
      continue;
×
51
    vector<double> triso_center = model::surfaces[abs(token) - 1]->get_center();
1,600✔
52
    for (int i = 0; i < 3; i++) {
6,400✔
53
      mesh_ind[i] =
4,800✔
54
        floor((triso_center[i] - lattice_lower_left[i]) / lattice_pitch[i]);
4,800✔
55
    }
56
    for (int i = mesh_ind[0] - 1; i <= mesh_ind[0] + 1; i++) {
6,400✔
57
      for (int j = mesh_ind[1] - 1; j <= mesh_ind[1] + 1; j++) {
19,200✔
58
        for (int k = mesh_ind[2] - 1; k <= mesh_ind[2] + 1; k++) {
57,600✔
59
          if (i < 0 || i >= lattice_shape[0] || j < 0 ||
38,160✔
60
              j >= lattice_shape[1] || k < 0 || k >= lattice_shape[2])
81,360✔
61
            continue;
22,784✔
62
          mesh_center[0] = (i + 0.5) * lattice_pitch[0] + lattice_lower_left[0];
20,416✔
63
          mesh_center[1] = (j + 0.5) * lattice_pitch[1] + lattice_lower_left[1];
20,416✔
64
          mesh_center[2] = (k + 0.5) * lattice_pitch[2] + lattice_lower_left[2];
20,416✔
65
          if (model::surfaces[abs(token) - 1]->triso_in_mesh(
20,416✔
66
                mesh_center, lattice_pitch)) {
67
            triso_distribution[i + j * lattice_shape[0] +
2,576✔
68
                               k * lattice_shape[0] * lattice_shape[1]]
2,576✔
69
              .push_back(token);
2,576✔
70
            model::surfaces[abs(token) - 1]->connect_to_triso_base(id, "base");
2,576✔
71
          }
72
        }
73
      }
74
    }
75
  }
1,600✔
76

77
  return triso_distribution;
32✔
78
}
16✔
79

80
//==============================================================================
81
// Cell implementation
82
//==============================================================================
83

84
int32_t Cell::n_instances() const
772,720✔
85
{
86
  return model::universes[universe_]->n_instances_;
772,720✔
87
}
88

89
void Cell::set_rotation(const vector<double>& rot)
450✔
90
{
91
  if (fill_ == C_NONE) {
450✔
92
    fatal_error(fmt::format("Cannot apply a rotation to cell {}"
×
93
                            " because it is not filled with another universe",
94
      id_));
×
95
  }
96

97
  if (rot.size() != 3 && rot.size() != 9) {
450✔
98
    fatal_error(fmt::format("Non-3D rotation vector applied to cell {}", id_));
×
99
  }
100

101
  // Compute and store the rotation matrix.
102
  rotation_.clear();
450✔
103
  rotation_.reserve(rot.size() == 9 ? 9 : 12);
450✔
104
  if (rot.size() == 3) {
450✔
105
    double phi = -rot[0] * PI / 180.0;
450✔
106
    double theta = -rot[1] * PI / 180.0;
450✔
107
    double psi = -rot[2] * PI / 180.0;
450✔
108
    rotation_.push_back(std::cos(theta) * std::cos(psi));
450✔
109
    rotation_.push_back(-std::cos(phi) * std::sin(psi) +
×
110
                        std::sin(phi) * std::sin(theta) * std::cos(psi));
450✔
111
    rotation_.push_back(std::sin(phi) * std::sin(psi) +
×
112
                        std::cos(phi) * std::sin(theta) * std::cos(psi));
450✔
113
    rotation_.push_back(std::cos(theta) * std::sin(psi));
450✔
114
    rotation_.push_back(std::cos(phi) * std::cos(psi) +
×
115
                        std::sin(phi) * std::sin(theta) * std::sin(psi));
450✔
116
    rotation_.push_back(-std::sin(phi) * std::cos(psi) +
×
117
                        std::cos(phi) * std::sin(theta) * std::sin(psi));
450✔
118
    rotation_.push_back(-std::sin(theta));
450✔
119
    rotation_.push_back(std::sin(phi) * std::cos(theta));
450✔
120
    rotation_.push_back(std::cos(phi) * std::cos(theta));
450✔
121

122
    // When user specifies angles, write them at end of vector
123
    rotation_.push_back(rot[0]);
450✔
124
    rotation_.push_back(rot[1]);
450✔
125
    rotation_.push_back(rot[2]);
450✔
126
  } else {
127
    std::copy(rot.begin(), rot.end(), std::back_inserter(rotation_));
×
128
  }
129
}
450✔
130

131
double Cell::temperature(int32_t instance) const
114✔
132
{
133
  if (sqrtkT_.size() < 1) {
114✔
134
    throw std::runtime_error {"Cell temperature has not yet been set."};
×
135
  }
136

137
  if (instance >= 0) {
114✔
138
    double sqrtkT = sqrtkT_.size() == 1 ? sqrtkT_.at(0) : sqrtkT_.at(instance);
12✔
139
    return sqrtkT * sqrtkT / K_BOLTZMANN;
12✔
140
  } else {
141
    return sqrtkT_[0] * sqrtkT_[0] / K_BOLTZMANN;
102✔
142
  }
143
}
144

145
void Cell::set_temperature(double T, int32_t instance, bool set_contained)
471✔
146
{
147
  if (settings::temperature_method == TemperatureMethod::INTERPOLATION) {
471✔
148
    if (T < (data::temperature_min - settings::temperature_tolerance)) {
×
149
      throw std::runtime_error {
×
150
        fmt::format("Temperature of {} K is below minimum temperature at "
×
151
                    "which data is available of {} K.",
152
          T, data::temperature_min)};
×
153
    } else if (T > (data::temperature_max + settings::temperature_tolerance)) {
×
154
      throw std::runtime_error {
×
155
        fmt::format("Temperature of {} K is above maximum temperature at "
×
156
                    "which data is available of {} K.",
157
          T, data::temperature_max)};
×
158
    }
159
  }
160

161
  if (type_ == Fill::MATERIAL) {
471✔
162
    if (instance >= 0) {
439✔
163
      // If temperature vector is not big enough, resize it first
164
      if (sqrtkT_.size() != n_instances())
360✔
165
        sqrtkT_.resize(n_instances(), sqrtkT_[0]);
48✔
166

167
      // Set temperature for the corresponding instance
168
      sqrtkT_.at(instance) = std::sqrt(K_BOLTZMANN * T);
360✔
169
    } else {
170
      // Set temperature for all instances
171
      for (auto& T_ : sqrtkT_) {
158✔
172
        T_ = std::sqrt(K_BOLTZMANN * T);
79✔
173
      }
174
    }
175
  } else {
176
    if (!set_contained) {
32✔
177
      throw std::runtime_error {
×
178
        fmt::format("Attempted to set the temperature of cell {} "
×
179
                    "which is not filled by a material.",
180
          id_)};
×
181
    }
182

183
    auto contained_cells = this->get_contained_cells(instance);
32✔
184
    for (const auto& entry : contained_cells) {
128✔
185
      auto& cell = model::cells[entry.first];
96✔
186
      assert(cell->type_ == Fill::MATERIAL);
78✔
187
      auto& instances = entry.second;
96✔
188
      for (auto instance : instances) {
336✔
189
        cell->set_temperature(T, instance);
240✔
190
      }
191
    }
192
  }
32✔
193
}
471✔
194

195
void Cell::export_properties_hdf5(hid_t group) const
129✔
196
{
197
  // Create a group for this cell.
198
  auto cell_group = create_group(group, fmt::format("cell {}", id_));
258✔
199

200
  // Write temperature in [K] for one or more cell instances
201
  vector<double> temps;
129✔
202
  for (auto sqrtkT_val : sqrtkT_)
234✔
203
    temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
105✔
204
  write_dataset(cell_group, "temperature", temps);
129✔
205

206
  close_group(cell_group);
129✔
207
}
129✔
208

209
void Cell::import_properties_hdf5(hid_t group)
156✔
210
{
211
  auto cell_group = open_group(group, fmt::format("cell {}", id_));
312✔
212

213
  // Read temperatures from file
214
  vector<double> temps;
156✔
215
  read_dataset(cell_group, "temperature", temps);
156✔
216

217
  // Ensure number of temperatures makes sense
218
  auto n_temps = temps.size();
156✔
219
  if (n_temps > 1 && n_temps != n_instances()) {
156✔
220
    throw std::runtime_error(fmt::format(
×
221
      "Number of temperatures for cell {} doesn't match number of instances",
222
      id_));
×
223
  }
224

225
  // Modify temperatures for the cell
226
  sqrtkT_.clear();
156✔
227
  sqrtkT_.resize(temps.size());
156✔
228
  for (int64_t i = 0; i < temps.size(); ++i) {
264✔
229
    this->set_temperature(temps[i], i);
108✔
230
  }
231

232
  close_group(cell_group);
156✔
233
}
156✔
234

235
void Cell::to_hdf5(hid_t cell_group) const
27,669✔
236
{
237

238
  // Create a group for this cell.
239
  auto group = create_group(cell_group, fmt::format("cell {}", id_));
55,338✔
240

241
  if (!name_.empty()) {
27,669✔
242
    write_string(group, "name", name_, false);
4,887✔
243
  }
244

245
  write_dataset(group, "universe", model::universes[universe_]->id_);
27,669✔
246

247
  to_hdf5_inner(group);
27,669✔
248

249
  // Write fill information.
250
  if (type_ == Fill::MATERIAL) {
27,669✔
251
    write_dataset(group, "fill_type", "material");
21,562✔
252
    std::vector<int32_t> mat_ids;
21,562✔
253
    for (auto i_mat : material_) {
44,709✔
254
      if (i_mat != MATERIAL_VOID) {
23,147✔
255
        mat_ids.push_back(model::materials[i_mat]->id_);
15,102✔
256
      } else {
257
        mat_ids.push_back(MATERIAL_VOID);
8,045✔
258
      }
259
    }
260
    if (mat_ids.size() == 1) {
21,562✔
261
      write_dataset(group, "material", mat_ids[0]);
21,327✔
262
    } else {
263
      write_dataset(group, "material", mat_ids);
235✔
264
    }
265

266
    std::vector<double> temps;
21,562✔
267
    for (auto sqrtkT_val : sqrtkT_)
44,796✔
268
      temps.push_back(sqrtkT_val * sqrtkT_val / K_BOLTZMANN);
23,234✔
269
    write_dataset(group, "temperature", temps);
21,562✔
270

271
  } else if (type_ == Fill::UNIVERSE) {
27,669✔
272
    write_dataset(group, "fill_type", "universe");
4,661✔
273
    write_dataset(group, "fill", model::universes[fill_]->id_);
4,661✔
274
    if (translation_ != Position(0, 0, 0)) {
4,661✔
275
      write_dataset(group, "translation", translation_);
2,937✔
276
    }
277
    if (!rotation_.empty()) {
4,661✔
278
      if (rotation_.size() == 12) {
264✔
279
        std::array<double, 3> rot {rotation_[9], rotation_[10], rotation_[11]};
264✔
280
        write_dataset(group, "rotation", rot);
264✔
281
      } else {
282
        write_dataset(group, "rotation", rotation_);
×
283
      }
284
    }
285

286
  } else if (type_ == Fill::LATTICE) {
1,446✔
287
    write_dataset(group, "fill_type", "lattice");
1,446✔
288
    write_dataset(group, "lattice", model::lattices[fill_]->id_);
1,446✔
289
  }
290

291
  close_group(group);
27,669✔
292
}
27,669✔
293

294
//==============================================================================
295
// CSGCell implementation
296
//==============================================================================
297

298
CSGCell::CSGCell(pugi::xml_node cell_node)
34,352✔
299
{
300
  if (check_for_node(cell_node, "id")) {
34,352✔
301
    id_ = std::stoi(get_node_value(cell_node, "id"));
34,352✔
302
  } else {
303
    fatal_error("Must specify id of cell in geometry XML file.");
×
304
  }
305

306
  if (check_for_node(cell_node, "name")) {
34,352✔
307
    name_ = get_node_value(cell_node, "name");
6,906✔
308
  }
309

310
  if (check_for_node(cell_node, "universe")) {
34,352✔
311
    universe_ = std::stoi(get_node_value(cell_node, "universe"));
33,129✔
312
  } else {
313
    universe_ = 0;
1,223✔
314
  }
315

316
  // Check if the cell is the base of a virtual triso lattice
317
  bool virtual_lattice_present = check_for_node(cell_node, "virtual_lattice");
34,352✔
318
  if (virtual_lattice_present) {
34,352✔
319
    virtual_lattice_ = get_node_value_bool(cell_node, "virtual_lattice");
16✔
320
    if (virtual_lattice_) {
16✔
321
      if (check_for_node(cell_node, "lower_left") &&
16✔
322
          check_for_node(cell_node, "pitch") &&
32✔
323
          check_for_node(cell_node, "shape")) {
16✔
324
        vl_lower_left_ = get_node_array<double>(cell_node, "lower_left");
16✔
325
        vl_pitch_ = get_node_array<double>(cell_node, "pitch");
16✔
326
        vl_shape_ = get_node_array<int>(cell_node, "shape");
16✔
327
      } else {
NEW
328
        fatal_error(fmt::format("Lower_left, pitch and shape of the virtual "
×
329
                                "lattice must be specified for cell {}",
NEW
330
          id_));
×
331
      }
332
    }
333
  } else {
334
    virtual_lattice_ = false;
34,336✔
335
  }
336

337
  if (check_for_node(cell_node, "triso_particle")) {
34,352✔
338
    triso_particle_ = get_node_value_bool(cell_node, "triso_particle");
1,600✔
339
  } else {
340
    triso_particle_ = false;
32,752✔
341
  }
342

343
  // Make sure that either material or fill was specified, but not both.
344
  bool fill_present = check_for_node(cell_node, "fill");
34,352✔
345
  bool material_present = check_for_node(cell_node, "material");
34,352✔
346
  if (!(fill_present || material_present)) {
34,352✔
347
    fatal_error(
×
348
      fmt::format("Neither material nor fill was specified for cell {}", id_));
×
349
  }
350
  if (fill_present && material_present) {
34,352✔
351
    fatal_error(fmt::format("Cell {} has both a material and a fill specified; "
×
352
                            "only one can be specified per cell",
353
      id_));
×
354
  }
355

356
  if (fill_present) {
34,352✔
357
    fill_ = std::stoi(get_node_value(cell_node, "fill"));
8,552✔
358
    if (fill_ == universe_) {
8,552✔
359
      fatal_error(fmt::format("Cell {} is filled with the same universe that "
×
360
                              "it is contained in.",
361
        id_));
×
362
    }
363
  } else {
364
    fill_ = C_NONE;
25,800✔
365
  }
366

367
  // Read the material element.  There can be zero materials (filled with a
368
  // universe), more than one material (distribmats), and some materials may
369
  // be "void".
370
  if (material_present) {
34,352✔
371
    vector<std::string> mats {
372
      get_node_array<std::string>(cell_node, "material", true)};
25,800✔
373
    if (mats.size() > 0) {
25,800✔
374
      material_.reserve(mats.size());
25,800✔
375
      for (std::string mat : mats) {
53,185✔
376
        if (mat.compare("void") == 0) {
27,385✔
377
          material_.push_back(MATERIAL_VOID);
8,450✔
378
        } else {
379
          material_.push_back(std::stoi(mat));
18,935✔
380
        }
381
      }
27,385✔
382
    } else {
383
      fatal_error(fmt::format(
×
384
        "An empty material element was specified for cell {}", id_));
×
385
    }
386
  }
25,800✔
387

388
  // Read the temperature element which may be distributed like materials.
389
  if (check_for_node(cell_node, "temperature")) {
34,352✔
390
    sqrtkT_ = get_node_array<double>(cell_node, "temperature");
315✔
391
    sqrtkT_.shrink_to_fit();
315✔
392

393
    // Make sure this is a material-filled cell.
394
    if (material_.size() == 0) {
315✔
395
      fatal_error(fmt::format(
×
396
        "Cell {} was specified with a temperature but no material. Temperature"
397
        "specification is only valid for cells filled with a material.",
398
        id_));
×
399
    }
400

401
    // Make sure all temperatures are non-negative.
402
    for (auto T : sqrtkT_) {
678✔
403
      if (T < 0) {
363✔
404
        fatal_error(fmt::format(
×
405
          "Cell {} was specified with a negative temperature", id_));
×
406
      }
407
    }
408

409
    // Convert to sqrt(k*T).
410
    for (auto& T : sqrtkT_) {
678✔
411
      T = std::sqrt(K_BOLTZMANN * T);
363✔
412
    }
413
  }
414

415
  // Read the region specification.
416
  std::string region_spec;
34,352✔
417
  if (check_for_node(cell_node, "region")) {
34,352✔
418
    region_spec = get_node_value(cell_node, "region");
25,552✔
419
  }
420

421
  // Get a tokenized representation of the region specification and apply De
422
  // Morgans law
423
  Region region(region_spec, id_);
34,352✔
424
  region_ = region;
34,352✔
425
  vector<int32_t> rpn = region_.generate_postfix(id_);
34,352✔
426

427
  if (virtual_lattice_) {
34,352✔
428
    vl_triso_distribution_ = generate_triso_distribution(
48✔
429
      vl_shape_, vl_pitch_, vl_lower_left_, rpn, id_);
32✔
430
  }
431

432
  if (triso_particle_) {
34,352✔
433
    if (rpn.size() != 1) {
1,600✔
NEW
434
      fatal_error(
×
NEW
435
        fmt::format("Wrong surface definition of triso particle cell {}", id_));
×
436
    } else {
437
      model::surfaces[abs(rpn[0]) - 1]->connect_to_triso_base(id_, "particle");
1,600✔
438
    }
439
  }
440

441
  // Read the translation vector.
442
  if (check_for_node(cell_node, "translation")) {
34,352✔
443
    if (fill_ == C_NONE) {
4,346✔
444
      fatal_error(fmt::format("Cannot apply a translation to cell {}"
×
445
                              " because it is not filled with another universe",
446
        id_));
×
447
    }
448

449
    auto xyz {get_node_array<double>(cell_node, "translation")};
4,346✔
450
    if (xyz.size() != 3) {
4,346✔
451
      fatal_error(
×
452
        fmt::format("Non-3D translation vector applied to cell {}", id_));
×
453
    }
454
    translation_ = xyz;
4,346✔
455
  }
4,346✔
456

457
  // Read the rotation transform.
458
  if (check_for_node(cell_node, "rotation")) {
34,352✔
459
    auto rot {get_node_array<double>(cell_node, "rotation")};
416✔
460
    set_rotation(rot);
416✔
461
  }
416✔
462
}
34,352✔
463

464
//==============================================================================
465

466
void CSGCell::to_hdf5_inner(hid_t group_id) const
27,264✔
467
{
468
  write_string(group_id, "geom_type", "csg", false);
27,264✔
469
  write_string(group_id, "region", region_.str(), false);
27,264✔
470
}
27,264✔
471

472
//==============================================================================
473

474
vector<int32_t>::iterator CSGCell::find_left_parenthesis(
×
475
  vector<int32_t>::iterator start, const vector<int32_t>& infix)
476
{
477
  // start search at zero
478
  int parenthesis_level = 0;
×
479
  auto it = start;
×
480
  while (it != infix.begin()) {
×
481
    // look at two tokens at a time
482
    int32_t one = *it;
×
483
    int32_t two = *(it - 1);
×
484

485
    // decrement parenthesis level if there are two adjacent surfaces
486
    if (one < OP_UNION && two < OP_UNION) {
×
487
      parenthesis_level--;
×
488
      // increment if there are two adjacent operators
489
    } else if (one >= OP_UNION && two >= OP_UNION) {
×
490
      parenthesis_level++;
×
491
    }
492

493
    // if the level gets to zero, return the position
494
    if (parenthesis_level == 0) {
×
495
      // move the iterator back one before leaving the loop
496
      // so that all tokens in the parenthesis block are included
497
      it--;
×
498
      break;
×
499
    }
500

501
    // continue loop, one token at a time
502
    it--;
×
503
  }
504
  return it;
×
505
}
506
std::pair<double, int32_t> CSGCell::distance(
2,147,483,647✔
507
  Position r, Direction u, int32_t on_surface, GeometryState* p) const
508
{
509
  if (virtual_lattice_) {
2,147,483,647✔
510
    return distance_in_virtual_lattice(r, u, on_surface, p);
4,917,132✔
511
  } else {
512
    return region_.distance(r, u, on_surface);
2,147,483,647✔
513
  }
514
}
515

516
std::pair<double, int32_t> CSGCell::distance_in_virtual_lattice(
4,917,132✔
517
  Position r, Direction u, int32_t on_surface, GeometryState* p) const
518
{
519
  double min_dist {INFTY};
4,917,132✔
520
  int32_t i_surf {std::numeric_limits<int32_t>::max()};
4,917,132✔
521
  double min_dis_vl;
522
  int32_t i_surf_vl;
523

524
  double max_dis = p->collision_distance();
4,917,132✔
525
  double tol_dis = 0;
4,917,132✔
526
  vector<double> dis_to_bou(3), dis_to_bou_max(3);
9,834,264✔
527
  double u_value = sqrt(pow(u.x, 2) + pow(u.y, 2) +
4,917,132✔
528
                        pow(u.z, 2)); // don't know if u has been normalized
4,917,132✔
529
  vector<double> norm_u = {u.x / u_value, u.y / u_value, u.z / u_value};
4,917,132✔
530
  vector<int> lat_ind(3);
4,917,132✔
531
  vector<double> temp_pos = {r.x, r.y, r.z};
4,917,132✔
532
  int loop_time;
533
  for (int i = 0; i < 3; i++) {
19,668,528✔
534
    lat_ind[i] = floor((temp_pos[i] - vl_lower_left_[i]) / vl_pitch_[i]);
14,751,396✔
535
    if (lat_ind[i] == vl_shape_[i] && norm_u[i] < 0) {
14,751,396✔
536
      lat_ind[i] = vl_shape_[i] - 1;
1,590,457✔
537
    }
538
    if (lat_ind[i] == -1 && norm_u[i] > 0) {
14,751,396✔
539
      lat_ind[i] = 0;
10,989✔
540
    }
541
  }
542

543
  dis_to_bou = {INFTY, INFTY, INFTY};
4,917,132✔
544
  for (int i = 0; i < 3; i++) {
19,668,528✔
545
    if (norm_u[i] > 0) {
14,751,396✔
546
      dis_to_bou[i] = std::abs(
7,367,371✔
547
        ((lat_ind[i] + 1) * vl_pitch_[i] + vl_lower_left_[i] - temp_pos[i]) /
7,367,371✔
548
        norm_u[i]);
7,367,371✔
549
      dis_to_bou_max[i] = vl_pitch_[i] / norm_u[i];
7,367,371✔
550
    } else if (norm_u[i] < 0) {
7,384,025✔
551
      dis_to_bou[i] =
7,384,025✔
552
        std::abs((lat_ind[i] * vl_pitch_[i] + vl_lower_left_[i] - temp_pos[i]) /
14,768,050✔
553
                 norm_u[i]);
7,384,025✔
554
      dis_to_bou_max[i] = -vl_pitch_[i] / norm_u[i];
7,384,025✔
555
    }
556
  }
557

558
  while (true) {
559
    if (lat_ind[0] < 0 || lat_ind[0] >= vl_shape_[0] || lat_ind[1] < 0 ||
27,963,617✔
560
        lat_ind[1] >= vl_shape_[1] || lat_ind[2] < 0 ||
39,556,088✔
561
        lat_ind[2] >= vl_shape_[2])
11,592,471✔
562
      break;
3,185,512✔
563

564
    for (int token :
11,062,755✔
565
      vl_triso_distribution_[lat_ind[0] + lat_ind[1] * vl_shape_[0] +
11,062,755✔
566
                             lat_ind[2] * vl_shape_[0] * vl_shape_[1]]) {
99,822,855✔
567
      bool coincident {std::abs(token) == std::abs(on_surface)};
66,634,590✔
568
      double d {model::surfaces[abs(token) - 1]->distance(r, u, coincident)};
66,634,590✔
569
      if (d < min_dist) {
66,634,590✔
570
        if (min_dist - d >= FP_PRECISION * min_dist) {
1,271,281✔
571
          min_dist = d;
1,271,281✔
572
          i_surf = -token;
1,271,281✔
573
        }
574
      }
575
    }
576

577
    int mes_bou_crossed = 0;
11,062,755✔
578
    if (dis_to_bou[1] < dis_to_bou[0]) {
11,062,755✔
579
      mes_bou_crossed = 1;
5,526,081✔
580
    }
581
    if (dis_to_bou[2] < dis_to_bou[mes_bou_crossed]) {
11,062,755✔
582
      mes_bou_crossed = 2;
3,672,878✔
583
    }
584

585
    tol_dis = dis_to_bou[mes_bou_crossed];
11,062,755✔
586

587
    if (min_dist < tol_dis) {
11,062,755✔
588
      break;
1,233,133✔
589
    }
590

591
    if (norm_u[mes_bou_crossed] > 0) {
9,829,622✔
592
      lat_ind[mes_bou_crossed] += 1;
4,914,613✔
593
    } else {
594
      lat_ind[mes_bou_crossed] += -1;
4,915,009✔
595
    }
596

597
    dis_to_bou[mes_bou_crossed] += dis_to_bou_max[mes_bou_crossed];
9,829,622✔
598

599
    if (tol_dis > max_dis) {
9,829,622✔
600
      break;
498,487✔
601
    }
602
  }
9,331,135✔
603
  return {min_dist, i_surf};
9,834,264✔
604
}
4,917,132✔
605

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

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

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

624
      } else if (region_spec[i] == '|') {
121,364✔
625
        expression_.push_back(OP_UNION);
4,225✔
626
        i++;
4,225✔
627

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

632
      } else if (region_spec[i] == '-' || region_spec[i] == '+' ||
197,765✔
633
                 std::isdigit(region_spec[i])) {
80,658✔
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;
68,422✔
637
        while (j < region_spec.size() && std::isdigit(region_spec[j])) {
143,610✔
638
          j++;
75,188✔
639
        }
640
        expression_.push_back(std::stoi(region_spec.substr(i, j - i)));
68,422✔
641
        i = j;
68,422✔
642

643
      } else if (std::isspace(region_spec[i])) {
48,685✔
644
        i++;
48,685✔
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;
25,552✔
656
    while (i < expression_.size() - 1) {
115,232✔
657
      bool left_compat {
658
        (expression_[i] < OP_UNION) || (expression_[i] == OP_RIGHT_PAREN)};
89,680✔
659
      bool right_compat {(expression_[i + 1] < OP_UNION) ||
89,680✔
660
                         (expression_[i + 1] == OP_LEFT_PAREN) ||
95,923✔
661
                         (expression_[i + 1] == OP_COMPLEMENT)};
6,243✔
662
      if (left_compat && right_compat) {
89,680✔
663
        expression_.insert(expression_.begin() + i + 1, OP_INTERSECTION);
38,645✔
664
      }
665
      i++;
89,680✔
666
    }
667

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

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

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

698
    // Convert user IDs to surface indices.
699
    for (auto& r : expression_) {
140,752✔
700
      if (r < OP_UNION) {
115,200✔
701
        const auto& it {model::surface_map.find(abs(r))};
68,422✔
702
        if (it == model::surface_map.end()) {
68,422✔
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);
68,422✔
708
      }
709
    }
710

711
    // Check if this is a simple cell.
712
    simple_ = true;
25,552✔
713
    for (int32_t token : expression_) {
123,958✔
714
      if (token == OP_UNION) {
99,655✔
715
        simple_ = false;
1,249✔
716
        // Ensure intersections have precedence over unions
717
        add_precedence();
1,249✔
718
        break;
1,249✔
719
      }
720
    }
721

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

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

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

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

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

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

769
  // Keep track of return iterator distance. If we don't encounter a left
770
  // parenthesis, we return an iterator corresponding to wherever the right
771
  // parenthesis is inserted. If a left parenthesis is encountered, an iterator
772
  // corresponding to the left parenthesis is returned. Also note that we keep
773
  // track of a *distance* instead of an iterator because the underlying memory
774
  // allocation may change.
775
  std::size_t return_it_dist = 0;
32✔
776

777
  // Add right parenthesis
778
  // While the start iterator is within the bounds of infix
779
  while (start + 1 < expression_.size()) {
224✔
780
    start++;
224✔
781

782
    // If the current token is an operator and is different than the start token
783
    if (expression_[start] >= OP_UNION && expression_[start] != start_token) {
224✔
784
      // Skip wrapped regions but save iterator position to check precedence and
785
      // add right parenthesis, right parenthesis position depends on the
786
      // operator, when the operator is a union then do not include the operator
787
      // in the region, when the operator is an intersection then include the
788
      // operator and next surface
789
      if (expression_[start] == OP_LEFT_PAREN) {
32✔
790
        return_it_dist = start;
×
791
        int depth = 1;
×
792
        do {
793
          start++;
×
794
          if (expression_[start] > OP_COMPLEMENT) {
×
795
            if (expression_[start] == OP_RIGHT_PAREN) {
×
796
              depth--;
×
797
            } else {
798
              depth++;
×
799
            }
800
          }
801
        } while (depth > 0);
×
802
      } else {
803
        if (start_token == OP_UNION) {
32✔
804
          --start;
×
805
        }
806
        expression_.insert(expression_.begin() + start, OP_RIGHT_PAREN);
32✔
807
        if (return_it_dist > 0) {
32✔
808
          return return_it_dist;
×
809
        } else {
810
          return start - 1;
32✔
811
        }
812
      }
813
    }
814
  }
815
  // If we get here a right parenthesis hasn't been placed,
816
  // return iterator
817
  expression_.push_back(OP_RIGHT_PAREN);
×
818
  if (return_it_dist > 0) {
×
819
    return return_it_dist;
×
820
  } else {
821
    return start - 1;
×
822
  }
823
}
824

825
//==============================================================================
826

827
void Region::add_precedence()
1,249✔
828
{
829
  int32_t current_op = 0;
1,249✔
830
  std::size_t current_dist = 0;
1,249✔
831

832
  for (int64_t i = 0; i < expression_.size(); i++) {
24,606✔
833
    int32_t token = expression_[i];
23,357✔
834

835
    if (token == OP_UNION || token == OP_INTERSECTION) {
23,357✔
836
      if (current_op == 0) {
9,100✔
837
        // Set the current operator if is hasn't been set
838
        current_op = token;
3,283✔
839
        current_dist = i;
3,283✔
840
      } else if (token != current_op) {
5,817✔
841
        // If the current operator doesn't match the token, add parenthesis to
842
        // assert precedence
843
        if (current_op == OP_INTERSECTION) {
32✔
844
          i = add_parentheses(current_dist);
16✔
845
        } else {
846
          i = add_parentheses(i);
16✔
847
        }
848
        current_op = 0;
32✔
849
        current_dist = 0;
32✔
850
      }
851
    } else if (token > OP_COMPLEMENT) {
14,257✔
852
      // If the token is a parenthesis reset the current operator
853
      current_op = 0;
3,940✔
854
      current_dist = 0;
3,940✔
855
    }
856
  }
857
}
1,249✔
858

859
//==============================================================================
860
//! Convert infix region specification to Reverse Polish Notation (RPN)
861
//!
862
//! This function uses the shunting-yard algorithm.
863
//==============================================================================
864

865
vector<int32_t> Region::generate_postfix(int32_t cell_id) const
34,476✔
866
{
867
  vector<int32_t> rpn;
34,476✔
868
  vector<int32_t> stack;
34,476✔
869

870
  for (int32_t token : expression_) {
118,652✔
871
    if (token < OP_UNION) {
84,176✔
872
      // If token is not an operator, add it to output
873
      rpn.push_back(token);
69,538✔
874
    } else if (token < OP_RIGHT_PAREN) {
14,638✔
875
      // Regular operators union, intersection, complement
876
      while (stack.size() > 0) {
16,578✔
877
        int32_t op = stack.back();
13,106✔
878

879
        if (op < OP_RIGHT_PAREN && ((token == OP_COMPLEMENT && token < op) ||
13,106✔
880
                                     (token != OP_COMPLEMENT && token <= op))) {
6,470✔
881
          // While there is an operator, op, on top of the stack, if the token
882
          // is left-associative and its precedence is less than or equal to
883
          // that of op or if the token is right-associative and its precedence
884
          // is less than that of op, move op to the output queue and push the
885
          // token on to the stack. Note that only complement is
886
          // right-associative.
887
          rpn.push_back(op);
6,470✔
888
          stack.pop_back();
6,470✔
889
        } else {
890
          break;
891
        }
892
      }
893

894
      stack.push_back(token);
10,108✔
895

896
    } else if (token == OP_LEFT_PAREN) {
4,530✔
897
      // If the token is a left parenthesis, push it onto the stack
898
      stack.push_back(token);
2,265✔
899

900
    } else {
901
      // If the token is a right parenthesis, move operators from the stack to
902
      // the output queue until reaching the left parenthesis.
903
      for (auto it = stack.rbegin(); *it != OP_LEFT_PAREN; it++) {
4,530✔
904
        // If we run out of operators without finding a left parenthesis, it
905
        // means there are mismatched parentheses.
906
        if (it == stack.rend()) {
2,265✔
907
          fatal_error(fmt::format(
×
908
            "Mismatched parentheses in region specification for cell {}",
909
            cell_id));
910
        }
911
        rpn.push_back(stack.back());
2,265✔
912
        stack.pop_back();
2,265✔
913
      }
914

915
      // Pop the left parenthesis.
916
      stack.pop_back();
2,265✔
917
    }
918
  }
919

920
  while (stack.size() > 0) {
35,849✔
921
    int32_t op = stack.back();
1,373✔
922

923
    // If the operator is a parenthesis it is mismatched.
924
    if (op >= OP_RIGHT_PAREN) {
1,373✔
925
      fatal_error(fmt::format(
×
926
        "Mismatched parentheses in region specification for cell {}", cell_id));
927
    }
928

929
    rpn.push_back(stack.back());
1,373✔
930
    stack.pop_back();
1,373✔
931
  }
932

933
  return rpn;
68,952✔
934
}
34,476✔
935

936
//==============================================================================
937

938
std::string Region::str() const
27,264✔
939
{
940
  std::stringstream region_spec {};
27,264✔
941
  if (!expression_.empty()) {
27,264✔
942
    for (int32_t token : expression_) {
84,851✔
943
      if (token == OP_LEFT_PAREN) {
65,601✔
944
        region_spec << " (";
1,840✔
945
      } else if (token == OP_RIGHT_PAREN) {
63,761✔
946
        region_spec << " )";
1,840✔
947
      } else if (token == OP_COMPLEMENT) {
61,921✔
948
        region_spec << " ~";
×
949
      } else if (token == OP_INTERSECTION) {
61,921✔
950
      } else if (token == OP_UNION) {
57,534✔
951
        region_spec << " |";
4,011✔
952
      } else {
953
        // Note the off-by-one indexing
954
        auto surf_id = model::surfaces[abs(token) - 1]->id_;
53,523✔
955
        region_spec << " " << ((token > 0) ? surf_id : -surf_id);
53,523✔
956
      }
957
    }
958
  }
959
  return region_spec.str();
54,528✔
960
}
27,264✔
961

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

964
std::pair<double, int32_t> Region::distance(
2,147,483,647✔
965
  Position r, Direction u, int32_t on_surface) const
966
{
967
  double min_dist {INFTY};
2,147,483,647✔
968
  int32_t i_surf {std::numeric_limits<int32_t>::max()};
2,147,483,647✔
969

970
  for (int32_t token : expression_) {
2,147,483,647✔
971
    // Ignore this token if it corresponds to an operator rather than a region.
972
    if (token >= OP_UNION)
2,147,483,647✔
973
      continue;
169,611,236✔
974

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

980
    // Check if this distance is the new minimum.
981
    if (d < min_dist) {
2,147,483,647✔
982
      if (min_dist - d >= FP_PRECISION * min_dist) {
2,147,483,647✔
983
        min_dist = d;
2,147,483,647✔
984
        i_surf = -token;
2,147,483,647✔
985
      }
986
    }
987
  }
988

989
  return {min_dist, i_surf};
2,147,483,647✔
990
}
991

992
//==============================================================================
993

994
bool Region::contains(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
995
{
996
  if (simple_) {
2,147,483,647✔
997
    return contains_simple(r, u, on_surface);
2,147,483,647✔
998
  } else {
999
    return contains_complex(r, u, on_surface);
6,686,743✔
1000
  }
1001
}
1002

1003
//==============================================================================
1004

1005
bool Region::contains_simple(Position r, Direction u, int32_t on_surface) const
2,147,483,647✔
1006
{
1007
  for (int32_t token : expression_) {
2,147,483,647✔
1008
    // Assume that no tokens are operators. Evaluate the sense of particle with
1009
    // respect to the surface and see if the token matches the sense. If the
1010
    // particle's surface attribute is set and matches the token, that
1011
    // overrides the determination based on sense().
1012
    if (token == on_surface) {
2,147,483,647✔
1013
    } else if (-token == on_surface) {
2,147,483,647✔
1014
      return false;
948,278,305✔
1015
    } else {
1016
      // Note the off-by-one indexing
1017
      bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
2,147,483,647✔
1018
      if (sense != (token > 0)) {
2,147,483,647✔
1019
        return false;
815,784,378✔
1020
      }
1021
    }
1022
  }
1023
  return true;
2,147,483,647✔
1024
}
1025

1026
//==============================================================================
1027

1028
bool Region::contains_complex(Position r, Direction u, int32_t on_surface) const
6,686,743✔
1029
{
1030
  bool in_cell = true;
6,686,743✔
1031
  int total_depth = 0;
6,686,743✔
1032

1033
  // For each token
1034
  for (auto it = expression_.begin(); it != expression_.end(); it++) {
106,233,166✔
1035
    int32_t token = *it;
101,407,289✔
1036

1037
    // If the token is a surface evaluate the sense
1038
    // If the token is a union or intersection check to
1039
    // short circuit
1040
    if (token < OP_UNION) {
101,407,289✔
1041
      if (token == on_surface) {
44,315,806✔
1042
        in_cell = true;
3,307,181✔
1043
      } else if (-token == on_surface) {
41,008,625✔
1044
        in_cell = false;
670,777✔
1045
      } else {
1046
        // Note the off-by-one indexing
1047
        bool sense = model::surfaces[abs(token) - 1]->sense(r, u);
40,337,848✔
1048
        in_cell = (sense == (token > 0));
40,337,848✔
1049
      }
1050
    } else if ((token == OP_UNION && in_cell == true) ||
57,091,483✔
1051
               (token == OP_INTERSECTION && in_cell == false)) {
27,901,390✔
1052
      // If the total depth is zero return
1053
      if (total_depth == 0) {
6,227,464✔
1054
        return in_cell;
1,860,866✔
1055
      }
1056

1057
      total_depth--;
4,366,598✔
1058

1059
      // While the iterator is within the bounds of the vector
1060
      int depth = 1;
4,366,598✔
1061
      do {
1062
        // Get next token
1063
        it++;
27,521,256✔
1064
        int32_t next_token = *it;
27,521,256✔
1065

1066
        // If the token is an a parenthesis
1067
        if (next_token > OP_COMPLEMENT) {
27,521,256✔
1068
          // Adjust depth accordingly
1069
          if (next_token == OP_RIGHT_PAREN) {
4,553,042✔
1070
            depth--;
4,459,820✔
1071
          } else {
1072
            depth++;
93,222✔
1073
          }
1074
        }
1075
      } while (depth > 0);
27,521,256✔
1076
    } else if (token == OP_LEFT_PAREN) {
55,230,617✔
1077
      total_depth++;
8,800,777✔
1078
    } else if (token == OP_RIGHT_PAREN) {
42,063,242✔
1079
      total_depth--;
4,434,179✔
1080
    }
1081
  }
1082
  return in_cell;
4,825,877✔
1083
}
1084

1085
//==============================================================================
1086

1087
BoundingBox Region::bounding_box(int32_t cell_id) const
191✔
1088
{
1089
  if (simple_) {
191✔
1090
    return bounding_box_simple();
67✔
1091
  } else {
1092
    auto postfix = generate_postfix(cell_id);
124✔
1093
    return bounding_box_complex(postfix);
124✔
1094
  }
124✔
1095
}
1096

1097
//==============================================================================
1098

1099
BoundingBox Region::bounding_box_simple() const
67✔
1100
{
1101
  BoundingBox bbox;
67✔
1102
  for (int32_t token : expression_) {
287✔
1103
    bbox &= model::surfaces[abs(token) - 1]->bounding_box(token > 0);
220✔
1104
  }
1105
  return bbox;
67✔
1106
}
1107

1108
//==============================================================================
1109

1110
BoundingBox Region::bounding_box_complex(vector<int32_t> postfix) const
124✔
1111
{
1112
  vector<BoundingBox> stack(postfix.size());
124✔
1113
  int i_stack = -1;
124✔
1114

1115
  for (auto& token : postfix) {
2,232✔
1116
    if (token == OP_UNION) {
2,108✔
1117
      stack[i_stack - 1] = stack[i_stack - 1] | stack[i_stack];
434✔
1118
      i_stack--;
434✔
1119
    } else if (token == OP_INTERSECTION) {
1,674✔
1120
      stack[i_stack - 1] = stack[i_stack - 1] & stack[i_stack];
558✔
1121
      i_stack--;
558✔
1122
    } else {
1123
      i_stack++;
1,116✔
1124
      stack[i_stack] = model::surfaces[abs(token) - 1]->bounding_box(token > 0);
1,116✔
1125
    }
1126
  }
1127

1128
  assert(i_stack == 0);
100✔
1129
  return stack.front();
248✔
1130
}
124✔
1131

1132
//==============================================================================
1133

1134
vector<int32_t> Region::surfaces() const
6,994✔
1135
{
1136
  if (simple_) {
6,994✔
1137
    return expression_;
6,994✔
1138
  }
1139

1140
  vector<int32_t> surfaces = expression_;
×
1141

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

1145
  while (it != surfaces.end()) {
×
1146
    surfaces.erase(it);
×
1147

1148
    it = std::find_if(surfaces.begin(), surfaces.end(),
×
1149
      [&](const auto& value) { return value >= OP_UNION; });
×
1150
  }
1151

1152
  return surfaces;
×
1153
}
1154

1155
//==============================================================================
1156
// Non-method functions
1157
//==============================================================================
1158

1159
void read_cells(pugi::xml_node node)
7,315✔
1160
{
1161
  // Count the number of cells.
1162
  int n_cells = 0;
7,315✔
1163
  for (pugi::xml_node cell_node : node.children("cell")) {
41,667✔
1164
    n_cells++;
34,352✔
1165
  }
1166

1167
  // Loop over XML cell elements and populate the array.
1168
  model::cells.reserve(n_cells);
7,315✔
1169
  for (pugi::xml_node cell_node : node.children("cell")) {
41,667✔
1170
    model::cells.push_back(make_unique<CSGCell>(cell_node));
34,352✔
1171
  }
1172

1173
  // Fill the cell map.
1174
  for (int i = 0; i < model::cells.size(); i++) {
41,667✔
1175
    int32_t id = model::cells[i]->id_;
34,352✔
1176
    auto search = model::cell_map.find(id);
34,352✔
1177
    if (search == model::cell_map.end()) {
34,352✔
1178
      model::cell_map[id] = i;
34,352✔
1179
    } else {
1180
      fatal_error(
×
1181
        fmt::format("Two or more cells use the same unique ID: {}", id));
×
1182
    }
1183
  }
1184

1185
  read_dagmc_universes(node);
7,315✔
1186

1187
  populate_universes();
7,313✔
1188

1189
  // Allocate the cell overlap count if necessary.
1190
  if (settings::check_overlaps) {
7,313✔
1191
    model::overlap_check_count.resize(model::cells.size(), 0);
276✔
1192
  }
1193

1194
  if (model::cells.size() == 0) {
7,313✔
1195
    fatal_error("No cells were found in the geometry.xml file");
×
1196
  }
1197
}
7,313✔
1198

1199
void populate_universes()
7,315✔
1200
{
1201
  // Used to map universe index to the index of an implicit complement cell for
1202
  // DAGMC universes
1203
  std::unordered_map<int, int> implicit_comp_cells;
7,315✔
1204

1205
  // Populate the Universe vector and map.
1206
  for (int index_cell = 0; index_cell < model::cells.size(); index_cell++) {
42,135✔
1207
    int32_t uid = model::cells[index_cell]->universe_;
34,820✔
1208
    auto it = model::universe_map.find(uid);
34,820✔
1209
    if (it == model::universe_map.end()) {
34,820✔
1210
      model::universes.push_back(make_unique<Universe>());
18,635✔
1211
      model::universes.back()->id_ = uid;
18,635✔
1212
      model::universes.back()->cells_.push_back(index_cell);
18,635✔
1213
      model::universe_map[uid] = model::universes.size() - 1;
18,635✔
1214
    } else {
1215
#ifdef OPENMC_DAGMC_ENABLED
1216
      // Skip implicit complement cells for now
1217
      Universe* univ = model::universes[it->second].get();
2,370✔
1218
      DAGUniverse* dag_univ = dynamic_cast<DAGUniverse*>(univ);
2,370✔
1219
      if (dag_univ && (dag_univ->implicit_complement_idx() == index_cell)) {
2,370✔
1220
        implicit_comp_cells[it->second] = index_cell;
95✔
1221
        continue;
95✔
1222
      }
1223
#endif
1224

1225
      model::universes[it->second]->cells_.push_back(index_cell);
16,090✔
1226
    }
1227
    if (model::cells[index_cell]->virtual_lattice_) {
34,725✔
1228
      model::universes[it->second]->filled_with_triso_base_ =
16✔
1229
        model::cells[index_cell]->id_;
16✔
1230
    }
1231
  }
1232

1233
  // Add DAGUniverse implicit complement cells last
1234
  for (const auto& it : implicit_comp_cells) {
7,410✔
1235
    int index_univ = it.first;
95✔
1236
    int index_cell = it.second;
95✔
1237
    model::universes[index_univ]->cells_.push_back(index_cell);
95✔
1238
  }
1239

1240
  model::universes.shrink_to_fit();
7,315✔
1241
}
7,315✔
1242

1243
//==============================================================================
1244
// C-API functions
1245
//==============================================================================
1246

1247
extern "C" int openmc_cell_get_fill(
648✔
1248
  int32_t index, int* type, int32_t** indices, int32_t* n)
1249
{
1250
  if (index >= 0 && index < model::cells.size()) {
648✔
1251
    Cell& c {*model::cells[index]};
648✔
1252
    *type = static_cast<int>(c.type_);
648✔
1253
    if (c.type_ == Fill::MATERIAL) {
648✔
1254
      *indices = c.material_.data();
648✔
1255
      *n = c.material_.size();
648✔
1256
    } else {
1257
      *indices = &c.fill_;
×
1258
      *n = 1;
×
1259
    }
1260
  } else {
1261
    set_errmsg("Index in cells array is out of bounds.");
×
1262
    return OPENMC_E_OUT_OF_BOUNDS;
×
1263
  }
1264
  return 0;
648✔
1265
}
1266

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

1300
extern "C" int openmc_cell_set_temperature(
91✔
1301
  int32_t index, double T, const int32_t* instance, bool set_contained)
1302
{
1303
  if (index < 0 || index >= model::cells.size()) {
91✔
1304
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
1305
    return OPENMC_E_OUT_OF_BOUNDS;
×
1306
  }
1307

1308
  int32_t instance_index = instance ? *instance : -1;
91✔
1309
  try {
1310
    model::cells[index]->set_temperature(T, instance_index, set_contained);
91✔
1311
  } catch (const std::exception& e) {
×
1312
    set_errmsg(e.what());
×
1313
    return OPENMC_E_UNASSIGNED;
×
1314
  }
×
1315
  return 0;
91✔
1316
}
1317

1318
extern "C" int openmc_cell_get_temperature(
108✔
1319
  int32_t index, const int32_t* instance, double* T)
1320
{
1321
  if (index < 0 || index >= model::cells.size()) {
108✔
1322
    strcpy(openmc_err_msg, "Index in cells array is out of bounds.");
×
1323
    return OPENMC_E_OUT_OF_BOUNDS;
×
1324
  }
1325

1326
  int32_t instance_index = instance ? *instance : -1;
108✔
1327
  try {
1328
    *T = model::cells[index]->temperature(instance_index);
108✔
1329
  } catch (const std::exception& e) {
×
1330
    set_errmsg(e.what());
×
1331
    return OPENMC_E_UNASSIGNED;
×
1332
  }
×
1333
  return 0;
108✔
1334
}
1335

1336
//! Get the bounding box of a cell
1337
extern "C" int openmc_cell_bounding_box(
155✔
1338
  const int32_t index, double* llc, double* urc)
1339
{
1340

1341
  BoundingBox bbox;
155✔
1342

1343
  const auto& c = model::cells[index];
155✔
1344
  bbox = c->bounding_box();
155✔
1345

1346
  // set lower left corner values
1347
  llc[0] = bbox.xmin;
155✔
1348
  llc[1] = bbox.ymin;
155✔
1349
  llc[2] = bbox.zmin;
155✔
1350

1351
  // set upper right corner values
1352
  urc[0] = bbox.xmax;
155✔
1353
  urc[1] = bbox.ymax;
155✔
1354
  urc[2] = bbox.zmax;
155✔
1355

1356
  return 0;
155✔
1357
}
1358

1359
//! Get the name of a cell
1360
extern "C" int openmc_cell_get_name(int32_t index, const char** name)
24✔
1361
{
1362
  if (index < 0 || index >= model::cells.size()) {
24✔
1363
    set_errmsg("Index in cells array is out of bounds.");
×
1364
    return OPENMC_E_OUT_OF_BOUNDS;
×
1365
  }
1366

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

1369
  return 0;
24✔
1370
}
1371

1372
//! Set the name of a cell
1373
extern "C" int openmc_cell_set_name(int32_t index, const char* name)
12✔
1374
{
1375
  if (index < 0 || index >= model::cells.size()) {
12✔
1376
    set_errmsg("Index in cells array is out of bounds.");
×
1377
    return OPENMC_E_OUT_OF_BOUNDS;
×
1378
  }
1379

1380
  model::cells[index]->set_name(name);
12✔
1381

1382
  return 0;
12✔
1383
}
1384

1385
//==============================================================================
1386
//! Define a containing (parent) cell
1387
//==============================================================================
1388

1389
//! Used to locate a universe fill in the geometry
1390
struct ParentCell {
1391
  bool operator==(const ParentCell& other) const
96✔
1392
  {
1393
    return cell_index == other.cell_index &&
192✔
1394
           lattice_index == other.lattice_index;
192✔
1395
  }
1396

1397
  bool operator<(const ParentCell& other) const
1398
  {
1399
    return cell_index < other.cell_index ||
1400
           (cell_index == other.cell_index &&
1401
             lattice_index < other.lattice_index);
1402
  }
1403

1404
  int64_t cell_index;
1405
  int64_t lattice_index;
1406
};
1407

1408
//! Structure used to insert ParentCell into hashed STL data structures
1409
struct ParentCellHash {
1410
  std::size_t operator()(const ParentCell& p) const
481✔
1411
  {
1412
    return 4096 * p.cell_index + p.lattice_index;
481✔
1413
  }
1414
};
1415

1416
//! Used to manage a traversal stack when locating parent cells of a cell
1417
//! instance in the model
1418
struct ParentCellStack {
1419

1420
  //! push method that adds to the parent_cells visited cells for this search
1421
  //! universe
1422
  void push(int32_t search_universe, const ParentCell& pc)
64✔
1423
  {
1424
    parent_cells_.push_back(pc);
64✔
1425
    // add parent cell to the set of cells we've visited for this search
1426
    // universe
1427
    visited_cells_[search_universe].insert(pc);
64✔
1428
  }
64✔
1429

1430
  //! removes the last parent_cell and clears the visited cells for the popped
1431
  //! cell's universe
1432
  void pop()
48✔
1433
  {
1434
    visited_cells_[this->current_univ()].clear();
48✔
1435
    parent_cells_.pop_back();
48✔
1436
  }
48✔
1437

1438
  //! checks whether or not the parent cell has been visited already for this
1439
  //! search universe
1440
  bool visited(int32_t search_universe, const ParentCell& parent_cell)
417✔
1441
  {
1442
    return visited_cells_[search_universe].count(parent_cell) != 0;
417✔
1443
  }
1444

1445
  //! return the next universe to search for a parent cell
1446
  int32_t current_univ() const
48✔
1447
  {
1448
    return model::cells[parent_cells_.back().cell_index]->universe_;
48✔
1449
  }
1450

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

1454
  //! compute an instance for the provided distribcell index
1455
  int32_t compute_instance(int32_t distribcell_index) const
145✔
1456
  {
1457
    if (distribcell_index == C_NONE)
145✔
1458
      return 0;
65✔
1459

1460
    int32_t instance = 0;
80✔
1461
    for (const auto& parent_cell : this->parent_cells_) {
144✔
1462
      auto& cell = model::cells[parent_cell.cell_index];
64✔
1463
      if (cell->type_ == Fill::UNIVERSE) {
64✔
1464
        instance += cell->offset_[distribcell_index];
×
1465
      } else if (cell->type_ == Fill::LATTICE) {
64✔
1466
        auto& lattice = model::lattices[cell->fill_];
64✔
1467
        instance +=
64✔
1468
          lattice->offset(distribcell_index, parent_cell.lattice_index);
64✔
1469
      }
1470
    }
1471
    return instance;
80✔
1472
  }
1473

1474
  // Accessors
1475
  vector<ParentCell>& parent_cells() { return parent_cells_; }
291✔
1476
  const vector<ParentCell>& parent_cells() const { return parent_cells_; }
1477

1478
  // Data Members
1479
  vector<ParentCell> parent_cells_;
1480
  std::unordered_map<int32_t, std::unordered_set<ParentCell, ParentCellHash>>
1481
    visited_cells_;
1482
};
1483

1484
vector<ParentCell> Cell::find_parent_cells(
×
1485
  int32_t instance, const Position& r) const
1486
{
1487

1488
  // create a temporary particle
1489
  GeometryState dummy_particle {};
×
1490
  dummy_particle.r() = r;
×
1491
  dummy_particle.u() = {0., 0., 1.};
×
1492

1493
  return find_parent_cells(instance, dummy_particle);
×
1494
}
1495

1496
vector<ParentCell> Cell::find_parent_cells(
×
1497
  int32_t instance, GeometryState& p) const
1498
{
1499
  // look up the particle's location
1500
  exhaustive_find_cell(p);
×
1501
  const auto& coords = p.coord();
×
1502

1503
  // build a parent cell stack from the particle coordinates
1504
  ParentCellStack stack;
×
1505
  bool cell_found = false;
×
1506
  for (auto it = coords.begin(); it != coords.end(); it++) {
×
1507
    const auto& coord = *it;
×
1508
    const auto& cell = model::cells[coord.cell()];
×
1509
    // if the cell at this level matches the current cell, stop adding to the
1510
    // stack
1511
    if (coord.cell() == model::cell_map[this->id_]) {
×
1512
      cell_found = true;
×
1513
      break;
×
1514
    }
1515

1516
    // if filled with a lattice, get the lattice index from the next
1517
    // level in the coordinates to push to the stack
1518
    int lattice_idx = C_NONE;
×
1519
    if (cell->type_ == Fill::LATTICE) {
×
1520
      const auto& next_coord = *(it + 1);
×
1521
      lattice_idx = model::lattices[next_coord.lattice()]->get_flat_index(
×
1522
        next_coord.lattice_index());
1523
    }
1524
    stack.push(coord.universe(), {coord.cell(), lattice_idx});
×
1525
  }
1526

1527
  // if this loop finished because the cell was found and
1528
  // the instance matches the one requested in the call
1529
  // we have the correct path and can return the stack
1530
  if (cell_found &&
×
1531
      stack.compute_instance(this->distribcell_index_) == instance) {
×
1532
    return stack.parent_cells();
×
1533
  }
1534

1535
  // fall back on an exhaustive search for the cell's parents
1536
  return exhaustive_find_parent_cells(instance);
×
1537
}
1538

1539
vector<ParentCell> Cell::exhaustive_find_parent_cells(int32_t instance) const
97✔
1540
{
1541
  ParentCellStack stack;
97✔
1542
  // start with this cell's universe
1543
  int32_t prev_univ_idx;
1544
  int32_t univ_idx = this->universe_;
97✔
1545

1546
  while (true) {
1547
    const auto& univ = model::universes[univ_idx];
145✔
1548
    prev_univ_idx = univ_idx;
145✔
1549

1550
    // search for a cell that is filled w/ this universe
1551
    for (const auto& cell : model::cells) {
996✔
1552
      // if this is a material-filled cell, move on
1553
      if (cell->type_ == Fill::MATERIAL)
915✔
1554
        continue;
498✔
1555

1556
      if (cell->type_ == Fill::UNIVERSE) {
417✔
1557
        // if this is in the set of cells previously visited for this universe,
1558
        // move on
1559
        if (stack.visited(univ_idx, {model::cell_map[cell->id_], C_NONE}))
257✔
1560
          continue;
×
1561

1562
        // if this cell contains the universe we're searching for, add it to the
1563
        // stack
1564
        if (cell->fill_ == univ_idx) {
257✔
1565
          stack.push(univ_idx, {model::cell_map[cell->id_], C_NONE});
×
1566
          univ_idx = cell->universe_;
×
1567
        }
1568
      } else if (cell->type_ == Fill::LATTICE) {
160✔
1569
        // retrieve the lattice and lattice universes
1570
        const auto& lattice = model::lattices[cell->fill_];
160✔
1571
        const auto& lattice_univs = lattice->universes_;
160✔
1572

1573
        // start search for universe
1574
        auto lat_it = lattice_univs.begin();
160✔
1575
        while (true) {
1576
          // find the next lattice cell with this universe
1577
          lat_it = std::find(lat_it, lattice_univs.end(), univ_idx);
256✔
1578
          if (lat_it == lattice_univs.end())
256✔
1579
            break;
96✔
1580

1581
          int lattice_idx = lat_it - lattice_univs.begin();
160✔
1582

1583
          // move iterator forward one to avoid finding the same entry
1584
          lat_it++;
160✔
1585
          if (stack.visited(
160✔
1586
                univ_idx, {model::cell_map[cell->id_], lattice_idx}))
160✔
1587
            continue;
96✔
1588

1589
          // add this cell and lattice index to the stack and exit loop
1590
          stack.push(univ_idx, {model::cell_map[cell->id_], lattice_idx});
64✔
1591
          univ_idx = cell->universe_;
64✔
1592
          break;
64✔
1593
        }
96✔
1594
      }
1595
      // if we've updated the universe, break
1596
      if (prev_univ_idx != univ_idx)
417✔
1597
        break;
64✔
1598
    } // end cell loop search for universe
1599

1600
    // if we're at the top of the geometry and the instance matches, we're done
1601
    if (univ_idx == model::root_universe &&
290✔
1602
        stack.compute_instance(this->distribcell_index_) == instance)
145✔
1603
      break;
97✔
1604

1605
    // if there is no match on the original cell's universe, report an error
1606
    if (univ_idx == this->universe_) {
48✔
1607
      fatal_error(
×
1608
        fmt::format("Could not find the parent cells for cell {}, instance {}.",
×
1609
          this->id_, instance));
×
1610
    }
1611

1612
    // if we don't find a suitable update, adjust the stack and continue
1613
    if (univ_idx == model::root_universe || univ_idx == prev_univ_idx) {
48✔
1614
      stack.pop();
48✔
1615
      univ_idx = stack.empty() ? this->universe_ : stack.current_univ();
48✔
1616
    }
1617

1618
  } // end while
48✔
1619

1620
  // reverse the stack so the highest cell comes first
1621
  std::reverse(stack.parent_cells().begin(), stack.parent_cells().end());
97✔
1622
  return stack.parent_cells();
194✔
1623
}
97✔
1624

1625
std::unordered_map<int32_t, vector<int32_t>> Cell::get_contained_cells(
145✔
1626
  int32_t instance, Position* hint) const
1627
{
1628
  std::unordered_map<int32_t, vector<int32_t>> contained_cells;
145✔
1629

1630
  // if this is a material-filled cell it has no contained cells
1631
  if (this->type_ == Fill::MATERIAL)
145✔
1632
    return contained_cells;
48✔
1633

1634
  // find the pathway through the geometry to this cell
1635
  vector<ParentCell> parent_cells;
97✔
1636

1637
  // if a positional hint is provided, attempt to do a fast lookup
1638
  // of the parent cells
1639
  parent_cells = hint ? find_parent_cells(instance, *hint)
194✔
1640
                      : exhaustive_find_parent_cells(instance);
97✔
1641

1642
  // if this cell is filled w/ a material, it contains no other cells
1643
  if (type_ != Fill::MATERIAL) {
97✔
1644
    this->get_contained_cells_inner(contained_cells, parent_cells);
97✔
1645
  }
1646

1647
  return contained_cells;
97✔
1648
}
97✔
1649

1650
//! Get all cells within this cell
1651
void Cell::get_contained_cells_inner(
87,699✔
1652
  std::unordered_map<int32_t, vector<int32_t>>& contained_cells,
1653
  vector<ParentCell>& parent_cells) const
1654
{
1655

1656
  // filled by material, determine instance based on parent cells
1657
  if (type_ == Fill::MATERIAL) {
87,699✔
1658
    int instance = 0;
87,090✔
1659
    if (this->distribcell_index_ >= 0) {
87,090✔
1660
      for (auto& parent_cell : parent_cells) {
261,268✔
1661
        auto& cell = model::cells[parent_cell.cell_index];
174,178✔
1662
        if (cell->type_ == Fill::UNIVERSE) {
174,178✔
1663
          instance += cell->offset_[distribcell_index_];
85,490✔
1664
        } else if (cell->type_ == Fill::LATTICE) {
88,688✔
1665
          auto& lattice = model::lattices[cell->fill_];
88,688✔
1666
          instance += lattice->offset(
177,376✔
1667
            this->distribcell_index_, parent_cell.lattice_index);
88,688✔
1668
        }
1669
      }
1670
    }
1671
    // add entry to contained cells
1672
    contained_cells[model::cell_map[id_]].push_back(instance);
87,090✔
1673
    // filled with universe, add the containing cell to the parent cells
1674
    // and recurse
1675
  } else if (type_ == Fill::UNIVERSE) {
609✔
1676
    parent_cells.push_back({model::cell_map[id_], -1});
513✔
1677
    auto& univ = model::universes[fill_];
513✔
1678
    for (auto cell_index : univ->cells_) {
3,107✔
1679
      auto& cell = model::cells[cell_index];
2,594✔
1680
      cell->get_contained_cells_inner(contained_cells, parent_cells);
2,594✔
1681
    }
1682
    parent_cells.pop_back();
513✔
1683
    // filled with a lattice, visit each universe in the lattice
1684
    // with a recursive call to collect the cell instances
1685
  } else if (type_ == Fill::LATTICE) {
96✔
1686
    auto& lattice = model::lattices[fill_];
96✔
1687
    for (auto i = lattice->begin(); i != lattice->end(); ++i) {
84,768✔
1688
      auto& univ = model::universes[*i];
84,672✔
1689
      parent_cells.push_back({model::cell_map[id_], i.indx_});
84,672✔
1690
      for (auto cell_index : univ->cells_) {
169,680✔
1691
        auto& cell = model::cells[cell_index];
85,008✔
1692
        cell->get_contained_cells_inner(contained_cells, parent_cells);
85,008✔
1693
      }
1694
      parent_cells.pop_back();
84,672✔
1695
    }
1696
  }
1697
}
87,699✔
1698

1699
//! Return the index in the cells array of a cell with a given ID
1700
extern "C" int openmc_get_cell_index(int32_t id, int32_t* index)
730✔
1701
{
1702
  auto it = model::cell_map.find(id);
730✔
1703
  if (it != model::cell_map.end()) {
730✔
1704
    *index = it->second;
718✔
1705
    return 0;
718✔
1706
  } else {
1707
    set_errmsg("No cell exists with ID=" + std::to_string(id) + ".");
12✔
1708
    return OPENMC_E_INVALID_ID;
12✔
1709
  }
1710
}
1711

1712
//! Return the ID of a cell
1713
extern "C" int openmc_cell_get_id(int32_t index, int32_t* id)
601,675✔
1714
{
1715
  if (index >= 0 && index < model::cells.size()) {
601,675✔
1716
    *id = model::cells[index]->id_;
601,675✔
1717
    return 0;
601,675✔
1718
  } else {
1719
    set_errmsg("Index in cells array is out of bounds.");
×
1720
    return OPENMC_E_OUT_OF_BOUNDS;
×
1721
  }
1722
}
1723

1724
//! Set the ID of a cell
1725
extern "C" int openmc_cell_set_id(int32_t index, int32_t id)
24✔
1726
{
1727
  if (index >= 0 && index < model::cells.size()) {
24✔
1728
    model::cells[index]->id_ = id;
24✔
1729
    model::cell_map[id] = index;
24✔
1730
    return 0;
24✔
1731
  } else {
1732
    set_errmsg("Index in cells array is out of bounds.");
×
1733
    return OPENMC_E_OUT_OF_BOUNDS;
×
1734
  }
1735
}
1736

1737
//! Return the translation vector of a cell
1738
extern "C" int openmc_cell_get_translation(int32_t index, double xyz[])
58✔
1739
{
1740
  if (index >= 0 && index < model::cells.size()) {
58✔
1741
    auto& cell = model::cells[index];
58✔
1742
    xyz[0] = cell->translation_.x;
58✔
1743
    xyz[1] = cell->translation_.y;
58✔
1744
    xyz[2] = cell->translation_.z;
58✔
1745
    return 0;
58✔
1746
  } else {
1747
    set_errmsg("Index in cells array is out of bounds.");
×
1748
    return OPENMC_E_OUT_OF_BOUNDS;
×
1749
  }
1750
}
1751

1752
//! Set the translation vector of a cell
1753
extern "C" int openmc_cell_set_translation(int32_t index, const double xyz[])
35✔
1754
{
1755
  if (index >= 0 && index < model::cells.size()) {
35✔
1756
    if (model::cells[index]->fill_ == C_NONE) {
35✔
1757
      set_errmsg(fmt::format("Cannot apply a translation to cell {}"
12✔
1758
                             " because it is not filled with another universe",
1759
        index));
1760
      return OPENMC_E_GEOMETRY;
12✔
1761
    }
1762
    model::cells[index]->translation_ = Position(xyz);
23✔
1763
    return 0;
23✔
1764
  } else {
1765
    set_errmsg("Index in cells array is out of bounds.");
×
1766
    return OPENMC_E_OUT_OF_BOUNDS;
×
1767
  }
1768
}
1769

1770
//! Return the rotation matrix of a cell
1771
extern "C" int openmc_cell_get_rotation(int32_t index, double rot[], size_t* n)
58✔
1772
{
1773
  if (index >= 0 && index < model::cells.size()) {
58✔
1774
    auto& cell = model::cells[index];
58✔
1775
    *n = cell->rotation_.size();
58✔
1776
    std::memcpy(rot, cell->rotation_.data(), *n * sizeof(cell->rotation_[0]));
58✔
1777
    return 0;
58✔
1778
  } else {
1779
    set_errmsg("Index in cells array is out of bounds.");
×
1780
    return OPENMC_E_OUT_OF_BOUNDS;
×
1781
  }
1782
}
1783

1784
//! Set the flattened rotation matrix of a cell
1785
extern "C" int openmc_cell_set_rotation(
46✔
1786
  int32_t index, const double rot[], size_t rot_len)
1787
{
1788
  if (index >= 0 && index < model::cells.size()) {
46✔
1789
    if (model::cells[index]->fill_ == C_NONE) {
46✔
1790
      set_errmsg(fmt::format("Cannot apply a rotation to cell {}"
12✔
1791
                             " because it is not filled with another universe",
1792
        index));
1793
      return OPENMC_E_GEOMETRY;
12✔
1794
    }
1795
    std::vector<double> vec_rot(rot, rot + rot_len);
34✔
1796
    model::cells[index]->set_rotation(vec_rot);
34✔
1797
    return 0;
34✔
1798
  } else {
34✔
1799
    set_errmsg("Index in cells array is out of bounds.");
×
1800
    return OPENMC_E_OUT_OF_BOUNDS;
×
1801
  }
1802
}
1803

1804
//! Get the number of instances of the requested cell
1805
extern "C" int openmc_cell_get_num_instances(
12✔
1806
  int32_t index, int32_t* num_instances)
1807
{
1808
  if (index < 0 || index >= model::cells.size()) {
12✔
1809
    set_errmsg("Index in cells array is out of bounds.");
×
1810
    return OPENMC_E_OUT_OF_BOUNDS;
×
1811
  }
1812
  *num_instances = model::cells[index]->n_instances();
12✔
1813
  return 0;
12✔
1814
}
1815

1816
//! Extend the cells array by n elements
1817
extern "C" int openmc_extend_cells(
24✔
1818
  int32_t n, int32_t* index_start, int32_t* index_end)
1819
{
1820
  if (index_start)
24✔
1821
    *index_start = model::cells.size();
24✔
1822
  if (index_end)
24✔
1823
    *index_end = model::cells.size() + n - 1;
×
1824
  for (int32_t i = 0; i < n; i++) {
48✔
1825
    model::cells.push_back(make_unique<CSGCell>());
24✔
1826
  }
1827
  return 0;
24✔
1828
}
1829

1830
extern "C" int cells_size()
48✔
1831
{
1832
  return model::cells.size();
48✔
1833
}
1834

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