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

openmc-dev / openmc / 28685102997

03 Jul 2026 09:58PM UTC coverage: 81.289% (+0.2%) from 81.124%
28685102997

Pull #3995

github

web-flow
Merge 9457ab4e7 into e3bc61517
Pull Request #3995: Shared secondary bank performance optimizations

18184 of 26382 branches covered (68.93%)

Branch coverage included in aggregate %.

36 of 36 new or added lines in 4 files covered. (100.0%)

53 existing lines in 2 files now uncovered.

59321 of 68963 relevant lines covered (86.02%)

48711520.88 hits per line

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

80.42
/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/math_functions.h"
14
#include "openmc/string_utils.h"
15
#include "openmc/vector.h"
16
#include "openmc/xml_interface.h"
17

18
namespace openmc {
19

20
//==============================================================================
21
// Global variables
22
//==============================================================================
23

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

29
//==============================================================================
30
// Lattice implementation
31
//==============================================================================
32

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

41
  if (check_for_node(lat_node, "name")) {
1,949✔
42
    name_ = get_node_value(lat_node, "name");
359✔
43
  }
44

45
  if (check_for_node(lat_node, "outer")) {
1,949✔
46
    outer_ = std::stoi(get_node_value(lat_node, "outer"));
860✔
47
  }
48
}
1,949✔
49

50
//==============================================================================
51

52
LatticeIter Lattice::begin()
289,799,613✔
53
{
54
  return LatticeIter(*this, 0);
289,799,613✔
55
}
56

57
LatticeIter Lattice::end()
50,439,121✔
58
{
59
  return LatticeIter(*this, universes_.size());
50,439,121✔
60
}
61

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

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

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

77
//==============================================================================
78

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

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

105
//==============================================================================
106

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

123
  int32_t offset = 0;
12,778✔
124
  for (LatticeIter it = begin(); it != end(); ++it) {
9,426,454✔
125
    offsets_[map * universes_.size() + it.indx_] = offset;
9,413,676✔
126
    offset += count_universe_instances(*it, target_univ_id, univ_count_memo);
9,413,676✔
127
  }
128

129
  return offset;
12,778✔
130
}
131

132
//==============================================================================
133

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

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

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

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

156
  close_group(lat_group);
1,495✔
157
}
1,495✔
158

159
//==============================================================================
160
// RectLattice implementation
161
//==============================================================================
162

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

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

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

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

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

221
  // Parse the universes.
222
  universes_.resize(n_cells_[0] * n_cells_[1] * n_cells_[2], C_NONE);
1,863✔
223
  for (int iz = 0; iz < n_cells_[2]; iz++) {
8,447✔
224
    for (int iy = n_cells_[1] - 1; iy > -1; iy--) {
80,232✔
225
      for (int ix = 0; ix < n_cells_[0]; ix++) {
1,014,914✔
226
        int indx1 = n_cells_[0] * n_cells_[1] * iz +
941,266✔
227
                    n_cells_[0] * (n_cells_[1] - iy - 1) + ix;
941,266✔
228
        int indx2 = n_cells_[0] * n_cells_[1] * iz + n_cells_[0] * iy + ix;
941,266✔
229
        universes_[indx1] = std::stoi(univ_words[indx2]);
941,266✔
230
      }
231
    }
232
  }
233
}
1,863✔
234

235
//==============================================================================
236

237
const int32_t& RectLattice::operator[](const array<int, 3>& i_xyz)
916,007,890✔
238
{
239
  return universes_[get_flat_index(i_xyz)];
916,007,890✔
240
}
241

242
//==============================================================================
243

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

251
//==============================================================================
252

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

261
  // Determine the oncoming edge.
262
  double x0 {copysign(0.5 * pitch_[0], u.x)};
1,300,522,803✔
263
  double y0 {copysign(0.5 * pitch_[1], u.y)};
1,300,522,803✔
264

265
  // Evaluate the distance to each oncoming edge independently. Comparing these
266
  // distances directly (rather than reconstructing the crossing position)
267
  // avoids the floating-point cancellation that occurs for large pitches.
268
  double dx = u.x != 0.0 ? (x0 - x) / u.x : INFTY;
1,300,522,803!
269
  double dy = u.y != 0.0 ? (y0 - y) / u.y : INFTY;
1,300,522,803✔
270
  double dz = INFTY;
1,300,522,803✔
271
  if (is_3d_) {
1,300,522,803✔
272
    double z0 {copysign(0.5 * pitch_[2], u.z)};
788,443,395✔
273
    dz = u.z != 0.0 ? (z0 - z) / u.z : INFTY;
788,443,395!
274
  }
275

276
  // The distance to the nearest lattice boundary is the smallest axial
277
  // distance.
278
  double d = std::min({dx, dy, dz});
1,300,522,803✔
279

280
  // Determine which lattice boundaries are being crossed. The axis attaining
281
  // the minimum is exactly equal to d, so at least one translation is always
282
  // set for a finite crossing; a near-equal second axis indicates a corner
283
  // crossing.
284
  array<int, 3> lattice_trans = {0, 0, 0};
1,300,522,803✔
285
  if (isclose(d, dx, FP_COINCIDENT, FP_PRECISION))
1,300,522,803✔
286
    lattice_trans[0] = copysign(1, u.x);
584,534,040✔
287
  if (isclose(d, dy, FP_COINCIDENT, FP_PRECISION))
1,300,522,803✔
288
    lattice_trans[1] = copysign(1, u.y);
486,736,402✔
289
  if (is_3d_ && isclose(d, dz, FP_COINCIDENT, FP_PRECISION))
1,300,522,803✔
290
    lattice_trans[2] = copysign(1, u.z);
229,472,339✔
291

292
  return {d, lattice_trans};
1,300,522,803✔
293
}
294

295
//==============================================================================
296

297
void RectLattice::get_indices(
140,473,949✔
298
  Position r, Direction u, array<int, 3>& result) const
