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

openmc-dev / openmc / 23910205296

02 Apr 2026 04:14PM UTC coverage: 81.224% (-0.3%) from 81.567%
23910205296

Pull #3766

github

web-flow
Merge 264dcb1ef into 8223099ed
Pull Request #3766: Approximate multigroup velocity

17579 of 25426 branches covered (69.14%)

Branch coverage included in aggregate %.

24 of 25 new or added lines in 4 files covered. (96.0%)

710 existing lines in 29 files now uncovered.

58015 of 67642 relevant lines covered (85.77%)

31291841.44 hits per line

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

79.24
/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,023✔
33
{
34
  if (check_for_node(lat_node, "id")) {
1,023!
35
    id_ = std::stoi(get_node_value(lat_node, "id"));
2,046✔
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,023✔
41
    name_ = get_node_value(lat_node, "name");
192✔
42
  }
43

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

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

51
LatticeIter Lattice::begin()
158,079,954✔
52
{
53
  return LatticeIter(*this, 0);
158,079,954✔
54
}
55

56
LatticeIter Lattice::end()
37,321,471✔
57
{
58
  return LatticeIter(*this, universes_.size());
37,321,471✔
59
}
60

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

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

71
ReverseLatticeIter Lattice::rend()
157,662,456✔
72
{
73
  return ReverseLatticeIter(*this, -1);
157,662,456✔
74
}
75

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

78
void Lattice::adjust_indices()
1,023✔
79
{
80
  // Adjust the indices for the universes array.
81
  for (LatticeIter it = begin(); it != end(); ++it) {
492,583✔
82
    int uid = *it;
491,560!
83
    auto search = model::universe_map.find(uid);
491,560!
84
    if (search != model::universe_map.end()) {
491,560!
85
      *it = search->second;
491,560✔
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,023✔
94
    auto search = model::universe_map.find(outer_);
223!
95
    if (search != model::universe_map.end()) {
223!
96
      outer_ = search->second;
223✔
97
    } else {
98
      fatal_error(fmt::format(
×
99
        "Invalid universe number {} specified on lattice {}", outer_, id_));
×
100
    }
101
  }
102
}
1,023✔
103

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

106
int32_t Lattice::fill_offset_table(int32_t target_univ_id, int map,
9,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) {
9,561✔
115
    int last_offset =
18✔
116
      offsets_[(map + 1) * universes_.size() - this->begin().indx_ - 1];
18✔
117
    int last_univ = this->back();
18✔
118
    return last_offset +
18✔
119
           count_universe_instances(last_univ, target_univ_id, univ_count_memo);
18✔
120
  }
121

122
  int32_t offset = 0;
9,543✔
123
  for (LatticeIter it = begin(); it != end(); ++it) {
7,721,921✔
124
    offsets_[map * universes_.size() + it.indx_] = offset;
7,712,378✔
125
    offset += count_universe_instances(*it, target_univ_id, univ_count_memo);
7,712,378✔
126
  }
127

128
  return offset;
9,543✔
129
}
130

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

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

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

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

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

155
  close_group(lat_group);
801✔
156
}
801✔
157

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

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

166
  // Read the number of lattice cells in each dimension.
167
  std::string dimension_str {get_node_value(lat_node, "dimension")};
977✔
168
  vector<std::string> dimension_words {split(dimension_str)};
977✔
169
  if (dimension_words.size() == 2) {
977✔
170
    n_cells_[0] = std::stoi(dimension_words[0]);
714✔
171
    n_cells_[1] = std::stoi(dimension_words[1]);
714✔
172
    n_cells_[2] = 1;
714✔
173
    is_3d_ = false;
714✔
174
  } else if (dimension_words.size() == 3) {
263!
175
    n_cells_[0] = std::stoi(dimension_words[0]);
263✔
176
    n_cells_[1] = std::stoi(dimension_words[1]);
263✔
177
    n_cells_[2] = std::stoi(dimension_words[2]);
263✔
178
    is_3d_ = true;
263✔
179
  } else {
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")};
977✔
185
  vector<std::string> ll_words {split(ll_str)};
977✔
186
  if (ll_words.size() != dimension_words.size()) {
977!
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]);
977✔
191
  lower_left_[1] = stod(ll_words[1]);
977✔
192
  if (is_3d_) {
977✔
193
    lower_left_[2] = stod(ll_words[2]);
263✔
194
  }
