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

openmc-dev / openmc / 16646681306

31 Jul 2025 10:36AM UTC coverage: 84.927% (-0.2%) from 85.103%
16646681306

Pull #3508

github

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

42 of 215 new or added lines in 5 files covered. (19.53%)

400 existing lines in 32 files now uncovered.

52933 of 62328 relevant lines covered (84.93%)

36043131.63 hits per line

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

94.1
/src/lattice.cpp
1
#include "openmc/lattice.h"
2

3
#include <cmath>
4
#include <string>
5

6
#include <fmt/core.h>
7

8
#include "openmc/cell.h"
9
#include "openmc/error.h"
10
#include "openmc/geometry.h"
11
#include "openmc/geometry_aux.h"
12
#include "openmc/hdf5_interface.h"
13
#include "openmc/string_utils.h"
14
#include "openmc/vector.h"
15
#include "openmc/xml_interface.h"
16

17
namespace openmc {
18

19
//==============================================================================
20
// Global variables
21
//==============================================================================
22

23
namespace model {
24
std::unordered_map<int32_t, int32_t> lattice_map;
25
vector<unique_ptr<Lattice>> lattices;
26
} // namespace model
27

28
//==============================================================================
29
// Lattice implementation
30
//==============================================================================
31

32
Lattice::Lattice(pugi::xml_node lat_node)
1,794✔
33
{
34
  if (check_for_node(lat_node, "id")) {
1,794✔
35
    id_ = std::stoi(get_node_value(lat_node, "id"));
1,794✔
36
  } else {
37
    fatal_error("Must specify id of lattice in geometry XML file.");
×
38
  }
39

40
  if (check_for_node(lat_node, "name")) {
1,794✔
41
    name_ = get_node_value(lat_node, "name");
358✔
42
  }
43

44
  if (check_for_node(lat_node, "outer")) {
1,794✔
45
    outer_ = std::stoi(get_node_value(lat_node, "outer"));
386✔
46
  }
47
}
1,794✔
48

49
//==============================================================================
50

51
LatticeIter Lattice::begin()
289,798,412✔
52
{
53
  return LatticeIter(*this, 0);
289,798,412✔
54
}
55

56
LatticeIter Lattice::end()
48,650,007✔
57
{
58
  return LatticeIter(*this, universes_.size());
48,650,007✔
59
}
60

61
int32_t& Lattice::back()
22✔
62
{
63
  return universes_.back();
22✔
64
}
65

66
ReverseLatticeIter Lattice::rbegin()
1,638,252✔
67
{
68
  return ReverseLatticeIter(*this, universes_.size() - 1);
1,638,252✔
69
}
70

71
ReverseLatticeIter Lattice::rend()
289,047,726✔
72
{
73
  return ReverseLatticeIter(*this, -1);
289,047,726✔
74
}
75

76
//==============================================================================
77

78
void Lattice::adjust_indices()
1,794✔
79
{
80
  // Adjust the indices for the universes array.
81
  for (LatticeIter it = begin(); it != end(); ++it) {
890,687✔
82
    int uid = *it;
888,893✔
83
    auto search = model::universe_map.find(uid);
888,893✔
84
    if (search != model::universe_map.end()) {
888,893✔
85
      *it = search->second;
888,893✔
86
    } else {
87
      fatal_error(fmt::format(
×
88
        "Invalid universe number {} specified on lattice {}", uid, id_));
×
89
    }
90
  }
91

92
  // Adjust the index for the outer universe.
93
  if (outer_ != NO_OUTER_UNIVERSE) {
1,794✔
94
    auto search = model::universe_map.find(outer_);
386✔
95
    if (search != model::universe_map.end()) {
386✔
96
      outer_ = search->second;
386✔
97
    } else {
98
      fatal_error(fmt::format(
×
99
        "Invalid universe number {} specified on lattice {}", outer_, id_));
×
100
    }
101
  }
102
}
1,794✔
103

104
//==============================================================================
105

106
int32_t Lattice::fill_offset_table(int32_t target_univ_id, int map,
12,561✔
107
  std::unordered_map<int32_t, int32_t>& univ_count_memo)
108
{
109
  // If the offsets have already been determined for this "map", don't bother
110
  // recalculating all of them and just return the total offset. Note that the
111
  // offsets_ array doesn't actually include the offset accounting for the last
112
  // universe, so we get the before-last offset for the given map and then
113
  // explicitly add the count for the last universe.
114
  if (offsets_[map * universes_.size() + this->begin().indx_] != C_NONE) {
12,561✔
115
    int last_offset =
116
      offsets_[(map + 1) * universes_.size() - this->begin().indx_ - 1];
33✔
117
    int last_univ = this->back();
33✔
118
    return last_offset +
119
           count_universe_instances(last_univ, target_univ_id, univ_count_memo);
33✔
120
  }
121

122
  int32_t offset = 0;
12,528✔
123
  for (LatticeIter it = begin(); it != end(); ++it) {
9,183,993✔
124
    offsets_[map * universes_.size() + it.indx_] = offset;
9,171,465✔
125
    offset += count_universe_instances(*it, target_univ_id, univ_count_memo);
9,171,465✔
126
  }
127

128
  return offset;
12,528✔
129
}
130

131
//==============================================================================
132

133
void Lattice::to_hdf5(hid_t lattices_group) const
1,282✔
134
{
135
  // Make a group for the lattice.
136
  std::string group_name {"lattice "};
1,282✔
137
  group_name += std::to_string(id_);
1,282✔
138
  hid_t lat_group = create_group(lattices_group, group_name);
1,282✔
139

140
  // Write the name and outer universe.
141
  if (!name_.empty()) {
1,282✔
142
    write_string(lat_group, "name", name_, false);
242✔
143
  }
144

145
  if (outer_ != NO_OUTER_UNIVERSE) {
1,282✔
146
    int32_t outer_id = model::universes[outer_]->id_;
265✔
147
    write_dataset(lat_group, "outer", outer_id);
265✔
148
  } else {
149
    write_dataset(lat_group, "outer", outer_);
1,017✔
150
  }
151

152
  // Call subclass-overriden function to fill in other details.
153
  to_hdf5_inner(lat_group);
1,282✔
154

155
  close_group(lat_group);
1,282✔
156
}
1,282✔
157

158
//==============================================================================
159
// RectLattice implementation
160
//==============================================================================
161

162
RectLattice::RectLattice(pugi::xml_node lat_node) : Lattice {lat_node}
1,703✔
163
{
164
  type_ = LatticeType::rect;
1,703✔
165

166
  // Read the number of lattice cells in each dimension.
167
  std::string dimension_str {get_node_value(lat_node, "dimension")};
1,703✔
168
  vector<std::string> dimension_words {split(dimension_str)};
1,703✔
169
  if (dimension_words.size() == 2) {
1,703✔
170
    n_cells_[0] = std::stoi(dimension_words[0]);
1,279✔
171
    n_cells_[1] = std::stoi(dimension_words[1]);
1,279✔
172
    n_cells_[2] = 1;
1,279✔
173
    is_3d_ = false;
1,279✔
174
  } else if (dimension_words.size() == 3) {
424✔
175
    n_cells_[0] = std::stoi(dimension_words[0]);
424✔
176
    n_cells_[1] = std::stoi(dimension_words[1]);
424✔
177
    n_cells_[2] = std::stoi(dimension_words[2]);
424✔
178
    is_3d_ = true;
424✔
179
  } else {
UNCOV
180
    fatal_error("Rectangular lattice must be two or three dimensions.");
×
181
  }
182

183
  // Read the lattice lower-left location.
184
  std::string ll_str {get_node_value(lat_node, "lower_left")};
1,703✔
185
  vector<std::string> ll_words {split(ll_str)};
1,703✔
186
  if (ll_words.size() != dimension_words.size()) {
1,703✔
UNCOV
187
    fatal_error("Number of entries on <lower_left> must be the same as the "
×
188
                "number of entries on <dimension>.");
189
  }
190
  lower_left_[0] = stod(ll_words[0]);
1,703✔
191
  lower_left_[1] = stod(ll_words[1]);
1,703✔
192
  if (is_3d_) {
1,703✔
193
    lower_left_[2] = stod(ll_words[2]);
424✔
194
  }
195

196
  // Read the lattice pitches.
197
  std::string pitch_str {get_node_value(lat_node, "pitch")};
1,703✔
198
  vector<std::string> pitch_words {split(pitch_str)};
1,703✔
199
  if (pitch_words.size() != dimension_words.size()) {
1,703✔
UNCOV
200
    fatal_error("Number of entries on <pitch> must be the same as the "
×
201
                "number of entries on <dimension>.");
202
  }
203
  pitch_[0] = stod(pitch_words[0]);
1,703✔
204
  pitch_[1] = stod(pitch_words[1]);
1,703✔
205
  if (is_3d_) {
1,703✔
206
    pitch_[2] = stod(pitch_words[2]);
424✔
207
  }
208

209
  // Read the universes and make sure the correct number was specified.
210
  std::string univ_str {get_node_value(lat_node, "universes")};
1,703✔
211
  vector<std::string> univ_words {split(univ_str)};
1,703✔
212
  if (univ_words.size() != n_cells_[0] * n_cells_[1] * n_cells_[2]) {
1,703✔
UNCOV
213
    fatal_error(fmt::format(
×
214
      "Expected {} universes for a rectangular lattice of size {}x{}x{} but {} "
215
      "were specified.",
216
      n_cells_[0] * n_cells_[1] * n_cells_[2], n_cells_[0], n_cells_[1],
×
UNCOV
217
      n_cells_[2], univ_words.size()));
×
218
  }
219

220
  // Parse the universes.
221
  universes_.resize(n_cells_[0] * n_cells_[1] * n_cells_[2], C_NONE);
1,703✔
222
  for (int iz = 0; iz < n_cells_[2]; iz++) {
7,718✔
223
    for (int iy = n_cells_[1] - 1; iy > -1; iy--) {
74,097✔
224
      for (int ix = 0; ix < n_cells_[0]; ix++) {
929,890✔
225
        int indx1 = n_cells_[0] * n_cells_[1] * iz +
861,808✔
226
                    n_cells_[0] * (n_cells_[1] - iy - 1) + ix;
861,808✔
227
        int indx2 = n_cells_[0] * n_cells_[1] * iz + n_cells_[0] * iy + ix;
861,808✔
228
        universes_[indx1] = std::stoi(univ_words[indx2]);
861,808✔
229
      }
230
    }
231
  }
232
}
1,703✔
233

234
//==============================================================================
235

236
const int32_t& RectLattice::operator[](const array<int, 3>& i_xyz)
766,708,744✔
237
{
238
  return universes_[get_flat_index(i_xyz)];
766,708,744✔
239
}
240

241
//==============================================================================
242

243
bool RectLattice::are_valid_indices(const array<int, 3>& i_xyz) const
1,806,958,398✔
244
{
245
  return ((i_xyz[0] >= 0) && (i_xyz[0] < n_cells_[0]) && (i_xyz[1] >= 0) &&
2,147,483,647✔
246
          (i_xyz[1] < n_cells_[1]) && (i_xyz[2] >= 0) &&
2,147,483,647✔
247
          (i_xyz[2] < n_cells_[2]));
2,147,483,647✔
248
}
249

250
//==============================================================================
251

252
std::pair<double, array<int, 3>> RectLattice::distance(
1,053,216,565✔
253
  Position r, Direction u, const array<int, 3>& i_xyz) const
254
{
255
  // Get short aliases to the coordinates.
256
  double x = r.x;
1,053,216,565✔
257
  double y = r.y;
1,053,216,565✔
258
  double z = r.z;
1,053,216,565✔
259

260
  // Determine the oncoming edge.
261
  double x0 {copysign(0.5 * pitch_[0], u.x)};
1,053,216,565✔
262
  double y0 {copysign(0.5 * pitch_[1], u.y)};
1,053,216,565✔
263

264
  // Left and right sides
265
  double d {INFTY};
1,053,216,565✔
266
  array<int, 3> lattice_trans;
267
  if ((std::abs(x - x0) > FP_PRECISION) && u.x != 0) {
1,053,216,565✔
268
    d = (x0 - x) / u.x;
1,053,216,565✔
269
    if (u.x > 0) {
1,053,216,565✔
270
      lattice_trans = {1, 0, 0};
536,723,745✔
271
    } else {
272
      lattice_trans = {-1, 0, 0};
516,492,820✔
273
    }
274
  }
275

276
  // Front and back sides
277
  if ((std::abs(y - y0) > FP_PRECISION) && u.y != 0) {
1,053,216,565✔
278
    double this_d = (y0 - y) / u.y;
1,053,216,565✔
279
    if (this_d < d) {
1,053,216,565✔
280
      d = this_d;
522,521,708✔
281
      if (u.y > 0) {
522,521,708✔
282
        lattice_trans = {0, 1, 0};
266,303,171✔
283
      } else {
284
        lattice_trans = {0, -1, 0};
256,218,537✔
285
      }
286
    }
287
  }
288

289
  // Top and bottom sides
290
  if (is_3d_) {
1,053,216,565✔
291
    double z0 {copysign(0.5 * pitch_[2], u.z)};
632,481,522✔
292
    if ((std::abs(z - z0) > FP_PRECISION) && u.z != 0) {
632,481,522✔
293
      double this_d = (z0 - z) / u.z;
632,481,522✔
294
      if (this_d < d) {
632,481,522✔
295
        d = this_d;
207,729,629✔
296
        if (u.z > 0) {
207,729,629✔
297
          lattice_trans = {0, 0, 1};
106,978,502✔
298
        } else {
299
          lattice_trans = {0, 0, -1};
100,751,127✔
300
        }
301
      }
302
    }
303
  }
304

305
  return {d, lattice_trans};
2,106,433,130✔
306
}
307

308
//==============================================================================
309

310
void RectLattice::get_indices(
113,818,810✔
311
  Position r, Direction u, array<int, 3>& result) const
312
{
313
  // Determine x index, accounting for coincidence
314
  double ix_ {(r.x - lower_left_.x) / pitch_.x};
113,818,810✔
315
  long ix_close {std::lround(ix_)};
113,818,810✔
316
  if (coincident(ix_, ix_close)) {
113,818,810✔
317
    result[0] = (u.x > 0) ? ix_close : ix_close - 1;
35,068,409✔
318
  } else {
319
    result[0] = std::floor(ix_);
78,750,401✔
320
  }
321

322
  // Determine y index, accounting for coincidence
323
  double iy_ {(r.y - lower_left_.y) / pitch_.y};
113,818,810✔
324
  long iy_close {std::lround(iy_)};
113,818,810✔
325
  if (coincident(iy_, iy_close)) {
113,818,810✔
326
    result[1] = (u.y > 0) ? iy_close : iy_close - 1;
35,485,859✔
327
  } else {
328
    result[1] = std::floor(iy_);
78,332,951✔
329
  }
330

331
  // Determine z index, accounting for coincidence
332
  result[2] = 0;
113,818,810✔
333
  if (is_3d_) {
113,818,810✔
334
    double iz_ {(r.z - lower_left_.z) / pitch_.z};
65,112,754✔
335
    long iz_close {std::lround(iz_)};
65,112,754✔
336
    if (coincident(iz_, iz_close)) {
65,112,754✔
337
      result[2] = (u.z > 0) ? iz_close : iz_close - 1;
17,634,058✔
338
    } else {
339
      result[2] = std::floor(iz_);
47,478,696✔
340
    }
341
  }
342
}
113,818,810✔
343

344
int RectLattice::get_flat_index(const array<int, 3>& i_xyz) const
766,708,744✔
345
{
346
  return n_cells_[0] * n_cells_[1] * i_xyz[2] + n_cells_[0] * i_xyz[1] +
766,708,744✔
347
         i_xyz[0];
766,708,744✔
348
}
349

350
//==============================================================================
351

352
Position RectLattice::get_local_position(
770,941,632✔
353
  Position r, const array<int, 3>& i_xyz) const
354
{
355
  r.x -= (lower_left_.x + (i_xyz[0] + 0.5) * pitch_.x);
770,941,632✔
356
  r.y -= (lower_left_.y + (i_xyz[1] + 0.5) * pitch_.y);
770,941,632✔
357
  if (is_3d_) {
770,941,632✔
358
    r.z -= (lower_left_.z + (i_xyz[2] + 0.5) * pitch_.z);
628,521,149✔
359
  }
360
  return r;
770,941,632✔
361
}
362

363
//==============================================================================
364

365
int32_t& RectLattice::offset(int map, const array<int, 3>& i_xyz)
1,033,431,359✔
366
{
367
  return offsets_[n_cells_[0] * n_cells_[1] * n_cells_[2] * map +
1,033,431,359✔
368
                  n_cells_[0] * n_cells_[1] * i_xyz[2] +
1,033,431,359✔
369
                  n_cells_[0] * i_xyz[1] + i_xyz[0]];
2,066,862,718✔
370
}
371

372
//==============================================================================
373

374
int32_t RectLattice::offset(int map, int indx) const
88,752✔
375
{
376
  return offsets_[n_cells_[0] * n_cells_[1] * n_cells_[2] * map + indx];
88,752✔
377
}
378

379
//==============================================================================
380

381
std::string RectLattice::index_to_string(int indx) const
1,638,252✔
382
{
383
  int iz {indx / (n_cells_[0] * n_cells_[1])};
1,638,252✔
384
  int iy {(indx - n_cells_[0] * n_cells_[1] * iz) / n_cells_[0]};
1,638,252✔
385
  int ix {indx - n_cells_[0] * n_cells_[1] * iz - n_cells_[0] * iy};
1,638,252✔
386
  std::string out {std::to_string(ix)};
1,638,252✔
387
  out += ',';
1,638,252✔
388
  out += std::to_string(iy);
1,638,252✔
389
  if (is_3d_) {
1,638,252✔
390
    out += ',';
×
UNCOV
391
    out += std::to_string(iz);
×
392
  }
393
  return out;
1,638,252✔
UNCOV
394
}
×
395

396
//==============================================================================
397

398
void RectLattice::to_hdf5_inner(hid_t lat_group) const
1,227✔
399
{
400
  // Write basic lattice information.
401
  write_string(lat_group, "type", "rectangular", false);
1,227✔
402
  if (is_3d_) {
1,227✔
403
    write_dataset(lat_group, "pitch", pitch_);
304✔
404
    write_dataset(lat_group, "lower_left", lower_left_);
304✔
405
    write_dataset(lat_group, "dimension", n_cells_);
304✔
406
  } else {
407
    array<double, 2> pitch_short {{pitch_[0], pitch_[1]}};
923✔
408
    write_dataset(lat_group, "pitch", pitch_short);
923✔
409
    array<double, 2> ll_short {{lower_left_[0], lower_left_[1]}};
923✔
410
    write_dataset(lat_group, "lower_left", ll_short);
923✔
411
    array<int, 2> nc_short {{n_cells_[0], n_cells_[1]}};
923✔
412
    write_dataset(lat_group, "dimension", nc_short);
923✔
413
  }
414

415
  // Write the universe ids.  The convention here is to switch the ordering on
416
  // the y-axis to match the way universes are input in a text file.
417
  if (is_3d_) {
1,227✔
418
    hsize_t nx {static_cast<hsize_t>(n_cells_[0])};
304✔
419
    hsize_t ny {static_cast<hsize_t>(n_cells_[1])};
304✔
420
    hsize_t nz {static_cast<hsize_t>(n_cells_[2])};
304✔
421
    vector<int> out(nx * ny * nz);
304✔
422

423
    for (int m = 0; m < nz; m++) {
3,665✔
424
      for (int k = 0; k < ny; k++) {
42,792✔
425
        for (int j = 0; j < nx; j++) {
510,952✔
426
          int indx1 = nx * ny * m + nx * k + j;
471,521✔
427
          int indx2 = nx * ny * m + nx * (ny - k - 1) + j;
471,521✔
428
          out[indx2] = model::universes[universes_[indx1]]->id_;
471,521✔
429
        }
430
      }
431
    }
432

433
    hsize_t dims[3] {nz, ny, nx};
304✔
434
    write_int(lat_group, 3, dims, "universes", out.data(), false);
304✔
435

436
  } else {
304✔
437
    hsize_t nx {static_cast<hsize_t>(n_cells_[0])};
923✔
438
    hsize_t ny {static_cast<hsize_t>(n_cells_[1])};
923✔
439
    vector<int> out(nx * ny);
923✔
440

441
    for (int k = 0; k < ny; k++) {
9,677✔
442
      for (int j = 0; j < nx; j++) {
146,712✔
443
        int indx1 = nx * k + j;
137,958✔
444
        int indx2 = nx * (ny - k - 1) + j;
137,958✔
445
        out[indx2] = model::universes[universes_[indx1]]->id_;
137,958✔
446
      }
447
    }
448

449
    hsize_t dims[3] {1, ny, nx};
923✔
450
    write_int(lat_group, 3, dims, "universes", out.data(), false);
923✔
451
  }
923✔
452
}
1,227✔
453

454
//==============================================================================
455
// HexLattice implementation
456
//==============================================================================
457

458
HexLattice::HexLattice(pugi::xml_node lat_node) : Lattice {lat_node}
91✔
459
{
460
  type_ = LatticeType::hex;
91✔
461

462
  // Read the number of lattice cells in each dimension.
463
  n_rings_ = std::stoi(get_node_value(lat_node, "n_rings"));
91✔
464
  if (check_for_node(lat_node, "n_axial")) {
91✔
465
    n_axial_ = std::stoi(get_node_value(lat_node, "n_axial"));
32✔
466
    is_3d_ = true;
32✔
467
  } else {
468
    n_axial_ = 1;
59✔
469
    is_3d_ = false;
59✔
470
  }
471

472
  // Read the lattice orientation.  Default to 'y'.
473
  if (check_for_node(lat_node, "orientation")) {
91✔
474
    std::string orientation = get_node_value(lat_node, "orientation");
16✔
475
    if (orientation == "y") {
16✔
UNCOV
476
      orientation_ = Orientation::y;
×
477
    } else if (orientation == "x") {
16✔
478
      orientation_ = Orientation::x;
16✔
479
    } else {
480
      fatal_error("Unrecognized orientation '" + orientation +
×
UNCOV
481
                  "' for lattice " + std::to_string(id_));
×
482
    }
483
  } else {
16✔
484
    orientation_ = Orientation::y;
75✔
485
  }
486

487
  // Read the lattice center.
488
  std::string center_str {get_node_value(lat_node, "center")};
91✔
489
  vector<std::string> center_words {split(center_str)};
91✔
490
  if (is_3d_ && (center_words.size() != 3)) {
91✔
UNCOV
491
    fatal_error("A hexagonal lattice with <n_axial> must have <center> "
×
492
                "specified by 3 numbers.");
493
  } else if (!is_3d_ && center_words.size() != 2) {
91✔
UNCOV
494
    fatal_error("A hexagonal lattice without <n_axial> must have <center> "
×
495
                "specified by 2 numbers.");
496
  }
497
  center_[0] = stod(center_words[0]);
91✔
498
  center_[1] = stod(center_words[1]);
91✔
499
  if (is_3d_) {
91✔
500
    center_[2] = stod(center_words[2]);
32✔
501
  }
502

503
  // Read the lattice pitches.
504
  std::string pitch_str {get_node_value(lat_node, "pitch")};
91✔
505
  vector<std::string> pitch_words {split(pitch_str)};
91✔
506
  if (is_3d_ && (pitch_words.size() != 2)) {
91✔
UNCOV
507
    fatal_error("A hexagonal lattice with <n_axial> must have <pitch> "
×
508
                "specified by 2 numbers.");
509
  } else if (!is_3d_ && (pitch_words.size() != 1)) {
91✔
UNCOV
510
    fatal_error("A hexagonal lattice without <n_axial> must have <pitch> "
×
511
                "specified by 1 number.");
512
  }
513
  pitch_[0] = stod(pitch_words[0]);
91✔
514
  if (is_3d_) {
91✔
515
    pitch_[1] = stod(pitch_words[1]);
32✔
516
  }
517

518
  // Read the universes and make sure the correct number was specified.
519
  int n_univ = (3 * n_rings_ * n_rings_ - 3 * n_rings_ + 1) * n_axial_;
91✔
520
  std::string univ_str {get_node_value(lat_node, "universes")};
91✔
521
  vector<std::string> univ_words {split(univ_str)};
91✔
522
  if (univ_words.size() != n_univ) {
91✔
UNCOV
523
    fatal_error(fmt::format(
×
524
      "Expected {} universes for a hexagonal lattice with {} rings and {} "
525
      "axial levels but {} were specified.",
UNCOV
526
      n_univ, n_rings_, n_axial_, univ_words.size()));
×
527
  }
528

529
  // Parse the universes.
530
  // Universes in hexagonal lattices are stored in a manner that represents
531
  // a skewed coordinate system: (x, alpha) in case of 'y' orientation
532
  // and (alpha,y) in 'x' one rather than (x, y).  There is
533
  // no obvious, direct relationship between the order of universes in the
534
  // input and the order that they will be stored in the skewed array so
535
  // the following code walks a set of index values across the skewed array
536
  // in a manner that matches the input order.  Note that i_x = 0, i_a = 0
537
  // or i_a = 0, i_y = 0 corresponds to the center of the hexagonal lattice.
538
  universes_.resize((2 * n_rings_ - 1) * (2 * n_rings_ - 1) * n_axial_, C_NONE);
91✔
539
  if (orientation_ == Orientation::y) {
91✔
540
    fill_lattice_y(univ_words);
75✔
541
  } else {
542
    fill_lattice_x(univ_words);
16✔
543
  }
544
}
91✔
545

546
//==============================================================================
547

548
void HexLattice::fill_lattice_x(const vector<std::string>& univ_words)
16✔
549
{
550
  int input_index = 0;
16✔
551
  for (int m = 0; m < n_axial_; m++) {
48✔
552
    // Initialize lattice indecies.
553
    int i_a = -(n_rings_ - 1);
32✔
554
    int i_y = n_rings_ - 1;
32✔
555

556
    // Map upper region of hexagonal lattice which is found in the
557
    // first n_rings-1 rows of the input.
558
    for (int k = 0; k < n_rings_ - 1; k++) {
352✔
559

560
      // Iterate over the input columns.
561
      for (int j = 0; j < k + n_rings_; j++) {
5,280✔
562
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
4,960✔
563
                   (2 * n_rings_ - 1) * (i_y + n_rings_ - 1) +
4,960✔
564
                   (i_a + n_rings_ - 1);
4,960✔
565
        universes_[indx] = std::stoi(univ_words[input_index]);
4,960✔
566
        input_index++;
4,960✔
567
        // Move to the next right neighbour cell
568
        i_a += 1;
4,960✔
569
      }
570

571
      // Return the lattice index to the start of the current row.
572
      i_a = -(n_rings_ - 1);
320✔
573
      i_y -= 1;
320✔
574
    }
575

576
    // Map the lower region from the centerline of cart to down side
577
    for (int k = 0; k < n_rings_; k++) {
384✔
578
      // Walk the index to the lower-right neighbor of the last row start.
579
      i_a = -(n_rings_ - 1) + k;
352✔
580

581
      // Iterate over the input columns.
582
      for (int j = 0; j < 2 * n_rings_ - k - 1; j++) {
5,984✔
583
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
5,632✔
584
                   (2 * n_rings_ - 1) * (i_y + n_rings_ - 1) +
5,632✔
585
                   (i_a + n_rings_ - 1);
5,632✔
586
        universes_[indx] = std::stoi(univ_words[input_index]);
5,632✔
587
        input_index++;
5,632✔
588
        // Move to the next right neighbour cell
589
        i_a += 1;
5,632✔
590
      }
591

592
      // Return lattice index to start of current row.
593
      i_y -= 1;
352✔
594
    }
595
  }
596
}
16✔
597

598
//==============================================================================
599

600
void HexLattice::fill_lattice_y(const vector<std::string>& univ_words)
75✔
601
{
602
  int input_index = 0;
75✔
603
  for (int m = 0; m < n_axial_; m++) {
182✔
604
    // Initialize lattice indecies.
605
    int i_x = 1;
107✔
606
    int i_a = n_rings_ - 1;
107✔
607

608
    // Map upper triangular region of hexagonal lattice which is found in the
609
    // first n_rings-1 rows of the input.
610
    for (int k = 0; k < n_rings_ - 1; k++) {
662✔
611
      // Walk the index to lower-left neighbor of last row start.
612
      i_x -= 1;
555✔
613

614
      // Iterate over the input columns.
615
      for (int j = 0; j < k + 1; j++) {
3,286✔
616
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
2,731✔
617
                   (2 * n_rings_ - 1) * (i_a + n_rings_ - 1) +
2,731✔
618
                   (i_x + n_rings_ - 1);
2,731✔
619
        universes_[indx] = std::stoi(univ_words[input_index]);
2,731✔
620
        input_index++;
2,731✔
621
        // Walk the index to the right neighbor (which is not adjacent).
622
        i_x += 2;
2,731✔
623
        i_a -= 1;
2,731✔
624
      }
625

626
      // Return the lattice index to the start of the current row.
627
      i_x -= 2 * (k + 1);
555✔
628
      i_a += (k + 1);
555✔
629
    }
630

631
    // Map the middle square region of the hexagonal lattice which is found in
632
    // the next 2*n_rings-1 rows of the input.
633
    for (int k = 0; k < 2 * n_rings_ - 1; k++) {
1,324✔
634
      if ((k % 2) == 0) {
1,217✔
635
        // Walk the index to the lower-left neighbor of the last row start.
636
        i_x -= 1;
662✔
637
      } else {
638
        // Walk the index to the lower-right neighbor of the last row start.
639
        i_x += 1;
555✔
640
        i_a -= 1;
555✔
641
      }
642

643
      // Iterate over the input columns.
644
      for (int j = 0; j < n_rings_ - (k % 2); j++) {
12,248✔
645
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
11,031✔
646
                   (2 * n_rings_ - 1) * (i_a + n_rings_ - 1) +
11,031✔
647
                   (i_x + n_rings_ - 1);
11,031✔
648
        universes_[indx] = std::stoi(univ_words[input_index]);
11,031✔
649
        input_index++;
11,031✔
650
        // Walk the index to the right neighbor (which is not adjacent).
651
        i_x += 2;
11,031✔
652
        i_a -= 1;
11,031✔
653
      }
654

655
      // Return the lattice index to the start of the current row.
656
      i_x -= 2 * (n_rings_ - (k % 2));
1,217✔
657
      i_a += n_rings_ - (k % 2);
1,217✔
658
    }
659

660
    // Map the lower triangular region of the hexagonal lattice.
661
    for (int k = 0; k < n_rings_ - 1; k++) {
662✔
662
      // Walk the index to the lower-right neighbor of the last row start.
663
      i_x += 1;
555✔
664
      i_a -= 1;
555✔
665

666
      // Iterate over the input columns.
667
      for (int j = 0; j < n_rings_ - k - 1; j++) {
3,286✔
668
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
2,731✔
669
                   (2 * n_rings_ - 1) * (i_a + n_rings_ - 1) +
2,731✔
670
                   (i_x + n_rings_ - 1);
2,731✔
671
        universes_[indx] = std::stoi(univ_words[input_index]);
2,731✔
672
        input_index++;
2,731✔
673
        // Walk the index to the right neighbor (which is not adjacent).
674
        i_x += 2;
2,731✔
675
        i_a -= 1;
2,731✔
676
      }
677

678
      // Return lattice index to start of current row.
679
      i_x -= 2 * (n_rings_ - k - 1);
555✔
680
      i_a += n_rings_ - k - 1;
555✔
681
    }
682
  }
683
}
75✔
684

685
//==============================================================================
686

687
const int32_t& HexLattice::operator[](const array<int, 3>& i_xyz)
29,393,276✔
688
{
689
  return universes_[get_flat_index(i_xyz)];
29,393,276✔
690
}
691

692
//==============================================================================
693

694
// The HexLattice iterators need their own versions b/c the universes array is
695
// "square", meaning that it is allocated with entries that are intentionally
696
// left empty. As such, the iterator indices need to skip the empty entries to
697
// get cell instances and geometry paths correct. See the image in the Theory
698
// and Methodology section on "Hexagonal Lattice Indexing" for a visual of where
699
// the empty positions are.
700
LatticeIter HexLattice::begin()
2,151✔
701
{
702
  return LatticeIter(*this, n_rings_ - 1);
2,151✔
703
}
704

705
ReverseLatticeIter HexLattice::rbegin()
231✔
706
{
707
  return ReverseLatticeIter(*this, universes_.size() - n_rings_);
231✔
708
}
709

710
int32_t& HexLattice::back()
11✔
711
{
712
  return universes_[universes_.size() - n_rings_];
11✔
713
}
714

715
LatticeIter HexLattice::end()
685,917✔
716
{
717
  return LatticeIter(*this, universes_.size() - n_rings_ + 1);
685,917✔
718
}
719

720
ReverseLatticeIter HexLattice::rend()
924✔
721
{
722
  return ReverseLatticeIter(*this, n_rings_ - 2);
924✔
723
}
724

725
//==============================================================================
726

727
bool HexLattice::are_valid_indices(const array<int, 3>& i_xyz) const
114,002,816✔
728
{
729
  // Check if (x, alpha, z) indices are valid, accounting for number of rings
730
  return ((i_xyz[0] >= 0) && (i_xyz[1] >= 0) && (i_xyz[2] >= 0) &&
227,464,256✔
731
          (i_xyz[0] < 2 * n_rings_ - 1) && (i_xyz[1] < 2 * n_rings_ - 1) &&
110,710,703✔
732
          (i_xyz[0] + i_xyz[1] > n_rings_ - 2) &&
109,932,552✔
733
          (i_xyz[0] + i_xyz[1] < 3 * n_rings_ - 2) && (i_xyz[2] < n_axial_));
227,464,256✔
734
}
735

736
//==============================================================================
737

738
std::pair<double, array<int, 3>> HexLattice::distance(
85,718,490✔
739
  Position r, Direction u, const array<int, 3>& i_xyz) const
740
{
741
  // Short description of the direction vectors used here.  The beta, gamma, and
742
  // delta vectors point towards the flat sides of each hexagonal tile.
743
  // Y - orientation:
744
  //   basis0 = (1, 0)
745
  //   basis1 = (-1/sqrt(3), 1)   = +120 degrees from basis0
746
  //   beta   = (sqrt(3)/2, 1/2)  = +30 degrees from basis0
747
  //   gamma  = (sqrt(3)/2, -1/2) = -60 degrees from beta
748
  //   delta  = (0, 1)            = +60 degrees from beta
749
  // X - orientation:
750
  //   basis0 = (1/sqrt(3), -1)
751
  //   basis1 = (0, 1)            = +120 degrees from basis0
752
  //   beta   = (1, 0)            = +30 degrees from basis0
753
  //   gamma  = (1/2, -sqrt(3)/2) = -60 degrees from beta
754
  //   delta  = (1/2, sqrt(3)/2)  = +60 degrees from beta
755
  // The z-axis is considered separately.
756
  double beta_dir;
757
  double gamma_dir;
758
  double delta_dir;
759
  if (orientation_ == Orientation::y) {
85,718,490✔
760
    beta_dir = u.x * std::sqrt(3.0) / 2.0 + u.y / 2.0;
71,249,211✔
761
    gamma_dir = u.x * std::sqrt(3.0) / 2.0 - u.y / 2.0;
71,249,211✔
762
    delta_dir = u.y;
71,249,211✔
763
  } else {
764
    beta_dir = u.x;
14,469,279✔
765
    gamma_dir = u.x / 2.0 - u.y * std::sqrt(3.0) / 2.0;
14,469,279✔
766
    delta_dir = u.x / 2.0 + u.y * std::sqrt(3.0) / 2.0;
14,469,279✔
767
  }
768

769
  // Note that hexagonal lattice distance calculations are performed
770
  // using the particle's coordinates relative to the neighbor lattice
771
  // cells, not relative to the particle's current cell.  This is done
772
  // because there is significant disagreement between neighboring cells
773
  // on where the lattice boundary is due to finite precision issues.
774

775
  // beta direction
776
  double d {INFTY};
85,718,490✔
777
  array<int, 3> lattice_trans;
778
  double edge = -copysign(0.5 * pitch_[0], beta_dir); // Oncoming edge
85,718,490✔
779
  Position r_t;
85,718,490✔
780
  if (beta_dir > 0) {
85,718,490✔
781
    const array<int, 3> i_xyz_t {i_xyz[0] + 1, i_xyz[1], i_xyz[2]};
42,898,680✔
782
    r_t = get_local_position(r, i_xyz_t);
42,898,680✔
783
  } else {
784
    const array<int, 3> i_xyz_t {i_xyz[0] - 1, i_xyz[1], i_xyz[2]};
42,819,810✔
785
    r_t = get_local_position(r, i_xyz_t);
42,819,810✔
786
  }
787
  double beta;
788
  if (orientation_ == Orientation::y) {
85,718,490✔
789
    beta = r_t.x * std::sqrt(3.0) / 2.0 + r_t.y / 2.0;
71,249,211✔
790
  } else {
791
    beta = r_t.x;
14,469,279✔
792
  }
793
  if ((std::abs(beta - edge) > FP_PRECISION) && beta_dir != 0) {
85,718,490✔
794
    d = (edge - beta) / beta_dir;
85,718,490✔
795
    if (beta_dir > 0) {
85,718,490✔
796
      lattice_trans = {1, 0, 0};
42,898,680✔
797
    } else {
798
      lattice_trans = {-1, 0, 0};
42,819,810✔
799
    }
800
  }
801

802
  // gamma direction
803
  edge = -copysign(0.5 * pitch_[0], gamma_dir);
85,718,490✔
804
  if (gamma_dir > 0) {
85,718,490✔
805
    const array<int, 3> i_xyz_t {i_xyz[0] + 1, i_xyz[1] - 1, i_xyz[2]};
42,875,382✔
806
    r_t = get_local_position(r, i_xyz_t);
42,875,382✔
807
  } else {
808
    const array<int, 3> i_xyz_t {i_xyz[0] - 1, i_xyz[1] + 1, i_xyz[2]};
42,843,108✔
809
    r_t = get_local_position(r, i_xyz_t);
42,843,108✔
810
  }
811
  double gamma;
812
  if (orientation_ == Orientation::y) {
85,718,490✔
813
    gamma = r_t.x * std::sqrt(3.0) / 2.0 - r_t.y / 2.0;
71,249,211✔
814
  } else {
815
    gamma = r_t.x / 2.0 - r_t.y * std::sqrt(3.0) / 2.0;
14,469,279✔
816
  }
817
  if ((std::abs(gamma - edge) > FP_PRECISION) && gamma_dir != 0) {
85,718,490✔
818
    double this_d = (edge - gamma) / gamma_dir;
85,718,490✔
819
    if (this_d < d) {
85,718,490✔
820
      if (gamma_dir > 0) {
42,886,041✔
821
        lattice_trans = {1, -1, 0};
21,467,996✔
822
      } else {
823
        lattice_trans = {-1, 1, 0};
21,418,045✔
824
      }
825
      d = this_d;
42,886,041✔
826
    }
827
  }
828

829
  // delta direction
830
  edge = -copysign(0.5 * pitch_[0], delta_dir);
85,718,490✔
831
  if (delta_dir > 0) {
85,718,490✔
832
    const array<int, 3> i_xyz_t {i_xyz[0], i_xyz[1] + 1, i_xyz[2]};
42,841,645✔
833
    r_t = get_local_position(r, i_xyz_t);
42,841,645✔
834
  } else {
835
    const array<int, 3> i_xyz_t {i_xyz[0], i_xyz[1] - 1, i_xyz[2]};
42,876,845✔
836
    r_t = get_local_position(r, i_xyz_t);
42,876,845✔
837
  }
838
  double delta;
839
  if (orientation_ == Orientation::y) {
85,718,490✔
840
    delta = r_t.y;
71,249,211✔
841
  } else {
842
    delta = r_t.x / 2.0 + r_t.y * std::sqrt(3.0) / 2.0;
14,469,279✔
843
  }
844
  if ((std::abs(delta - edge) > FP_PRECISION) && delta_dir != 0) {
85,718,490✔
845
    double this_d = (edge - delta) / delta_dir;
85,718,490✔
846
    if (this_d < d) {
85,718,490✔
847
      if (delta_dir > 0) {
28,611,253✔
848
        lattice_trans = {0, 1, 0};
14,299,769✔
849
      } else {
850
        lattice_trans = {0, -1, 0};
14,311,484✔
851
      }
852
      d = this_d;
28,611,253✔
853
    }
854
  }
855

856
  // Top and bottom sides
857
  if (is_3d_) {
85,718,490✔
858
    double z = r.z;
20,079,686✔
859
    double z0 {copysign(0.5 * pitch_[1], u.z)};
20,079,686✔
860
    if ((std::abs(z - z0) > FP_PRECISION) && u.z != 0) {
20,079,686✔
861
      double this_d = (z0 - z) / u.z;
20,079,686✔
862
      if (this_d < d) {
20,079,686✔
863
        d = this_d;
3,089,537✔
864
        if (u.z > 0) {
3,089,537✔
865
          lattice_trans = {0, 0, 1};
1,543,773✔
866
        } else {
867
          lattice_trans = {0, 0, -1};
1,545,764✔
868
        }
869
        d = this_d;
3,089,537✔
870
      }
871
    }
872
  }
873

874
  return {d, lattice_trans};
171,436,980✔
875
}
876

877
//==============================================================================
878

879
void HexLattice::get_indices(
11,946,770✔
880
  Position r, Direction u, array<int, 3>& result) const
881
{
882
  // Offset the xyz by the lattice center.
883
  Position r_o {r.x - center_.x, r.y - center_.y, r.z};
11,946,770✔
884
  if (is_3d_) {
11,946,770✔
885
    r_o.z -= center_.z;
2,495,438✔
886
  }
887

888
  // Index the z direction, accounting for coincidence
889
  result[2] = 0;
11,946,770✔
890
  if (is_3d_) {
11,946,770✔
891
    double iz_ {r_o.z / pitch_[1] + 0.5 * n_axial_};
2,495,438✔
892
    long iz_close {std::lround(iz_)};
2,495,438✔
893
    if (coincident(iz_, iz_close)) {
2,495,438✔
894
      result[2] = (u.z > 0) ? iz_close : iz_close - 1;
774,026✔
895
    } else {
896
      result[2] = std::floor(iz_);
1,721,412✔
897
    }
898
  }
899

900
  if (orientation_ == Orientation::y) {
11,946,770✔
901
    // Convert coordinates into skewed bases.  The (x, alpha) basis is used to
902
    // find the index of the global coordinates to within 4 cells.
903
    double alpha = r_o.y - r_o.x / std::sqrt(3.0);
11,130,537✔
904
    result[0] = std::floor(r_o.x / (0.5 * std::sqrt(3.0) * pitch_[0]));
11,130,537✔
905
    result[1] = std::floor(alpha / pitch_[0]);
11,130,537✔
906
  } else {
907
    // Convert coordinates into skewed bases.  The (alpha, y) basis is used to
908
    // find the index of the global coordinates to within 4 cells.
909
    double alpha = r_o.y - r_o.x * std::sqrt(3.0);
816,233✔
910
    result[0] = std::floor(-alpha / (std::sqrt(3.0) * pitch_[0]));
816,233✔
911
    result[1] = std::floor(r_o.y / (0.5 * std::sqrt(3.0) * pitch_[0]));
816,233✔
912
  }
913

914
  // Add offset to indices (the center cell is (i1, i2) = (0, 0) but
915
  // the array is offset so that the indices never go below 0).
916
  result[0] += n_rings_ - 1;
11,946,770✔
917
  result[1] += n_rings_ - 1;
11,946,770✔
918

919
  // Calculate the (squared) distance between the particle and the centers of
920
  // the four possible cells.  Regular hexagonal tiles form a Voronoi
921
  // tessellation so the xyz should be in the hexagonal cell that it is closest
922
  // to the center of.  This method is used over a method that uses the
923
  // remainders of the floor divisions above because it provides better finite
924
  // precision performance.  Squared distances are used because they are more
925
  // computationally efficient than normal distances.
926

927
  // COINCIDENCE CHECK
928
  // if a distance to center, d, is within the coincidence tolerance of the
929
  // current minimum distance, d_min, the particle is on an edge or vertex.
930
  // In this case, the dot product of the position vector and direction vector
931
  // for the current indices, dp, and the dot product for the currently selected
932
  // indices, dp_min, are compared. The cell which the particle is moving into
933
  // is kept (i.e. the cell with the lowest dot product as the vectors will be
934
  // completely opposed if the particle is moving directly toward the center of
935
  // the cell).
936
  int i1_chg;
937
  int i2_chg;
938
  double d_min {INFTY};
11,946,770✔
939
  double dp_min {INFTY};
11,946,770✔
940
  for (int i = 0; i < 2; i++) {
35,840,310✔
941
    for (int j = 0; j < 2; j++) {
71,680,620✔
942
      // get local coordinates
943
      const array<int, 3> i_xyz {result[0] + j, result[1] + i, 0};
47,787,080✔
944
      Position r_t = get_local_position(r, i_xyz);
47,787,080✔
945
      // calculate distance
946
      double d = r_t.x * r_t.x + r_t.y * r_t.y;
47,787,080✔
947
      // check for coincidence. Because the numerical error incurred
948
      // in hex geometry is higher than other geometries, the relative
949
      // coincidence is checked here so that coincidence is successfully
950
      // detected on large hex lattice with particles far from the origin
951
      // which have rounding errors larger than the FP_COINCIDENT thresdhold.
952
      bool on_boundary = coincident(1.0, d_min / d);
47,787,080✔
953
      if (d < d_min || on_boundary) {
47,787,080✔
954
        // normalize r_t and find dot product
955
        r_t /= std::sqrt(d);
29,929,493✔
956
        double dp = u.x * r_t.x + u.y * r_t.y;
29,929,493✔
957
        // do not update values if particle is on a
958
        // boundary and not moving into this cell
959
        if (on_boundary && dp > dp_min)
29,929,493✔
960
          continue;
2,061,675✔
961
        // update values
962
        d_min = d;
27,867,818✔
963
        i1_chg = j;
27,867,818✔
964
        i2_chg = i;
27,867,818✔
965
        dp_min = dp;
27,867,818✔
966
      }
967
    }
968
  }
969

970
  // update outgoing indices
971
  result[0] += i1_chg;
11,946,770✔
972
  result[1] += i2_chg;
11,946,770✔
973
}
11,946,770✔
974

975
int HexLattice::get_flat_index(const array<int, 3>& i_xyz) const
29,393,276✔
976
{
977
  return (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * i_xyz[2] +
29,393,276✔
978
         (2 * n_rings_ - 1) * i_xyz[1] + i_xyz[0];
29,393,276✔
979
}
980

981
//==============================================================================
982

983
Position HexLattice::get_local_position(
338,714,970✔
984
  Position r, const array<int, 3>& i_xyz) const
985
{
986
  if (orientation_ == Orientation::y) {
338,714,970✔
987
    // x_l = x_g - (center + pitch_x*cos(30)*index_x)
988
    r.x -=
287,785,982✔
989
      center_.x + std::sqrt(3.0) / 2.0 * (i_xyz[0] - n_rings_ + 1) * pitch_[0];
287,785,982✔
990
    // y_l = y_g - (center + pitch_x*index_x + pitch_y*sin(30)*index_y)
991
    r.y -= (center_.y + (i_xyz[1] - n_rings_ + 1) * pitch_[0] +
287,785,982✔
992
            (i_xyz[0] - n_rings_ + 1) * pitch_[0] / 2.0);
287,785,982✔
993
  } else {
994
    // x_l = x_g - (center + pitch_x*index_a + pitch_y*sin(30)*index_y)
995
    r.x -= (center_.x + (i_xyz[0] - n_rings_ + 1) * pitch_[0] +
50,928,988✔
996
            (i_xyz[1] - n_rings_ + 1) * pitch_[0] / 2.0);
50,928,988✔
997
    // y_l = y_g - (center + pitch_y*cos(30)*index_y)
998
    r.y -=
50,928,988✔
999
      center_.y + std::sqrt(3.0) / 2.0 * (i_xyz[1] - n_rings_ + 1) * pitch_[0];
50,928,988✔
1000
  }
1001

1002
  if (is_3d_) {
338,714,970✔
1003
    r.z -= center_.z - (0.5 * n_axial_ - i_xyz[2] - 0.5) * pitch_[1];
78,243,693✔
1004
  }
1005

1006
  return r;
338,714,970✔
1007
}
1008

1009
//==============================================================================
1010

1011
bool HexLattice::is_valid_index(int indx) const
387,424✔
1012
{
1013
  int nx {2 * n_rings_ - 1};
387,424✔
1014
  int ny {2 * n_rings_ - 1};
387,424✔
1015
  int iz = indx / (nx * ny);
387,424✔
1016
  int iy = (indx - nx * ny * iz) / nx;
387,424✔
1017
  int ix = indx - nx * ny * iz - nx * iy;
387,424✔
1018
  array<int, 3> i_xyz {ix, iy, iz};
387,424✔
1019
  return are_valid_indices(i_xyz);
774,848✔
1020
}
1021

1022
//==============================================================================
1023

1024
int32_t& HexLattice::offset(int map, const array<int, 3>& i_xyz)
77,413,281✔
1025
{
1026
  int nx {2 * n_rings_ - 1};
77,413,281✔
1027
  int ny {2 * n_rings_ - 1};
77,413,281✔
1028
  int nz {n_axial_};
77,413,281✔
1029
  return offsets_[nx * ny * nz * map + nx * ny * i_xyz[2] + nx * i_xyz[1] +
77,413,281✔
1030
                  i_xyz[0]];
154,826,562✔
1031
}
1032

UNCOV
1033
int32_t HexLattice::offset(int map, int indx) const
×
1034
{
1035
  int nx {2 * n_rings_ - 1};
×
1036
  int ny {2 * n_rings_ - 1};
×
1037
  int nz {n_axial_};
×
UNCOV
1038
  return offsets_[nx * ny * nz * map + indx];
×
1039
}
1040

1041
//==============================================================================
1042

1043
std::string HexLattice::index_to_string(int indx) const
231✔
1044
{
1045
  int nx {2 * n_rings_ - 1};
231✔
1046
  int ny {2 * n_rings_ - 1};
231✔
1047
  int iz {indx / (nx * ny)};
231✔
1048
  int iy {(indx - nx * ny * iz) / nx};
231✔
1049
  int ix {indx - nx * ny * iz - nx * iy};
231✔
1050
  std::string out {std::to_string(ix - n_rings_ + 1)};
231✔
1051
  out += ',';
231✔
1052
  out += std::to_string(iy - n_rings_ + 1);
231✔
1053
  if (is_3d_) {
231✔
1054
    out += ',';
×
UNCOV
1055
    out += std::to_string(iz);
×
1056
  }
1057
  return out;
231✔
UNCOV
1058
}
×
1059

1060
//==============================================================================
1061

1062
void HexLattice::to_hdf5_inner(hid_t lat_group) const
55✔
1063
{
1064
  // Write basic lattice information.
1065
  write_string(lat_group, "type", "hexagonal", false);
55✔
1066
  write_dataset(lat_group, "n_rings", n_rings_);
55✔
1067
  write_dataset(lat_group, "n_axial", n_axial_);
55✔
1068
  if (orientation_ == Orientation::y) {
55✔
1069
    write_string(lat_group, "orientation", "y", false);
44✔
1070
  } else {
1071
    write_string(lat_group, "orientation", "x", false);
11✔
1072
  }
1073
  if (is_3d_) {
55✔
1074
    write_dataset(lat_group, "pitch", pitch_);
22✔
1075
    write_dataset(lat_group, "center", center_);
22✔
1076
  } else {
1077
    array<double, 1> pitch_short {{pitch_[0]}};
33✔
1078
    write_dataset(lat_group, "pitch", pitch_short);
33✔
1079
    array<double, 2> center_short {{center_[0], center_[1]}};
33✔
1080
    write_dataset(lat_group, "center", center_short);
33✔
1081
  }
1082

1083
  // Write the universe ids.
1084
  hsize_t nx {static_cast<hsize_t>(2 * n_rings_ - 1)};
55✔
1085
  hsize_t ny {static_cast<hsize_t>(2 * n_rings_ - 1)};
55✔
1086
  hsize_t nz {static_cast<hsize_t>(n_axial_)};
55✔
1087
  vector<int> out(nx * ny * nz);
55✔
1088

1089
  for (int m = 0; m < nz; m++) {
143✔
1090
    for (int k = 0; k < ny; k++) {
1,364✔
1091
      for (int j = 0; j < nx; j++) {
26,004✔
1092
        int indx = nx * ny * m + nx * k + j;
24,728✔
1093
        if (j + k < n_rings_ - 1) {
24,728✔
1094
          // This array position is never used; put a -1 to indicate this.
1095
          out[indx] = -1;
3,080✔
1096
        } else if (j + k > 3 * n_rings_ - 3) {
21,648✔
1097
          // This array position is never used; put a -1 to indicate this.
1098
          out[indx] = -1;
3,080✔
1099
        } else {
1100
          out[indx] = model::universes[universes_[indx]]->id_;
18,568✔
1101
        }
1102
      }
1103
    }
1104
  }
1105

1106
  hsize_t dims[3] {nz, ny, nx};
55✔
1107
  write_int(lat_group, 3, dims, "universes", out.data(), false);
55✔
1108
}
55✔
1109

1110
//==============================================================================
1111
// Non-method functions
1112
//==============================================================================
1113

1114
void read_lattices(pugi::xml_node node)
6,852✔
1115
{
1116
  for (pugi::xml_node lat_node : node.children("lattice")) {
8,555✔
1117
    model::lattices.push_back(make_unique<RectLattice>(lat_node));
1,703✔
1118
  }
1119
  for (pugi::xml_node lat_node : node.children("hex_lattice")) {
6,943✔
1120
    model::lattices.push_back(make_unique<HexLattice>(lat_node));
91✔
1121
  }
1122

1123
  // Fill the lattice map.
1124
  for (int i_lat = 0; i_lat < model::lattices.size(); i_lat++) {
8,646✔
1125
    int id = model::lattices[i_lat]->id_;
1,794✔
1126
    auto in_map = model::lattice_map.find(id);
1,794✔
1127
    if (in_map == model::lattice_map.end()) {
1,794✔
1128
      model::lattice_map[id] = i_lat;
1,794✔
1129
    } else {
1130
      fatal_error(
×
UNCOV
1131
        fmt::format("Two or more lattices use the same unique ID: {}", id));
×
1132
    }
1133
  }
1134
}
6,852✔
1135

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

© 2025 Coveralls, Inc