299
{
300
  // Determine x index, accounting for coincidence
301
  double ix_ {(r.x - lower_left_.x) / pitch_.x};
140,473,949✔
302
  long ix_close {std::lround(ix_)};
140,473,949✔
303
  if (coincident(ix_, ix_close)) {
140,473,949✔
304
    result[0] = (u.x > 0) ? ix_close : ix_close - 1;
45,436,819✔
305
  } else {
306
    result[0] = std::floor(ix_);
95,037,130✔
307
  }
308

309
  // Determine y index, accounting for coincidence
310
  double iy_ {(r.y - lower_left_.y) / pitch_.y};
140,473,949✔
311
  long iy_close {std::lround(iy_)};
140,473,949✔
312
  if (coincident(iy_, iy_close)) {
140,473,949✔
313
    result[1] = (u.y > 0) ? iy_close : iy_close - 1;
45,918,704✔
314
  } else {
315
    result[1] = std::floor(iy_);
94,555,245✔
316
  }
317

318
  // Determine z index, accounting for coincidence
319
  result[2] = 0;
140,473,949✔
320
  if (is_3d_) {
140,473,949✔
321
    double iz_ {(r.z - lower_left_.z) / pitch_.z};
74,155,930✔
322
    long iz_close {std::lround(iz_)};
74,155,930✔
323
    if (coincident(iz_, iz_close)) {
74,155,930✔
324
      result[2] = (u.z > 0) ? iz_close : iz_close - 1;
19,590,374✔
325
    } else {
326
      result[2] = std::floor(iz_);
54,565,556✔
327
    }
328
  }
329
}
140,473,949✔
330

331
int RectLattice::get_flat_index(const array<int, 3>& i_xyz) const
916,007,890✔
332
{
333
  return n_cells_[0] * n_cells_[1] * i_xyz[2] + n_cells_[0] * i_xyz[1] +
916,007,890✔
334
         i_xyz[0];
916,007,890✔
335
}
336

337
//==============================================================================
338

339
Position RectLattice::get_local_position(
920,245,299✔
340
  Position r, const array<int, 3>& i_xyz) const
341
{
342
  r.x -= (lower_left_.x + (i_xyz[0] + 0.5) * pitch_.x);
920,245,299✔
343
  r.y -= (lower_left_.y + (i_xyz[1] + 0.5) * pitch_.y);
920,245,299✔
344
  if (is_3d_) {
920,245,299✔
345
    r.z -= (lower_left_.z + (i_xyz[2] + 0.5) * pitch_.z);
739,003,783✔
346
  }
347
  return r;
920,245,299✔
348
}
349

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

352
Direction RectLattice::get_normal(
55✔
353
  const array<int, 3>& i_xyz, bool& is_valid) const
354
{
355
  is_valid = false;
55✔
356
  Direction dir = {0.0, 0.0, 0.0};
55!
357
  if ((std::abs(i_xyz[0]) == 1) && (i_xyz[1] == 0) && (i_xyz[2] == 0)) {
55!
358
    is_valid = true;
55✔
359
    dir[0] = std::copysign(1.0, i_xyz[0]);
55✔
UNCOV
360
  } else if ((i_xyz[0] == 0) && (std::abs(i_xyz[1]) == 1) && (i_xyz[2] == 0)) {
×
UNCOV
361
    is_valid = true;
×
UNCOV
362
    dir[1] = std::copysign(1.0, i_xyz[1]);
×
UNCOV
363
  } else if ((i_xyz[0] == 0) && (i_xyz[1] == 0) && (std::abs(i_xyz[2]) == 1)) {
×
UNCOV
364
    is_valid = true;
×
UNCOV
365
    dir[2] = std::copysign(1.0, i_xyz[2]);
×
366
  }
367
  return dir;
55✔
368
}
369

370
//==============================================================================
371

372
int32_t& RectLattice::offset(int map, const array<int, 3>& i_xyz)
1,290,251,435✔
373
{
374
  return offsets_[n_cells_[0] * n_cells_[1] * n_cells_[2] * map +
1,290,251,435✔
375
                  n_cells_[0] * n_cells_[1] * i_xyz[2] +
1,290,251,435✔
376
                  n_cells_[0] * i_xyz[1] + i_xyz[0]];
1,290,251,435✔
377
}
378

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

381
int32_t RectLattice::offset(int map, int indx) const
135,135✔
382
{
383
  return offsets_[n_cells_[0] * n_cells_[1] * n_cells_[2] * map + indx];
135,135✔
384
}
385

386
//==============================================================================
387

388
std::string RectLattice::index_to_string(int indx) const
1,638,296✔
389
{
390
  int iz {indx / (n_cells_[0] * n_cells_[1])};
1,638,296✔
391
  int iy {(indx - n_cells_[0] * n_cells_[1] * iz) / n_cells_[0]};
1,638,296✔
392
  int ix {indx - n_cells_[0] * n_cells_[1] * iz - n_cells_[0] * iy};
1,638,296✔
393
  std::string out {std::to_string(ix)};
1,638,296✔
394
  out += ',';
1,638,296✔
395
  out += std::to_string(iy);
3,276,592✔
396
  if (is_3d_) {
1,638,296!
UNCOV
397
    out += ',';
×
UNCOV
398
    out += std::to_string(iz);
×
399
  }
400
  return out;
1,638,296✔
UNCOV
401
}
×
402

403
//==============================================================================
404