195

196
  // Read the lattice pitches.
197
  std::string pitch_str {get_node_value(lat_node, "pitch")};
977✔
198
  vector<std::string> pitch_words {split(pitch_str)};
977✔
199
  if (pitch_words.size() != dimension_words.size()) {
977!
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]);
977✔
204
  pitch_[1] = stod(pitch_words[1]);
977✔
205
  if (is_3d_) {
977✔
206
    pitch_[2] = stod(pitch_words[2]);
263✔
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")};
977✔
211
  vector<std::string> univ_words {split(univ_str)};
977✔
212
  if (univ_words.size() != n_cells_[0] * n_cells_[1] * n_cells_[2]) {
977!
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],
×
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);
977✔
222
  for (int iz = 0; iz < n_cells_[2]; iz++) {
4,324✔
223
    for (int iy = n_cells_[1] - 1; iy > -1; iy--) {
40,638✔
224
      for (int ix = 0; ix < n_cells_[0]; ix++) {
515,305✔
225
        int indx1 = n_cells_[0] * n_cells_[1] * iz +
478,014✔
226
                    n_cells_[0] * (n_cells_[1] - iy - 1) + ix;
478,014✔
227
        int indx2 = n_cells_[0] * n_cells_[1] * iz + n_cells_[0] * iy + ix;
478,014✔
228
        universes_[indx1] = std::stoi(univ_words[indx2]);
478,014✔
229
      }
230
    }
231
  }
232
}
977✔
233

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

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

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

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

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

252
std::pair<double, array<int, 3>> RectLattice::distance(
682,460,702✔
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;
682,460,702✔
257
  double y = r.y;
682,460,702✔
258
  double z = r.z;
682,460,702✔
259

260
  // Determine the oncoming edge.
261
  double x0 {copysign(0.5 * pitch_[0], u.x)};
682,460,702✔
262
  double y0 {copysign(0.5 * pitch_[1], u.y)};
682,460,702✔
263
  double z0;
682,460,702✔
264

265
  double d = std::min(
1,364,921,404✔
266
    u.x != 0.0 ? (x0 - x) / u.x : INFTY, u.y != 0.0 ? (y0 - y) / u.y : INFTY);
682,460,702!
267
  if (is_3d_) {
682,460,702✔
268
    z0 = copysign(0.5 * pitch_[2], u.z);
405,447,250✔
269
    d = std::min(d, u.z != 0.0 ? (z0 - z) / u.z : INFTY);
520,383,318!
270
  }
271

272
  // Determine which lattice boundaries are being crossed
273
  array<int, 3> lattice_trans = {0, 0, 0};
682,460,702✔
274
  if (u.x != 0.0 && std::abs(x + u.x * d - x0) < FP_PRECISION)
682,460,702!
275
    lattice_trans[0] = copysign(1, u.x);
313,344,168✔
276
  if (u.y != 0.0 && std::abs(y + u.y * d - y0) < FP_PRECISION)
682,460,702!
277
    lattice_trans[1] = copysign(1, u.y);
254,299,103✔
278
  if (is_3d_) {
682,460,702✔
279
    if (u.z != 0.0 && std::abs(z + u.z * d - z0) < FP_PRECISION)
405,447,250!
280
      lattice_trans[2] = copysign(1, u.z);
114,934,736✔
281
  }
282

283
  return {d, lattice_trans};
682,460,702✔
284
}
285

286
//==============================================================================
287

288
void RectLattice::get_indices(
76,816,784✔
289
  Position r, Direction u, array<int, 3>& result) const