405
void RectLattice::to_hdf5_inner(hid_t lat_group) const
1,440✔
406
{
407
  // Write basic lattice information.
408
  write_string(lat_group, "type", "rectangular", false);
1,440✔
409
  if (is_3d_) {
1,440✔
410
    write_dataset(lat_group, "pitch", pitch_);
403✔
411
    write_dataset(lat_group, "lower_left", lower_left_);
403✔
412
    write_dataset(lat_group, "dimension", n_cells_);
403✔
413
  } else {
414
    array<double, 2> pitch_short {{pitch_[0], pitch_[1]}};
1,037✔
415
    write_dataset(lat_group, "pitch", pitch_short);
1,037✔
416
    array<double, 2> ll_short {{lower_left_[0], lower_left_[1]}};
1,037✔
417
    write_dataset(lat_group, "lower_left", ll_short);
1,037✔
418
    array<int, 2> nc_short {{n_cells_[0], n_cells_[1]}};
1,037✔
419
    write_dataset(lat_group, "dimension", nc_short);
1,037✔
420
  }
421

422
  // Write the universe ids.  The convention here is to switch the ordering on
423
  // the y-axis to match the way universes are input in a text file.
424
  if (is_3d_) {
1,440✔
425
    hsize_t nx {static_cast<hsize_t>(n_cells_[0])};
403✔
426
    hsize_t ny {static_cast<hsize_t>(n_cells_[1])};
403✔
427
    hsize_t nz {static_cast<hsize_t>(n_cells_[2])};
403✔
428
    vector<int> out(nx * ny * nz);
403✔
429

430
    for (int m = 0; m < nz; m++) {
4,347✔
431
      for (int k = 0; k < ny; k++) {
49,766✔
432
        for (int j = 0; j < nx; j++) {
609,875✔
433
          int indx1 = nx * ny * m + nx * k + j;
564,053✔
434
          int indx2 = nx * ny * m + nx * (ny - k - 1) + j;
564,053✔
435
          out[indx2] = model::universes[universes_[indx1]]->id_;
564,053✔
436
        }
437
      }
438
    }
439

440
    hsize_t dims[3] {nz, ny, nx};
403✔
441
    write_int(lat_group, 3, dims, "universes", out.data(), false);
403✔
442

443
  } else {
403✔
444
    hsize_t nx {static_cast<hsize_t>(n_cells_[0])};
1,037✔
445
    hsize_t ny {static_cast<hsize_t>(n_cells_[1])};
1,037✔
446
    vector<int> out(nx * ny);
1,037✔
447

448
    for (int k = 0; k < ny; k++) {
10,521✔
449
      for (int j = 0; j < nx; j++) {
156,293✔
450
        int indx1 = nx * k + j;
146,809✔
451
        int indx2 = nx * (ny - k - 1) + j;
146,809✔
452
        out[indx2] = model::universes[universes_[indx1]]->id_;
146,809✔
453
      }
454
    }
455

456
    hsize_t dims[3] {1, ny, nx};
1,037✔
457
    write_int(lat_group, 3, dims, "universes", out.data(), false);
1,037✔
458
  }
1,037✔
459
}
1,440✔
460

461
//==============================================================================
462
// HexLattice implementation
463
//==============================================================================
464