290
{
291
  // Determine x index, accounting for coincidence
292
  double ix_ {(r.x - lower_left_.x) / pitch_.x};
76,816,784✔
293
  long ix_close {std::lround(ix_)};
76,816,784✔
294
  if (coincident(ix_, ix_close)) {
76,816,784✔
295
    result[0] = (u.x > 0) ? ix_close : ix_close - 1;
24,002,104✔
296
  } else {
297
    result[0] = std::floor(ix_);
52,814,680✔
298
  }
299

300
  // Determine y index, accounting for coincidence
301
  double iy_ {(r.y - lower_left_.y) / pitch_.y};
76,816,784✔
302
  long iy_close {std::lround(iy_)};
76,816,784✔
303
  if (coincident(iy_, iy_close)) {
76,816,784✔
304
    result[1] = (u.y > 0) ? iy_close : iy_close - 1;
24,262,503✔
305
  } else {
306
    result[1] = std::floor(iy_);
52,554,281✔
307
  }
308

309
  // Determine z index, accounting for coincidence
310
  result[2] = 0;
76,816,784✔
311
  if (is_3d_) {
76,816,784✔
312
    double iz_ {(r.z - lower_left_.z) / pitch_.z};
38,479,070✔
313
    long iz_close {std::lround(iz_)};
38,479,070✔
314
    if (coincident(iz_, iz_close)) {
38,479,070✔
315
      result[2] = (u.z > 0) ? iz_close : iz_close - 1;
9,878,774✔
316
    } else {
317
      result[2] = std::floor(iz_);
28,600,296✔
318
    }
319
  }
320
}
76,816,784✔
321

322
int RectLattice::get_flat_index(const array<int, 3>& i_xyz) const
477,406,959✔
323
{
324
  return n_cells_[0] * n_cells_[1] * i_xyz[2] + n_cells_[0] * i_xyz[1] +
477,406,959✔
325
         i_xyz[0];
477,406,959✔
326
}
327

328
//==============================================================================
329

330
Position RectLattice::get_local_position(
479,718,273✔
331
  Position r, const array<int, 3>& i_xyz) const
332
{
333
  r.x -= (lower_left_.x + (i_xyz[0] + 0.5) * pitch_.x);
479,718,273✔
334
  r.y -= (lower_left_.y + (i_xyz[1] + 0.5) * pitch_.y);
479,718,273✔
335
  if (is_3d_) {
479,718,273✔
336
    r.z -= (lower_left_.z + (i_xyz[2] + 0.5) * pitch_.z);
378,815,346✔
337
  }
338
  return r;
479,718,273✔
339
}
340

341
//==============================================================================
342

UNCOV
343
Direction RectLattice::get_normal(
×
344
  const array<int, 3>& i_xyz, bool& is_valid) const
345
{
UNCOV
346
  is_valid = false;
×
UNCOV
347
  Direction dir = {0.0, 0.0, 0.0};
×
UNCOV
348
  if ((std::abs(i_xyz[0]) == 1) && (i_xyz[1] == 0) && (i_xyz[2] == 0)) {
×
UNCOV
349
    is_valid = true;
×
UNCOV
350
    dir[0] = std::copysign(1.0, i_xyz[0]);
×
UNCOV
351
  } else if ((i_xyz[0] == 0) && (std::abs(i_xyz[1]) == 1) && (i_xyz[2] == 0)) {
×
UNCOV
352
    is_valid = true;
×
UNCOV
353
    dir[1] = std::copysign(1.0, i_xyz[1]);
×
UNCOV
354
  } else if ((i_xyz[0] == 0) && (i_xyz[1] == 0) && (std::abs(i_xyz[2]) == 1)) {
×
UNCOV
355
    is_valid = true;
×
UNCOV
356
    dir[2] = std::copysign(1.0, i_xyz[2]);
×
357
  }
UNCOV
358
  return dir;
×
359
}
360

361
//==============================================================================
362

363
int32_t& RectLattice::offset(int map, const array<int, 3>& i_xyz)
681,658,466✔
364
{
365
  return offsets_[n_cells_[0] * n_cells_[1] * n_cells_[2] * map +
681,658,466✔
366
                  n_cells_[0] * n_cells_[1] * i_xyz[2] +
681,658,466✔
367
                  n_cells_[0] * i_xyz[1] + i_xyz[0]];
681,658,466✔
368
}
369

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

372
int32_t RectLattice::offset(int map, int indx) const
44,424✔
373
{
374
  return offsets_[n_cells_[0] * n_cells_[1] * n_cells_[2] * map + indx];
44,424✔
375
}
376

377
//==============================================================================
378

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

394
//==============================================================================
395

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

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

421
    for (int m = 0; m < nz; m++) {
2,246✔
422
      for (int k = 0; k < ny; k++) {
25,574✔
423
        for (int j = 0; j < nx; j++) {
313,241✔
424
          int indx1 = nx * ny * m + nx * k + j;
289,702✔
425
          int indx2 = nx * ny * m + nx * (ny - k - 1) + j;
289,702✔
426
          out[indx2] = model::universes[universes_[indx1]]->id_;
289,702✔
427
        }
428
      }
429
    }
430

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

434
  } else {
211✔
435
    hsize_t nx {static_cast<hsize_t>(n_cells_[0])};
560✔
436
    hsize_t ny {static_cast<hsize_t>(n_cells_[1])};
560✔
437
    vector<int> out(nx * ny);
560✔
438

439
    for (int k = 0; k < ny; k++) {
5,722✔
440
      for (int j = 0; j < nx; j++) {
85,200✔
441
        int indx1 = nx * k + j;
80,038✔
442
        int indx2 = nx * (ny - k - 1) + j;
80,038✔
443
        out[indx2] = model::universes[universes_[indx1]]->id_;
80,038✔
444
      }
445
    }
446

447
    hsize_t dims[3] {1, ny, nx};
560✔
448
    write_int(lat_group, 3, dims, "universes", out.data(), false);
560✔
449
  }
560✔
450
}
771✔
451

452
//==============================================================================
453
// HexLattice implementation
454
//==============================================================================
455

456
HexLattice::HexLattice(pugi::xml_node lat_node) : Lattice {lat_node}
46✔
457
{
458
  type_ = LatticeType::hex;
46✔
459

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

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

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

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

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

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

544
//==============================================================================
545

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

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

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

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

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

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

590
      // Return lattice index to start of current row.
591
      i_y -= 1;
176✔
592
    }
593
  }
594
}
8✔
595

596
//==============================================================================
597

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

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

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

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

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

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

653
      // Return the lattice index to the start of the current row.
654
      i_x -= 2 * (n_rings_ - (k % 2));
610✔
655
      i_a += n_rings_ - (k % 2);
610✔
656
    }
657

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

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

676
      // Return lattice index to start of current row.
677
      i_x -= 2 * (n_rings_ - k - 1);
278✔
678
      i_a += n_rings_ - k - 1;
278✔
679
    }
680
  }
681
}
38✔
682

683
//==============================================================================
684

685
const int32_t& HexLattice::operator[](const array<int, 3>& i_xyz)
15,974,160✔
686
{
687
  return universes_[get_flat_index(i_xyz)];
15,974,160✔
688
}
689

690
//==============================================================================
691

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

703
ReverseLatticeIter HexLattice::rbegin()
126✔
704
{
705
  return ReverseLatticeIter(*this, universes_.size() - n_rings_);
126✔
706
}
707

708
int32_t& HexLattice::back()
6✔
709
{
710
  return universes_[universes_.size() - n_rings_];
6✔
711
}
712

713
LatticeIter HexLattice::end()
343,018✔
714
{
715
  return LatticeIter(*this, universes_.size() - n_rings_ + 1);
343,018✔
716
}
717

718
ReverseLatticeIter HexLattice::rend()
504✔
719
{
720
  return ReverseLatticeIter(*this, n_rings_ - 2);
504✔
721
}
722

723
//==============================================================================
724

725
bool HexLattice::are_valid_indices(const array<int, 3>& i_xyz) const
61,980,352✔
726
{
727
  // Check if (x, alpha, z) indices are valid, accounting for number of rings
728
  return ((i_xyz[0] >= 0) && (i_xyz[1] >= 0) && (i_xyz[2] >= 0) &&
61,980,352✔
729
          (i_xyz[0] < 2 * n_rings_ - 1) && (i_xyz[1] < 2 * n_rings_ - 1) &&
60,169,426✔
730
          (i_xyz[0] + i_xyz[1] > n_rings_ - 2) &&
59,747,434✔
731
          (i_xyz[0] + i_xyz[1] < 3 * n_rings_ - 2) && (i_xyz[2] < n_axial_));
121,539,336✔
732
}
733

734
//==============================================================================
735

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

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

773
  // beta direction
774
  double d {INFTY};
46,638,162✔
775
  array<int, 3> lattice_trans;
46,638,162✔
776
  double edge = -copysign(0.5 * pitch_[0], beta_dir); // Oncoming edge
46,638,162✔
777
  Position r_t;