465
HexLattice::HexLattice(pugi::xml_node lat_node) : Lattice {lat_node}
86✔
466
{
467
  type_ = LatticeType::hex;
86✔
468

469
  // Read the number of lattice cells in each dimension.
470
  n_rings_ = std::stoi(get_node_value(lat_node, "n_rings"));
172✔
471
  if (check_for_node(lat_node, "n_axial")) {
86✔
472
    n_axial_ = std::stoi(get_node_value(lat_node, "n_axial"));
60✔
473
    is_3d_ = true;
30✔
474
  } else {
475
    n_axial_ = 1;
56✔
476
    is_3d_ = false;
56✔
477
  }
478

479
  // Read the lattice orientation.  Default to 'y'.
480
  if (check_for_node(lat_node, "orientation")) {
86✔
481
    std::string orientation = get_node_value(lat_node, "orientation");
15✔
482
    if (orientation == "y") {
15!
UNCOV
483
      orientation_ = Orientation::y;
×
484
    } else if (orientation == "x") {
15!
485
      orientation_ = Orientation::x;
15✔
486
    } else {
UNCOV
487
      fatal_error("Unrecognized orientation '" + orientation +
×
UNCOV
488
                  "' for lattice " + std::to_string(id_));
×
489
    }
490
  } else {
15✔
491
    orientation_ = Orientation::y;
71✔
492
  }
493

494
  // Read the lattice center.
495
  std::string center_str {get_node_value(lat_node, "center")};
86✔
496
  vector<std::string> center_words {split(center_str)};
86✔
497
  if (is_3d_ && (center_words.size() != 3)) {
86!
UNCOV
498
    fatal_error("A hexagonal lattice with <n_axial> must have <center> "
×
499
                "specified by 3 numbers.");
500
  } else if (!is_3d_ && center_words.size() != 2) {
86!
UNCOV
501
    fatal_error("A hexagonal lattice without <n_axial> must have <center> "
×
502
                "specified by 2 numbers.");
503
  }
504
  center_[0] = stod(center_words[0]);
86✔
505
  center_[1] = stod(center_words[1]);
86✔
506
  if (is_3d_) {
86✔
507
    center_[2] = stod(center_words[2]);
30✔
508
  }
509

510
  // Read the lattice pitches.
511
  std::string pitch_str {get_node_value(lat_node, "pitch")};
86✔
512
  vector<std::string> pitch_words {split(pitch_str)};
86✔
513
  if (is_3d_ && (pitch_words.size() != 2)) {
86!
UNCOV
514
    fatal_error("A hexagonal lattice with <n_axial> must have <pitch> "
×
515
                "specified by 2 numbers.");
516
  } else if (!is_3d_ && (pitch_words.size() != 1)) {
86!
UNCOV
517
    fatal_error("A hexagonal lattice without <n_axial> must have <pitch> "
×
518
                "specified by 1 number.");
519
  }
520
  pitch_[0] = stod(pitch_words[0]);
86✔
521
  if (is_3d_) {
86✔
522
    pitch_[1] = stod(pitch_words[1]);
30✔
523
  }
524

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

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

553
//==============================================================================
554

555
void HexLattice::fill_lattice_x(const vector<std::string>& univ_words)
15✔
556
{
557
  int input_index = 0;
15✔
558
  for (int m = 0; m < n_axial_; m++) {
45✔
559
    // Initialize lattice indecies.
560
    int i_a = -(n_rings_ - 1);
30✔
561
    int i_y = n_rings_ - 1;
30✔
562

563
    // Map upper region of hexagonal lattice which is found in the
564
    // first n_rings-1 rows of the input.
565
    for (int k = 0; k < n_rings_ - 1; k++) {
330✔
566

567
      // Iterate over the input columns.
568
      for (int j = 0; j < k + n_rings_; j++) {
4,950✔
569
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
4,650✔
570
                   (2 * n_rings_ - 1) * (i_y + n_rings_ - 1) +
4,650✔
571
                   (i_a + n_rings_ - 1);
4,650✔
572
        universes_[indx] = std::stoi(univ_words[input_index]);
4,650✔
573
        input_index++;
4,650✔
574
        // Move to the next right neighbour cell
575
        i_a += 1;
4,650✔
576
      }
577

578
      // Return the lattice index to the start of the current row.
579
      i_a = -(n_rings_ - 1);
300✔
580
      i_y -= 1;
300✔
581
    }
582

583
    // Map the lower region from the centerline of cart to down side
584
    for (int k = 0; k < n_rings_; k++) {
360✔
585
      // Walk the index to the lower-right neighbor of the last row start.
586
      i_a = -(n_rings_ - 1) + k;
330✔
587

588
      // Iterate over the input columns.
589
      for (int j = 0; j < 2 * n_rings_ - k - 1; j++) {
5,610✔
590
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
5,280✔
591
                   (2 * n_rings_ - 1) * (i_y + n_rings_ - 1) +
5,280✔
592
                   (i_a + n_rings_ - 1);
5,280✔
593
        universes_[indx] = std::stoi(univ_words[input_index]);
5,280✔
594
        input_index++;
5,280✔
595
        // Move to the next right neighbour cell
596
        i_a += 1;
5,280✔
597
      }
598

599
      // Return lattice index to start of current row.
600
      i_y -= 1;
330✔
601
    }
602
  }
603
}
15✔
604

605
//==============================================================================
606

607
void HexLattice::fill_lattice_y(const vector<std::string>& univ_words)
71✔
608
{
609
  int input_index = 0;
71✔
610
  for (int m = 0; m < n_axial_; m++) {
172✔
611
    // Initialize lattice indecies.
612
    int i_x = 1;
101✔
613
    int i_a = n_rings_ - 1;
101✔
614

615
    // Map upper triangular region of hexagonal lattice which is found in the
616
    // first n_rings-1 rows of the input.
617
    for (int k = 0; k < n_rings_ - 1; k++) {
622✔
618
      // Walk the index to lower-left neighbor of last row start.
619
      i_x -= 1;
521✔
620

621
      // Iterate over the input columns.
622
      for (int j = 0; j < k + 1; j++) {
3,082✔
623
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
2,561✔
624
                   (2 * n_rings_ - 1) * (i_a + n_rings_ - 1) +
2,561✔
625
                   (i_x + n_rings_ - 1);
2,561✔
626
        universes_[indx] = std::stoi(univ_words[input_index]);
2,561✔
627
        input_index++;
2,561✔
628
        // Walk the index to the right neighbor (which is not adjacent).
629
        i_x += 2;
2,561✔
630
        i_a -= 1;
2,561✔
631
      }
632

633
      // Return the lattice index to the start of the current row.
634
      i_x -= 2 * (k + 1);
521✔
635
      i_a += (k + 1);
521✔
636
    }
637

638
    // Map the middle square region of the hexagonal lattice which is found in
639
    // the next 2*n_rings-1 rows of the input.
640
    for (int k = 0; k < 2 * n_rings_ - 1; k++) {
1,244✔
641
      if ((k % 2) == 0) {
1,143✔
642
        // Walk the index to the lower-left neighbor of the last row start.
643
        i_x -= 1;
622✔
644
      } else {
645
        // Walk the index to the lower-right neighbor of the last row start.
646
        i_x += 1;
521✔
647
        i_a -= 1;
521✔
648
      }
649

650
      // Iterate over the input columns.
651
      for (int j = 0; j < n_rings_ - (k % 2); j++) {
11,488✔
652
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
10,345✔
653
                   (2 * n_rings_ - 1) * (i_a + n_rings_ - 1) +
10,345✔
654
                   (i_x + n_rings_ - 1);
10,345✔
655
        universes_[indx] = std::stoi(univ_words[input_index]);
10,345✔
656
        input_index++;
10,345✔
657
        // Walk the index to the right neighbor (which is not adjacent).
658
        i_x += 2;
10,345✔
659
        i_a -= 1;
10,345✔
660
      }
661

662
      // Return the lattice index to the start of the current row.
663
      i_x -= 2 * (n_rings_ - (k % 2));
1,143✔
664
      i_a += n_rings_ - (k % 2);
1,143✔
665
    }
666

667
    // Map the lower triangular region of the hexagonal lattice.
668
    for (int k = 0; k < n_rings_ - 1; k++) {
622✔
669
      // Walk the index to the lower-right neighbor of the last row start.
670
      i_x += 1;
521✔
671
      i_a -= 1;
521✔
672

673
      // Iterate over the input columns.
674
      for (int j = 0; j < n_rings_ - k - 1; j++) {
3,082✔
675
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
2,561✔
676
                   (2 * n_rings_ - 1) * (i_a + n_rings_ - 1) +
2,561✔
677
                   (i_x + n_rings_ - 1);
2,561✔
678
        universes_[indx] = std::stoi(univ_words[input_index]);
2,561✔
679
        input_index++;
2,561✔
680
        // Walk the index to the right neighbor (which is not adjacent).
681
        i_x += 2;
2,561✔
682
        i_a -= 1;
2,561✔
683
      }
684

685
      // Return lattice index to start of current row.
686
      i_x -= 2 * (n_rings_ - k - 1);
521✔
687
      i_a += n_rings_ - k - 1;
521✔
688
    }
689
  }
690
}
71✔
691

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

694
const int32_t& HexLattice::operator[](const array<int, 3>& i_xyz)
29,285,960✔
695
{
696
  return universes_[get_flat_index(i_xyz)];
29,285,960✔
697
}
698

699
//==============================================================================
700

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

712
ReverseLatticeIter HexLattice::rbegin()
231✔
713
{
714
  return ReverseLatticeIter(*this, universes_.size() - n_rings_);
231✔
715
}
716

717
int32_t& HexLattice::back()
11✔
718
{
719
  return universes_[universes_.size() - n_rings_];
11✔
720
}
721

722
LatticeIter HexLattice::end()
643,129✔
723
{
724
  return LatticeIter(*this, universes_.size() - n_rings_ + 1);
643,129✔
725
}
726

727
ReverseLatticeIter HexLattice::rend()
924✔
728
{
729
  return ReverseLatticeIter(*this, n_rings_ - 2);
924✔
730
}
731

732
//==============================================================================
733

734
bool HexLattice::are_valid_indices(const array<int, 3>& i_xyz) const
113,638,691✔
735
{
736
  // Check if (x, alpha, z) indices are valid, accounting for number of rings
737
  return ((i_xyz[0] >= 0) && (i_xyz[1] >= 0) && (i_xyz[2] >= 0) &&
113,638,691✔
738
          (i_xyz[0] < 2 * n_rings_ - 1) && (i_xyz[1] < 2 * n_rings_ - 1) &&
110,318,660✔
739
          (i_xyz[0] + i_xyz[1] > n_rings_ - 2) &&
109,545,008✔
740
          (i_xyz[0] + i_xyz[1] < 3 * n_rings_ - 2) && (i_xyz[2] < n_axial_));
222,837,269✔
741
}
742

743
//==============================================================================
744

745
std::pair<double, array<int, 3>> HexLattice::distance(
85,503,297✔
746
  Position r, Direction u, const array<int, 3>& i_xyz) const
747
{
748
  // Short description of the direction vectors used here.  The beta, gamma, and
749
  // delta vectors point towards the flat sides of each hexagonal tile.
750
  // Y - orientation:
751
  //   basis0 = (1, 0)
752
  //   basis1 = (-1/sqrt(3), 1)   = +120 degrees from basis0
753
  //   beta   = (sqrt(3)/2, 1/2)  = +30 degrees from basis0
754
  //   gamma  = (sqrt(3)/2, -1/2) = -60 degrees from beta
755
  //   delta  = (0, 1)            = +60 degrees from beta
756
  // X - orientation:
757
  //   basis0 = (1/sqrt(3), -1)
758
  //   basis1 = (0, 1)            = +120 degrees from basis0
759
  //   beta   = (1, 0)            = +30 degrees from basis0
760
  //   gamma  = (1/2, -sqrt(3)/2) = -60 degrees from beta
761
  //   delta  = (1/2, sqrt(3)/2)  = +60 degrees from beta
762
  // The z-axis is considered separately.
763
  double beta_dir;
85,503,297✔
764
  double gamma_dir;
85,503,297✔
765
  double delta_dir;
85,503,297✔
766
  if (orientation_ == Orientation::y) {
85,503,297✔
767
    beta_dir = u.x * std::sqrt(3.0) / 2.0 + u.y / 2.0;
70,869,348✔
768
    gamma_dir = u.x * std::sqrt(3.0) / 2.0 - u.y / 2.0;
70,869,348✔
769
    delta_dir = u.y;
70,869,348✔
770
  } else {
771
    beta_dir = u.x;
14,633,949✔
772
    gamma_dir = u.x / 2.0 - u.y * std::sqrt(3.0) / 2.0;
14,633,949✔
773
    delta_dir = u.x / 2.0 + u.y * std::sqrt(3.0) / 2.0;
14,633,949✔
774
  }
775

776
  // Note that hexagonal lattice distance calculations are performed
777
  // using the particle's coordinates relative to the neighbor lattice
778
  // cells, not relative to the particle's current cell.  This is done
779
  // because there is significant disagreement between neighboring cells
780
  // on where the lattice boundary is due to finite precision issues.
781

782
  // beta direction
783
  double d {INFTY};
85,503,297✔
784
  array<int, 3> lattice_trans;
85,503,297✔
785
  double edge = -copysign(0.5 * pitch_[0], beta_dir); // Oncoming edge
85,503,297✔
786
  Position r_t;
85,503,297✔
787
  if (beta_dir > 0) {
85,503,297✔
788
    const array<int, 3> i_xyz_t {i_xyz[0] + 1, i_xyz[1], i_xyz[2]};
42,766,713✔
789
    r_t = get_local_position(r, i_xyz_t);
42,766,713✔
790
  } else {
791
    const array<int, 3> i_xyz_t {i_xyz[0] - 1, i_xyz[1], i_xyz[2]};
42,736,584✔
792
    r_t = get_local_position(r, i_xyz_t);
42,736,584✔
793
  }
794
  double beta;
85,503,297✔
795
  if (orientation_ == Orientation::y) {
85,503,297✔
796
    beta = r_t.x * std::sqrt(3.0) / 2.0 + r_t.y / 2.0;
70,869,348✔
797
  } else {
798
    beta = r_t.x;
799
  }
800
  if ((std::abs(beta - edge) > FP_PRECISION) && beta_dir != 0) {
85,503,297!
801
    d = (edge - beta) / beta_dir;
85,503,297✔
802
    if (beta_dir > 0) {
85,503,297✔
803
      lattice_trans = {1, 0, 0};
804
    } else {
805
      lattice_trans = {-1, 0, 0};
42,736,584✔
806
    }
807
  }
808

809
  // gamma direction
810
  edge = -copysign(0.5 * pitch_[0], gamma_dir);
85,503,297✔
811
  if (gamma_dir > 0) {
85,503,297✔
812
    const array<int, 3> i_xyz_t {i_xyz[0] + 1, i_xyz[1] - 1, i_xyz[2]};
42,833,450✔
813
    r_t = get_local_position(r, i_xyz_t);
42,833,450✔
814
  } else {
815
    const array<int, 3> i_xyz_t {i_xyz[0] - 1, i_xyz[1] + 1, i_xyz[2]};
42,669,847✔
816
    r_t = get_local_position(r, i_xyz_t);
42,669,847✔
817
  }
818
  double gamma;
85,503,297✔
819
  if (orientation_ == Orientation::y) {
85,503,297✔
820
    gamma = r_t.x * std::sqrt(3.0) / 2.0 - r_t.y / 2.0;
70,869,348✔
821
  } else {
822
    gamma = r_t.x / 2.0 - r_t.y * std::sqrt(3.0) / 2.0;
14,633,949✔
823
  }
824
  if ((std::abs(gamma - edge) > FP_PRECISION) && gamma_dir != 0) {
85,503,297!
825
    double this_d = (edge - gamma) / gamma_dir;
85,503,297✔
826
    if (this_d < d) {
85,503,297✔
827
      if (gamma_dir > 0) {
42,791,749✔
828
        lattice_trans = {1, -1, 0};
829
      } else {
830
        lattice_trans = {-1, 1, 0};
21,307,407✔
831
      }
832
      d = this_d;
833
    }
834
  }
835

836
  // delta direction
837
  edge = -copysign(0.5 * pitch_[0], delta_dir);
85,503,297✔
838
  if (delta_dir > 0) {
85,503,297✔
839
    const array<int, 3> i_xyz_t {i_xyz[0], i_xyz[1] + 1, i_xyz[2]};
42,674,093✔
840
    r_t = get_local_position(r, i_xyz_t);
42,674,093✔
841
  } else {
842
    const array<int, 3> i_xyz_t {i_xyz[0], i_xyz[1] - 1, i_xyz[2]};
42,829,204✔
843
    r_t = get_local_position(r, i_xyz_t);
42,829,204✔
844
  }
845
  double delta;
85,503,297✔
846
  if (orientation_ == Orientation::y) {
85,503,297✔
847
    delta = r_t.y;
848
  } else {
849
    delta = r_t.x / 2.0 + r_t.y * std::sqrt(3.0) / 2.0;
14,633,949✔
850
  }
851
  if ((std::abs(delta - edge) > FP_PRECISION) && delta_dir != 0) {
85,503,297!
852
    double this_d = (edge - delta) / delta_dir;
85,503,297✔
853
    if (this_d < d) {
85,503,297✔
854
      if (delta_dir > 0) {
28,519,040✔
855
        lattice_trans = {0, 1, 0};
856
      } else {
857
        lattice_trans = {0, -1, 0};
14,284,358✔
858
      }
859
      d = this_d;
860
    }
861
  }
862

863
  // Top and bottom sides
864
  if (is_3d_) {
85,503,297✔
865
    double z = r.z;
20,213,028✔
866
    double z0 {copysign(0.5 * pitch_[1], u.z)};
20,213,028!
867
    if ((std::abs(z - z0) > FP_PRECISION) && u.z != 0) {
20,213,028!
868
      double this_d = (z0 - z) / u.z;
20,213,028✔
869
      if (this_d < d) {
20,213,028✔
870
        d = this_d;
3,062,268✔
871
        if (u.z > 0) {
3,062,268✔
872
          lattice_trans = {0, 0, 1};
873
        } else {
874
          lattice_trans = {0, 0, -1};
1,535,215✔
875
        }
876
        d = this_d;
877
      }
878
    }
879
  }
880

881
  return {d, lattice_trans};
85,503,297✔
882
}
883

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

886
void HexLattice::get_indices(
11,900,471✔
887
  Position r, Direction u, array<int, 3>& result) const
888
{
889
  // Offset the xyz by the lattice center.
890
  Position r_o {r.x - center_.x, r.y - center_.y, r.z};
11,900,471✔
891
  if (is_3d_) {
11,900,471✔
892
    r_o.z -= center_.z;
2,484,878✔
893
  }
894

895
  // Index the z direction, accounting for coincidence
896
  result[2] = 0;
11,900,471✔
897
  if (is_3d_) {
11,900,471✔
898
    double iz_ {r_o.z / pitch_[1] + 0.5 * n_axial_};
2,484,878✔
899
    long iz_close {std::lround(iz_)};
2,484,878✔
900
    if (coincident(iz_, iz_close)) {
2,484,878✔
901
      result[2] = (u.z > 0) ? iz_close : iz_close - 1;
765,699✔
902
    } else {
903
      result[2] = std::floor(iz_);
1,719,179✔
904
    }
905
  }
906

907
  if (orientation_ == Orientation::y) {
11,900,471✔
908
    // Convert coordinates into skewed bases.  The (x, alpha) basis is used to
909
    // find the index of the global coordinates to within 4 cells.
910
    double alpha = r_o.y - r_o.x / std::sqrt(3.0);
11,078,199✔
911
    result[0] = std::floor(r_o.x / (0.5 * std::sqrt(3.0) * pitch_[0]));
11,078,199✔
912
    result[1] = std::floor(alpha / pitch_[0]);
11,078,199✔
913
  } else {
914
    // Convert coordinates into skewed bases.  The (alpha, y) basis is used to
915
    // find the index of the global coordinates to within 4 cells.
916
    double alpha = r_o.y - r_o.x * std::sqrt(3.0);
822,272✔
917
    result[0] = std::floor(-alpha / (std::sqrt(3.0) * pitch_[0]));
822,272✔
918
    result[1] = std::floor(r_o.y / (0.5 * std::sqrt(3.0) * pitch_[0]));
822,272✔
919
  }
920

921
  // Add offset to indices (the center cell is (i1, i2) = (0, 0) but
922
  // the array is offset so that the indices never go below 0).
923
  result[0] += n_rings_ - 1;
11,900,471✔
924
  result[1] += n_rings_ - 1;
11,900,471✔
925

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

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

977
  // update outgoing indices
978
  result[0] += i1_chg;
11,900,471✔
979
  result[1] += i2_chg;
11,900,471✔
980
}
11,900,471✔
981

982
int HexLattice::get_flat_index(const array<int, 3>& i_xyz) const
29,285,960✔
983
{
984
  return (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * i_xyz[2] +
29,285,960✔
985
         (2 * n_rings_ - 1) * i_xyz[1] + i_xyz[0];
29,285,960✔
986
}
987

988
//==============================================================================
989

990
Position HexLattice::get_local_position(
337,758,718✔
991
  Position r, const array<int, 3>& i_xyz) const
992
{
993
  if (orientation_ == Orientation::y) {
337,758,718✔
994
    // x_l = x_g - (center + pitch_x*cos(30)*index_x)
995
    r.x -=
572,539,000✔
996
      center_.x + std::sqrt(3.0) / 2.0 * (i_xyz[0] - n_rings_ + 1) * pitch_[0];
286,269,500✔
997
    // y_l = y_g - (center + pitch_x*index_x + pitch_y*sin(30)*index_y)
998
    r.y -= (center_.y + (i_xyz[1] - n_rings_ + 1) * pitch_[0] +
286,269,500✔
999
            (i_xyz[0] - n_rings_ + 1) * pitch_[0] / 2.0);
286,269,500✔
1000
  } else {
1001
    // x_l = x_g - (center + pitch_x*index_a + pitch_y*sin(30)*index_y)
1002
    r.x -= (center_.x + (i_xyz[0] - n_rings_ + 1) * pitch_[0] +
51,489,218✔
1003
            (i_xyz[1] - n_rings_ + 1) * pitch_[0] / 2.0);
51,489,218✔
1004
    // y_l = y_g - (center + pitch_y*cos(30)*index_y)
1005
    r.y -=
51,489,218✔
1006
      center_.y + std::sqrt(3.0) / 2.0 * (i_xyz[1] - n_rings_ + 1) * pitch_[0];
51,489,218✔
1007
  }
1008

1009
  if (is_3d_) {
337,758,718✔
1010
    r.z -= center_.z - (0.5 * n_axial_ - i_xyz[2] - 0.5) * pitch_[1];
78,611,665✔
1011
  }
1012

1013
  return r;
337,758,718✔
1014
}
1015

1016
//==============================================================================
1017

UNCOV
1018
Direction HexLattice::get_normal(
×
1019
  const array<int, 3>& i_xyz, bool& is_valid) const
1020
{
1021
  // Short description of the direction vectors used here.  The beta, gamma, and
1022
  // delta vectors point towards the flat sides of each hexagonal tile.
1023
  // Y - orientation:
1024
  //   basis0 = (1, 0)
1025
  //   basis1 = (-1/sqrt(3), 1)   = +120 degrees from basis0
1026
  //   beta   = (sqrt(3)/2, 1/2)  = +30 degrees from basis0
1027
  //   gamma  = (sqrt(3)/2, -1/2) = -60 degrees from beta
1028
  //   delta  = (0, 1)            = +60 degrees from beta
1029
  // X - orientation:
1030
  //   basis0 = (1/sqrt(3), -1)
1031
  //   basis1 = (0, 1)            = +120 degrees from basis0
1032
  //   beta   = (1, 0)            = +30 degrees from basis0
1033
  //   gamma  = (1/2, -sqrt(3)/2) = -60 degrees from beta
1034
  //   delta  = (1/2, sqrt(3)/2)  = +60 degrees from beta
1035

UNCOV
1036
  is_valid = false;
×
1037
  Direction dir = {0.0, 0.0, 0.0};
×
1038
  if ((i_xyz[0] == 0) && (i_xyz[1] == 0) && (std::abs(i_xyz[2]) == 1)) {
×
1039
    is_valid = true;
×
1040
    dir[2] = std::copysign(1.0, i_xyz[2]);
×
UNCOV
1041
  } else if ((i_xyz[2] == 0) &&
×
1042
             std::max({std::abs(i_xyz[0]), std::abs(i_xyz[1]),
×
1043
               std::abs(i_xyz[0] + i_xyz[1])}) == 1) {
×
UNCOV
1044
    is_valid = true;
×
1045
    // beta direction
1046
    if ((i_xyz[0] == 1) && (i_xyz[1] == 0)) {
×
1047
      if (orientation_ == Orientation::y) {
×
1048
        dir[0] = 0.5 * std::sqrt(3.0);
×
UNCOV
1049
        dir[1] = 0.5;
×
1050
      } else {
1051
        dir[0] = 1.0;
×
UNCOV
1052
        dir[1] = 0.0;
×
1053
      }
1054
    } else if ((i_xyz[0] == -1) && (i_xyz[1] == 0)) {
×
1055
      if (orientation_ == Orientation::y) {
×
1056
        dir[0] = -0.5 * std::sqrt(3.0);
×
1057
        dir[1] = -0.5;
×
1058
      } else {
1059
        dir[0] = -1.0;
×
1060
        dir[1] = 0.0;
×
1061
      }
1062
      // gamma direction
1063
    } else if ((i_xyz[0] == 1) && (i_xyz[1] == -1)) {
×
1064
      if (orientation_ == Orientation::y) {
×
1065
        dir[0] = 0.5 * std::sqrt(3.0);
×
UNCOV
1066
        dir[1] = -0.5;
×
1067
      } else {
1068
        dir[0] = 0.5;
×
UNCOV
1069
        dir[1] = -0.5 * std::sqrt(3.0);
×
1070
      }
1071
    } else if ((i_xyz[0] == -1) && (i_xyz[1] == 1)) {
×
1072
      if (orientation_ == Orientation::y) {
×
1073
        dir[0] = -0.5 * std::sqrt(3.0);
×
1074
        dir[1] = 0.5;
×
1075
      } else {
1076
        dir[0] = -0.5;
×
1077
        dir[1] = 0.5 * std::sqrt(3.0);
×
1078
      }
1079
      // delta direction
1080
    } else if ((i_xyz[0] == 0) && (i_xyz[1] == 1)) {
×
1081
      if (orientation_ == Orientation::y) {
×
1082
        dir[0] = 0.0;
×
UNCOV
1083
        dir[1] = 1.0;
×
1084
      } else {
1085
        dir[0] = 0.5;
×
UNCOV
1086
        dir[1] = 0.5 * std::sqrt(3.0);
×
1087
      }
UNCOV
1088
    } else if ((i_xyz[0] == 0) && (i_xyz[1] == -1)) {
×
1089
      if (orientation_ == Orientation::y) {
×
UNCOV
1090
        dir[0] = 0.0;
×
UNCOV
1091
        dir[1] = -1.0;
×
1092
      } else {
UNCOV
1093
        dir[0] = -0.5;
×
UNCOV
1094
        dir[1] = -0.5 * std::sqrt(3.0);
×
1095
      }
1096
    }
1097
  }
UNCOV
1098
  return dir;
×
1099
}
1100

1101
//==============================================================================
1102

1103
bool HexLattice::is_valid_index(int indx) const
363,287✔
1104
{
1105
  int nx {2 * n_rings_ - 1};
363,287✔
1106
  int ny {2 * n_rings_ - 1};
363,287✔
1107
  int iz = indx / (nx * ny);
363,287✔
1108
  int iy = (indx - nx * ny * iz) / nx;
363,287✔
1109
  int ix = indx - nx * ny * iz - nx * iy;
363,287✔
1110
  array<int, 3> i_xyz {ix, iy, iz};
363,287✔
1111
  return are_valid_indices(i_xyz);
363,287✔
1112
}
1113

1114
//==============================================================================
1115

1116
int32_t& HexLattice::offset(int map, const array<int, 3>& i_xyz)
77,204,831✔
1117
{
1118
  int nx {2 * n_rings_ - 1};
77,204,831✔
1119
  int ny {2 * n_rings_ - 1};
77,204,831✔
1120
  int nz {n_axial_};
77,204,831✔
1121
  return offsets_[nx * ny * nz * map + nx * ny * i_xyz[2] + nx * i_xyz[1] +
77,204,831✔
1122
                  i_xyz[0]];
77,204,831✔
1123
}
1124

UNCOV
1125
int32_t HexLattice::offset(int map, int indx) const
×
1126
{
UNCOV
1127
  int nx {2 * n_rings_ - 1};
×
UNCOV
1128
  int ny {2 * n_rings_ - 1};
×
UNCOV
1129
  int nz {n_axial_};
×
UNCOV
1130
  return offsets_[nx * ny * nz * map + indx];
×
1131
}
1132

1133
//==============================================================================
1134

1135
std::string HexLattice::index_to_string(int indx) const
231✔
1136
{
1137
  int nx {2 * n_rings_ - 1};
231✔
1138
  int ny {2 * n_rings_ - 1};
231✔
1139
  int iz {indx / (nx * ny)};
231✔
1140
  int iy {(indx - nx * ny * iz) / nx};
231✔
1141
  int ix {indx - nx * ny * iz - nx * iy};
231✔
1142
  std::string out {std::to_string(ix - n_rings_ + 1)};
231✔
1143
  out += ',';
231✔
1144
  out += std::to_string(iy - n_rings_ + 1);
462✔
1145
  if (is_3d_) {
231!
UNCOV
1146
    out += ',';
×
UNCOV
1147
    out += std::to_string(iz);
×
1148
  }
1149
  return out;
231✔
UNCOV
1150
}
×
1151

1152
//==============================================================================
1153

1154
void HexLattice::to_hdf5_inner(hid_t lat_group) const
55✔
1155
{
1156
  // Write basic lattice information.
1157
  write_string(lat_group, "type", "hexagonal", false);
55✔
1158
  write_dataset(lat_group, "n_rings", n_rings_);
55✔
1159
  write_dataset(lat_group, "n_axial", n_axial_);
55✔
1160
  if (orientation_ == Orientation::y) {
55✔
1161
    write_string(lat_group, "orientation", "y", false);
88✔
1162
  } else {
1163
    write_string(lat_group, "orientation", "x", false);
22✔
1164
  }
1165
  if (is_3d_) {
55✔
1166
    write_dataset(lat_group, "pitch", pitch_);
22✔
1167
    write_dataset(lat_group, "center", center_);
22✔
1168
  } else {
1169
    array<double, 1> pitch_short {{pitch_[0]}};
33✔
1170
    write_dataset(lat_group, "pitch", pitch_short);
33✔
1171
    array<double, 2> center_short {{center_[0], center_[1]}};
33✔
1172
    write_dataset(lat_group, "center", center_short);
33✔
1173
  }
1174

1175
  // Write the universe ids.
1176
  hsize_t nx {static_cast<hsize_t>(2 * n_rings_ - 1)};
55✔
1177
  hsize_t ny {static_cast<hsize_t>(2 * n_rings_ - 1)};
55✔
1178
  hsize_t nz {static_cast<hsize_t>(n_axial_)};
55✔
1179
  vector<int> out(nx * ny * nz);
55✔
1180

1181
  for (int m = 0; m < nz; m++) {
143✔
1182
    for (int k = 0; k < ny; k++) {
1,364✔
1183
      for (int j = 0; j < nx; j++) {
26,004✔
1184
        int indx = nx * ny * m + nx * k + j;
24,728✔
1185
        if (j + k < n_rings_ - 1) {
24,728✔
1186
          // This array position is never used; put a -1 to indicate this.
1187
          out[indx] = -1;
3,080✔
1188
        } else if (j + k > 3 * n_rings_ - 3) {
21,648✔
1189
          // This array position is never used; put a -1 to indicate this.
1190
          out[indx] = -1;
3,080✔
1191
        } else {
1192
          out[indx] = model::universes[universes_[indx]]->id_;
18,568✔
1193
        }
1194
      }
1195
    }
1196
  }
1197

1198
  hsize_t dims[3] {nz, ny, nx};
55✔
1199
  write_int(lat_group, 3, dims, "universes", out.data(), false);
55✔
1200
}
55✔
1201

1202
//==============================================================================
1203
// Non-method functions
1204
//==============================================================================
1205

1206
void read_lattices(pugi::xml_node node)
8,873✔
1207
{
1208
  for (pugi::xml_node lat_node : node.children("lattice")) {
10,736✔
1209
    model::lattices.push_back(make_unique<RectLattice>(lat_node));
1,863✔
1210
  }
1211
  for (pugi::xml_node lat_node : node.children("hex_lattice")) {
8,959✔
1212
    model::lattices.push_back(make_unique<HexLattice>(lat_node));
86✔
1213
  }
1214

1215
  // Fill the lattice map.
1216
  for (int i_lat = 0; i_lat < model::lattices.size(); i_lat++) {
10,822✔
1217
    int id = model::lattices[i_lat]->id_;
1,949!
1218
    auto in_map = model::lattice_map.find(id);
1,949!
1219
    if (in_map == model::lattice_map.end()) {
1,949!
1220
      model::lattice_map[id] = i_lat;
1,949✔
1221
    } else {
UNCOV
1222
      fatal_error(
×
UNCOV
1223
        fmt::format("Two or more lattices use the same unique ID: {}", id));
×
1224
    }
1225
  }
1226
}
8,873✔
1227

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