46,638,162✔
778
  if (beta_dir > 0) {
46,638,162✔
779
    const array<int, 3> i_xyz_t {i_xyz[0] + 1, i_xyz[1], i_xyz[2]};
23,327,298✔
780
    r_t = get_local_position(r, i_xyz_t);
23,327,298✔
781
  } else {
782
    const array<int, 3> i_xyz_t {i_xyz[0] - 1, i_xyz[1], i_xyz[2]};
23,310,864✔
783
    r_t = get_local_position(r, i_xyz_t);
23,310,864✔
784
  }
785
  double beta;
46,638,162✔
786
  if (orientation_ == Orientation::y) {
46,638,162✔
787
    beta = r_t.x * std::sqrt(3.0) / 2.0 + r_t.y / 2.0;
38,656,008✔
788
  } else {
789
    beta = r_t.x;
790
  }
791
  if ((std::abs(beta - edge) > FP_PRECISION) && beta_dir != 0) {
46,638,162!
792
    d = (edge - beta) / beta_dir;
46,638,162✔
793
    if (beta_dir > 0) {
46,638,162✔
794
      lattice_trans = {1, 0, 0};
795
    } else {
796
      lattice_trans = {-1, 0, 0};
23,310,864✔
797
    }
798
  }
799

800
  // gamma direction
801
  edge = -copysign(0.5 * pitch_[0], gamma_dir);
46,638,162✔
802
  if (gamma_dir > 0) {
46,638,162✔
803
    const array<int, 3> i_xyz_t {i_xyz[0] + 1, i_xyz[1] - 1, i_xyz[2]};
23,363,700✔
804
    r_t = get_local_position(r, i_xyz_t);
23,363,700✔
805
  } else {
806
    const array<int, 3> i_xyz_t {i_xyz[0] - 1, i_xyz[1] + 1, i_xyz[2]};
23,274,462✔
807
    r_t = get_local_position(r, i_xyz_t);
23,274,462✔
808
  }
809
  double gamma;
46,638,162✔
810
  if (orientation_ == Orientation::y) {
46,638,162✔
811
    gamma = r_t.x * std::sqrt(3.0) / 2.0 - r_t.y / 2.0;
38,656,008✔
812
  } else {
813
    gamma = r_t.x / 2.0 - r_t.y * std::sqrt(3.0) / 2.0;
7,982,154✔
814
  }
815
  if ((std::abs(gamma - edge) > FP_PRECISION) && gamma_dir != 0) {
46,638,162!
816
    double this_d = (edge - gamma) / gamma_dir;
46,638,162✔
817
    if (this_d < d) {
46,638,162✔
818
      if (gamma_dir > 0) {
23,340,954✔
819
        lattice_trans = {1, -1, 0};
820
      } else {
821
        lattice_trans = {-1, 1, 0};
11,622,222✔
822
      }
823
      d = this_d;
824
    }
825
  }
826

827
  // delta direction
828
  edge = -copysign(0.5 * pitch_[0], delta_dir);
46,638,162✔
829
  if (delta_dir > 0) {
46,638,162✔
830
    const array<int, 3> i_xyz_t {i_xyz[0], i_xyz[1] + 1, i_xyz[2]};
23,276,778✔
831
    r_t = get_local_position(r, i_xyz_t);
23,276,778✔
832
  } else {
833
    const array<int, 3> i_xyz_t {i_xyz[0], i_xyz[1] - 1, i_xyz[2]};
23,361,384✔
834
    r_t = get_local_position(r, i_xyz_t);
23,361,384✔
835
  }
836
  double delta;
46,638,162✔
837
  if (orientation_ == Orientation::y) {
46,638,162✔
838
    delta = r_t.y;
839
  } else {
840
    delta = r_t.x / 2.0 + r_t.y * std::sqrt(3.0) / 2.0;
7,982,154✔
841
  }
842
  if ((std::abs(delta - edge) > FP_PRECISION) && delta_dir != 0) {
46,638,162!
843
    double this_d = (edge - delta) / delta_dir;
46,638,162✔
844
    if (this_d < d) {
46,638,162✔
845
      if (delta_dir > 0) {
15,555,840✔
846
        lattice_trans = {0, 1, 0};
847
      } else {
848
        lattice_trans = {0, -1, 0};
7,791,468✔
849
      }
850
      d = this_d;
851
    }
852
  }
853

854
  // Top and bottom sides
855
  if (is_3d_) {
46,638,162✔
856
    double z = r.z;
11,025,288✔
857
    double z0 {copysign(0.5 * pitch_[1], u.z)};
11,025,288!
858
    if ((std::abs(z - z0) > FP_PRECISION) && u.z != 0) {
11,025,288!
859
      double this_d = (z0 - z) / u.z;
11,025,288✔
860
      if (this_d < d) {
11,025,288✔
861
        d = this_d;
1,670,328✔
862
        if (u.z > 0) {
1,670,328✔
863
          lattice_trans = {0, 0, 1};
864
        } else {
865
          lattice_trans = {0, 0, -1};
837,390✔
866
        }
867
        d = this_d;
868
      }
869
    }
870
  }
871

872
  return {d, lattice_trans};
46,638,162✔
873
}
874

875
//==============================================================================
876

877
void HexLattice::get_indices(
6,491,166✔
878
  Position r, Direction u, array<int, 3>& result) const
879
{
880
  // Offset the xyz by the lattice center.
881
  Position r_o {r.x - center_.x, r.y - center_.y, r.z};
6,491,166✔
882
  if (is_3d_) {
6,491,166✔
883
    r_o.z -= center_.z;
1,355,388✔
884
  }
885

886
  // Index the z direction, accounting for coincidence
887
  result[2] = 0;
6,491,166✔
888
  if (is_3d_) {
6,491,166✔
889
    double iz_ {r_o.z / pitch_[1] + 0.5 * n_axial_};
1,355,388✔
890
    long iz_close {std::lround(iz_)};
1,355,388✔
891
    if (coincident(iz_, iz_close)) {
1,355,388✔
892
      result[2] = (u.z > 0) ? iz_close : iz_close - 1;
417,654✔
893
    } else {
894
      result[2] = std::floor(iz_);
937,734✔
895
    }
896
  }
897

898
  if (orientation_ == Orientation::y) {
6,491,166✔
899
    // Convert coordinates into skewed bases.  The (x, alpha) basis is used to
900
    // find the index of the global coordinates to within 4 cells.
901
    double alpha = r_o.y - r_o.x / std::sqrt(3.0);
6,042,654✔
902
    result[0] = std::floor(r_o.x / (0.5 * std::sqrt(3.0) * pitch_[0]));
6,042,654✔
903
    result[1] = std::floor(alpha / pitch_[0]);
6,042,654✔
904
  } else {
905
    // Convert coordinates into skewed bases.  The (alpha, y) basis is used to
906
    // find the index of the global coordinates to within 4 cells.
907
    double alpha = r_o.y - r_o.x * std::sqrt(3.0);
448,512✔
908
    result[0] = std::floor(-alpha / (std::sqrt(3.0) * pitch_[0]));
448,512✔
909
    result[1] = std::floor(r_o.y / (0.5 * std::sqrt(3.0) * pitch_[0]));
448,512✔
910
  }
911

912
  // Add offset to indices (the center cell is (i1, i2) = (0, 0) but
913
  // the array is offset so that the indices never go below 0).
914
  result[0] += n_rings_ - 1;
6,491,166✔
915
  result[1] += n_rings_ - 1;
6,491,166✔
916

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

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

968
  // update outgoing indices
969
  result[0] += i1_chg;
6,491,166✔
970
  result[1] += i2_chg;
6,491,166✔
971
}
6,491,166✔
972

973
int HexLattice::get_flat_index(const array<int, 3>& i_xyz) const
15,974,160✔
974
{
975
  return (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * i_xyz[2] +
15,974,160✔
976
         (2 * n_rings_ - 1) * i_xyz[1] + i_xyz[0];
15,974,160✔
977
}
978

979
//==============================================================================
980

981
Position HexLattice::get_local_position(
184,232,028✔
982
  Position r, const array<int, 3>& i_xyz) const
983
{
984
  if (orientation_ == Orientation::y) {
184,232,028✔
985
    // x_l = x_g - (center + pitch_x*cos(30)*index_x)
986
    r.x -=
312,294,000✔
987
      center_.x + std::sqrt(3.0) / 2.0 * (i_xyz[0] - n_rings_ + 1) * pitch_[0];
156,147,000✔
988
    // y_l = y_g - (center + pitch_x*index_x + pitch_y*sin(30)*index_y)
989
    r.y -= (center_.y + (i_xyz[1] - n_rings_ + 1) * pitch_[0] +
156,147,000✔
990
            (i_xyz[0] - n_rings_ + 1) * pitch_[0] / 2.0);
156,147,000✔
991
  } else {
992
    // x_l = x_g - (center + pitch_x*index_a + pitch_y*sin(30)*index_y)
993
    r.x -= (center_.x + (i_xyz[0] - n_rings_ + 1) * pitch_[0] +
28,085,028✔
994
            (i_xyz[1] - n_rings_ + 1) * pitch_[0] / 2.0);
28,085,028✔
995
    // y_l = y_g - (center + pitch_y*cos(30)*index_y)
996
    r.y -=
28,085,028✔
997
      center_.y + std::sqrt(3.0) / 2.0 * (i_xyz[1] - n_rings_ + 1) * pitch_[0];
28,085,028✔
998
  }
999

1000
  if (is_3d_) {
184,232,028✔
1001
    r.z -= center_.z - (0.5 * n_axial_ - i_xyz[2] - 0.5) * pitch_[1];
42,879,090✔
1002
  }
1003

1004
  return r;
184,232,028✔
1005
}
1006

1007
//==============================================================================
1008

UNCOV
1009
Direction HexLattice::get_normal(
×
1010
  const array<int, 3>& i_xyz, bool& is_valid) const
1011
{
1012
  // Short description of the direction vectors used here.  The beta, gamma, and
1013
  // delta vectors point towards the flat sides of each hexagonal tile.
1014
  // Y - orientation:
1015
  //   basis0 = (1, 0)
1016
  //   basis1 = (-1/sqrt(3), 1)   = +120 degrees from basis0
1017
  //   beta   = (sqrt(3)/2, 1/2)  = +30 degrees from basis0
1018
  //   gamma  = (sqrt(3)/2, -1/2) = -60 degrees from beta
1019
  //   delta  = (0, 1)            = +60 degrees from beta
1020
  // X - orientation:
1021
  //   basis0 = (1/sqrt(3), -1)
1022
  //   basis1 = (0, 1)            = +120 degrees from basis0
1023
  //   beta   = (1, 0)            = +30 degrees from basis0
1024
  //   gamma  = (1/2, -sqrt(3)/2) = -60 degrees from beta
1025
  //   delta  = (1/2, sqrt(3)/2)  = +60 degrees from beta
1026

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

1092
//==============================================================================
1093

1094
bool HexLattice::is_valid_index(int indx) const
193,768✔
1095
{
1096
  int nx {2 * n_rings_ - 1};
193,768✔
1097
  int ny {2 * n_rings_ - 1};
193,768✔
1098
  int iz = indx / (nx * ny);
193,768✔
1099
  int iy = (indx - nx * ny * iz) / nx;
193,768✔
1100
  int ix = indx - nx * ny * iz - nx * iy;
193,768✔
1101
  array<int, 3> i_xyz {ix, iy, iz};
193,768✔
1102
  return are_valid_indices(i_xyz);
193,768✔
1103
}
1104

1105
//==============================================================================
1106

1107
int32_t& HexLattice::offset(int map, const array<int, 3>& i_xyz)
42,111,726✔
1108
{
1109
  int nx {2 * n_rings_ - 1};
42,111,726✔
1110
  int ny {2 * n_rings_ - 1};
42,111,726✔
1111
  int nz {n_axial_};
42,111,726✔
1112
  return offsets_[nx * ny * nz * map + nx * ny * i_xyz[2] + nx * i_xyz[1] +
42,111,726✔
1113
                  i_xyz[0]];
42,111,726✔
1114
}
1115

UNCOV
1116
int32_t HexLattice::offset(int map, int indx) const
×
1117
{
UNCOV
1118
  int nx {2 * n_rings_ - 1};
×
UNCOV
1119
  int ny {2 * n_rings_ - 1};
×
UNCOV
1120
  int nz {n_axial_};
×
UNCOV
1121
  return offsets_[nx * ny * nz * map + indx];
×
1122
}
1123

1124
//==============================================================================
1125

1126
std::string HexLattice::index_to_string(int indx) const
126✔
1127
{
1128
  int nx {2 * n_rings_ - 1};
126✔
1129
  int ny {2 * n_rings_ - 1};
126✔
1130
  int iz {indx / (nx * ny)};
126✔
1131
  int iy {(indx - nx * ny * iz) / nx};
126✔
1132
  int ix {indx - nx * ny * iz - nx * iy};
126✔
1133
  std::string out {std::to_string(ix - n_rings_ + 1)};
126✔
1134
  out += ',';
126✔
1135
  out += std::to_string(iy - n_rings_ + 1);
252✔
1136
  if (is_3d_) {
126!
UNCOV
1137
    out += ',';
×
UNCOV
1138
    out += std::to_string(iz);
×
1139
  }
1140
  return out;
126✔
UNCOV
1141
}
×
1142

1143
//==============================================================================
1144

1145
void HexLattice::to_hdf5_inner(hid_t lat_group) const
30✔
1146
{
1147
  // Write basic lattice information.
1148
  write_string(lat_group, "type", "hexagonal", false);
30✔
1149
  write_dataset(lat_group, "n_rings", n_rings_);
30✔
1150
  write_dataset(lat_group, "n_axial", n_axial_);
30✔
1151
  if (orientation_ == Orientation::y) {
30✔
1152
    write_string(lat_group, "orientation", "y", false);
48✔
1153
  } else {
1154
    write_string(lat_group, "orientation", "x", false);
12✔
1155
  }
1156
  if (is_3d_) {
30✔
1157
    write_dataset(lat_group, "pitch", pitch_);
12✔
1158
    write_dataset(lat_group, "center", center_);
12✔
1159
  } else {
1160
    array<double, 1> pitch_short {{pitch_[0]}};
18✔
1161
    write_dataset(lat_group, "pitch", pitch_short);
18✔
1162
    array<double, 2> center_short {{center_[0], center_[1]}};
18✔
1163
    write_dataset(lat_group, "center", center_short);
18✔
1164
  }
1165

1166
  // Write the universe ids.
1167
  hsize_t nx {static_cast<hsize_t>(2 * n_rings_ - 1)};
30✔
1168
  hsize_t ny {static_cast<hsize_t>(2 * n_rings_ - 1)};
30✔
1169
  hsize_t nz {static_cast<hsize_t>(n_axial_)};
30✔
1170
  vector<int> out(nx * ny * nz);
30✔
1171

1172
  for (int m = 0; m < nz; m++) {
78✔
1173
    for (int k = 0; k < ny; k++) {
744✔
1174
      for (int j = 0; j < nx; j++) {
14,184✔
1175
        int indx = nx * ny * m + nx * k + j;
13,488✔
1176
        if (j + k < n_rings_ - 1) {
13,488✔
1177
          // This array position is never used; put a -1 to indicate this.
1178
          out[indx] = -1;
1,680✔
1179
        } else if (j + k > 3 * n_rings_ - 3) {
11,808✔
1180
          // This array position is never used; put a -1 to indicate this.
1181
          out[indx] = -1;
1,680✔
1182
        } else {
1183
          out[indx] = model::universes[universes_[indx]]->id_;
10,128✔
1184
        }
1185
      }
1186
    }
1187
  }
1188

1189
  hsize_t dims[3] {nz, ny, nx};
30✔
1190
  write_int(lat_group, 3, dims, "universes", out.data(), false);
30✔
1191
}
30✔
1192

1193
//==============================================================================
1194
// Non-method functions
1195
//==============================================================================
1196

1197
void read_lattices(pugi::xml_node node)
4,472✔
1198
{
1199
  for (pugi::xml_node lat_node : node.children("lattice")) {
5,449✔
1200
    model::lattices.push_back(make_unique<RectLattice>(lat_node));
977✔
1201
  }
1202
  for (pugi::xml_node lat_node : node.children("hex_lattice")) {
4,518✔
1203
    model::lattices.push_back(make_unique<HexLattice>(lat_node));
46✔
1204
  }
1205

1206
  // Fill the lattice map.
1207
  for (int i_lat = 0; i_lat < model::lattices.size(); i_lat++) {
5,495✔
1208
    int id = model::lattices[i_lat]->id_;
1,023!
1209
    auto in_map = model::lattice_map.find(id);
1,023!
1210
    if (in_map == model::lattice_map.end()) {
1,023!
1211
      model::lattice_map[id] = i_lat;
1,023✔
1212
    } else {
UNCOV
1213
      fatal_error(
×
UNCOV
1214
        fmt::format("Two or more lattices use the same unique ID: {}", id));
×
1215
    }
1216
  }
1217
}
4,472✔
1218

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