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

openmc-dev / openmc / 20868058727

09 Jan 2026 11:00PM UTC coverage: 82.202% (+0.03%) from 82.171%
20868058727

Pull #3705

github

web-flow
Merge 233dcfa80 into 8c8867ea1
Pull Request #3705: Flux Energy Group Conversion using lethargy-weighted redistribution

17109 of 23668 branches covered (72.29%)

Branch coverage included in aggregate %.

34 of 39 new or added lines in 2 files covered. (87.18%)

265 existing lines in 9 files now uncovered.

55259 of 64369 relevant lines covered (85.85%)

43504872.3 hits per line

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

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

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

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

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

56
LatticeIter Lattice::end()
49,435,687✔
57
{
58
  return LatticeIter(*this, universes_.size());
49,435,687✔
59
}
60

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

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

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

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

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

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

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

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

128
  return offset;
12,707✔
129
}
130

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

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

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

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

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

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

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

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

166
  // Read the number of lattice cells in each dimension.
167
  std::string dimension_str {get_node_value(lat_node, "dimension")};
1,779✔
168
  vector<std::string> dimension_words {split(dimension_str)};
1,779✔
169
  if (dimension_words.size() == 2) {
1,779✔
170
    n_cells_[0] = std::stoi(dimension_words[0]);
1,317✔
171
    n_cells_[1] = std::stoi(dimension_words[1]);
1,317✔
172
    n_cells_[2] = 1;
1,317✔
173
    is_3d_ = false;
1,317✔
174
  } else if (dimension_words.size() == 3) {
462!
175
    n_cells_[0] = std::stoi(dimension_words[0]);
462✔
176
    n_cells_[1] = std::stoi(dimension_words[1]);
462✔
177
    n_cells_[2] = std::stoi(dimension_words[2]);
462✔
178
    is_3d_ = true;
462✔
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")};
1,779✔
185
  vector<std::string> ll_words {split(ll_str)};
1,779✔
186
  if (ll_words.size() != dimension_words.size()) {
1,779!
187
    fatal_error("Number of entries on <lower_left> must be the same as the "
×
188
                "number of entries on <dimension>.");
189
  }
190
  lower_left_[0] = stod(ll_words[0]);
1,779✔
191
  lower_left_[1] = stod(ll_words[1]);
1,779✔
192
  if (is_3d_) {
1,779✔
193
    lower_left_[2] = stod(ll_words[2]);
462✔
194
  }
195

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

209
  // Read the universes and make sure the correct number was specified.
210
  std::string univ_str {get_node_value(lat_node, "universes")};
1,779✔
211
  vector<std::string> univ_words {split(univ_str)};
1,779✔
212
  if (univ_words.size() != n_cells_[0] * n_cells_[1] * n_cells_[2]) {
1,779!
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);
1,779✔
222
  for (int iz = 0; iz < n_cells_[2]; iz++) {
8,046✔
223
    for (int iy = n_cells_[1] - 1; iy > -1; iy--) {
76,746✔
224
      for (int ix = 0; ix < n_cells_[0]; ix++) {
966,662✔
225
        int indx1 = n_cells_[0] * n_cells_[1] * iz +
896,183✔
226
                    n_cells_[0] * (n_cells_[1] - iy - 1) + ix;
896,183✔
227
        int indx2 = n_cells_[0] * n_cells_[1] * iz + n_cells_[0] * iy + ix;
896,183✔
228
        universes_[indx1] = std::stoi(univ_words[indx2]);
896,183✔
229
      }
230
    }
231
  }
232
}
1,779✔
233

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

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

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

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

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

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

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

265
  double d = std::min(
2,147,483,647✔
266
    u.x != 0.0 ? (x0 - x) / u.x : INFTY, u.y != 0.0 ? (y0 - y) / u.y : INFTY);
1,161,741,676!
267
  if (is_3d_) {
1,161,741,676✔
268
    z0 = copysign(0.5 * pitch_[2], u.z);
679,695,644✔
269
    d = std::min(d, u.z != 0.0 ? (z0 - z) / u.z : INFTY);
679,695,644!
270
  }
271

272
  // Determine which lattice boundaries are being crossed
273
  array<int, 3> lattice_trans = {0, 0, 0};
1,161,741,676✔
274
  if (u.x != 0.0 && std::abs(x + u.x * d - x0) < FP_PRECISION)
1,161,741,676!
275
    lattice_trans[0] = copysign(1, u.x);
501,411,583✔
276
  if (u.y != 0.0 && std::abs(y + u.y * d - y0) < FP_PRECISION)
1,161,741,676!
277
    lattice_trans[1] = copysign(1, u.y);
451,385,365✔
278
  if (is_3d_) {
1,161,741,676✔
279
    if (u.z != 0.0 && std::abs(z + u.z * d - z0) < FP_PRECISION)
679,695,644!
280
      lattice_trans[2] = copysign(1, u.z);
209,162,361✔
281
  }
282

283
  return {d, lattice_trans};
2,147,483,647✔
284
}
285

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

288
void RectLattice::get_indices(
131,738,116✔
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};
131,738,116✔
293
  long ix_close {std::lround(ix_)};
131,738,116✔
294
  if (coincident(ix_, ix_close)) {
131,738,116✔
295
    result[0] = (u.x > 0) ? ix_close : ix_close - 1;
42,931,385✔
296
  } else {
297
    result[0] = std::floor(ix_);
88,806,731✔
298
  }
299

300
  // Determine y index, accounting for coincidence
301
  double iy_ {(r.y - lower_left_.y) / pitch_.y};
131,738,116✔
302
  long iy_close {std::lround(iy_)};
131,738,116✔
303
  if (coincident(iy_, iy_close)) {
131,738,116✔
304
    result[1] = (u.y > 0) ? iy_close : iy_close - 1;
43,372,446✔
305
  } else {
306
    result[1] = std::floor(iy_);
88,365,670✔
307
  }
308

309
  // Determine z index, accounting for coincidence
310
  result[2] = 0;
131,738,116✔
311
  if (is_3d_) {
131,738,116✔
312
    double iz_ {(r.z - lower_left_.z) / pitch_.z};
67,327,912✔
313
    long iz_close {std::lround(iz_)};
67,327,912✔
314
    if (coincident(iz_, iz_close)) {
67,327,912✔
315
      result[2] = (u.z > 0) ? iz_close : iz_close - 1;
17,834,973✔
316
    } else {
317
      result[2] = std::floor(iz_);
49,492,939✔
318
    }
319
  }
320
}
131,738,116✔
321

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

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

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

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

343
int32_t& RectLattice::offset(int map, const array<int, 3>& i_xyz)
1,180,961,032✔
344
{
345
  return offsets_[n_cells_[0] * n_cells_[1] * n_cells_[2] * map +
1,180,961,032✔
346
                  n_cells_[0] * n_cells_[1] * i_xyz[2] +
1,180,961,032✔
347
                  n_cells_[0] * i_xyz[1] + i_xyz[0]];
2,147,483,647✔
348
}
349

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

352
int32_t RectLattice::offset(int map, int indx) const
88,848✔
353
{
354
  return offsets_[n_cells_[0] * n_cells_[1] * n_cells_[2] * map + indx];
88,848✔
355
}
356

357
//==============================================================================
358

359
std::string RectLattice::index_to_string(int indx) const
1,638,296✔
360
{
361
  int iz {indx / (n_cells_[0] * n_cells_[1])};
1,638,296✔
362
  int iy {(indx - n_cells_[0] * n_cells_[1] * iz) / n_cells_[0]};
1,638,296✔
363
  int ix {indx - n_cells_[0] * n_cells_[1] * iz - n_cells_[0] * iy};
1,638,296✔
364
  std::string out {std::to_string(ix)};
1,638,296✔
365
  out += ',';
1,638,296✔
366
  out += std::to_string(iy);
1,638,296✔
367
  if (is_3d_) {
1,638,296!
UNCOV
368
    out += ',';
×
UNCOV
369
    out += std::to_string(iz);
×
370
  }
371
  return out;
1,638,296✔
UNCOV
372
}
×
373

374
//==============================================================================
375

376
void RectLattice::to_hdf5_inner(hid_t lat_group) const
1,288✔
377
{
378
  // Write basic lattice information.
379
  write_string(lat_group, "type", "rectangular", false);
1,288✔
380
  if (is_3d_) {
1,288✔
381
    write_dataset(lat_group, "pitch", pitch_);
337✔
382
    write_dataset(lat_group, "lower_left", lower_left_);
337✔
383
    write_dataset(lat_group, "dimension", n_cells_);
337✔
384
  } else {
385
    array<double, 2> pitch_short {{pitch_[0], pitch_[1]}};
951✔
386
    write_dataset(lat_group, "pitch", pitch_short);
951✔
387
    array<double, 2> ll_short {{lower_left_[0], lower_left_[1]}};
951✔
388
    write_dataset(lat_group, "lower_left", ll_short);
951✔
389
    array<int, 2> nc_short {{n_cells_[0], n_cells_[1]}};
951✔
390
    write_dataset(lat_group, "dimension", nc_short);
951✔
391
  }
392

393
  // Write the universe ids.  The convention here is to switch the ordering on
394
  // the y-axis to match the way universes are input in a text file.
395
  if (is_3d_) {
1,288✔
396
    hsize_t nx {static_cast<hsize_t>(n_cells_[0])};
337✔
397
    hsize_t ny {static_cast<hsize_t>(n_cells_[1])};
337✔
398
    hsize_t nz {static_cast<hsize_t>(n_cells_[2])};
337✔
399
    vector<int> out(nx * ny * nz);
337✔
400

401
    for (int m = 0; m < nz; m++) {
3,852✔
402
      for (int k = 0; k < ny; k++) {
44,552✔
403
        for (int j = 0; j < nx; j++) {
538,166✔
404
          int indx1 = nx * ny * m + nx * k + j;
497,129✔
405
          int indx2 = nx * ny * m + nx * (ny - k - 1) + j;
497,129✔
406
          out[indx2] = model::universes[universes_[indx1]]->id_;
497,129✔
407
        }
408
      }
409
    }
410

411
    hsize_t dims[3] {nz, ny, nx};
337✔
412
    write_int(lat_group, 3, dims, "universes", out.data(), false);
337✔
413

414
  } else {
337✔
415
    hsize_t nx {static_cast<hsize_t>(n_cells_[0])};
951✔
416
    hsize_t ny {static_cast<hsize_t>(n_cells_[1])};
951✔
417
    vector<int> out(nx * ny);
951✔
418

419
    for (int k = 0; k < ny; k++) {
9,756✔
420
      for (int j = 0; j < nx; j++) {
146,850✔
421
        int indx1 = nx * k + j;
138,045✔
422
        int indx2 = nx * (ny - k - 1) + j;
138,045✔
423
        out[indx2] = model::universes[universes_[indx1]]->id_;
138,045✔
424
      }
425
    }
426

427
    hsize_t dims[3] {1, ny, nx};
951✔
428
    write_int(lat_group, 3, dims, "universes", out.data(), false);
951✔
429
  }
951✔
430
}
1,288✔
431

432
//==============================================================================
433
// HexLattice implementation
434
//==============================================================================
435

436
HexLattice::HexLattice(pugi::xml_node lat_node) : Lattice {lat_node}
91✔
437
{
438
  type_ = LatticeType::hex;
91✔
439

440
  // Read the number of lattice cells in each dimension.
441
  n_rings_ = std::stoi(get_node_value(lat_node, "n_rings"));
91✔
442
  if (check_for_node(lat_node, "n_axial")) {
91✔
443
    n_axial_ = std::stoi(get_node_value(lat_node, "n_axial"));
32✔
444
    is_3d_ = true;
32✔
445
  } else {
446
    n_axial_ = 1;
59✔
447
    is_3d_ = false;
59✔
448
  }
449

450
  // Read the lattice orientation.  Default to 'y'.
451
  if (check_for_node(lat_node, "orientation")) {
91✔
452
    std::string orientation = get_node_value(lat_node, "orientation");
16✔
453
    if (orientation == "y") {
16!
UNCOV
454
      orientation_ = Orientation::y;
×
455
    } else if (orientation == "x") {
16!
456
      orientation_ = Orientation::x;
16✔
457
    } else {
UNCOV
458
      fatal_error("Unrecognized orientation '" + orientation +
×
UNCOV
459
                  "' for lattice " + std::to_string(id_));
×
460
    }
461
  } else {
16✔
462
    orientation_ = Orientation::y;
75✔
463
  }
464

465
  // Read the lattice center.
466
  std::string center_str {get_node_value(lat_node, "center")};
91✔
467
  vector<std::string> center_words {split(center_str)};
91✔
468
  if (is_3d_ && (center_words.size() != 3)) {
91!
UNCOV
469
    fatal_error("A hexagonal lattice with <n_axial> must have <center> "
×
470
                "specified by 3 numbers.");
471
  } else if (!is_3d_ && center_words.size() != 2) {
91!
UNCOV
472
    fatal_error("A hexagonal lattice without <n_axial> must have <center> "
×
473
                "specified by 2 numbers.");
474
  }
475
  center_[0] = stod(center_words[0]);
91✔
476
  center_[1] = stod(center_words[1]);
91✔
477
  if (is_3d_) {
91✔
478
    center_[2] = stod(center_words[2]);
32✔
479
  }
480

481
  // Read the lattice pitches.
482
  std::string pitch_str {get_node_value(lat_node, "pitch")};
91✔
483
  vector<std::string> pitch_words {split(pitch_str)};
91✔
484
  if (is_3d_ && (pitch_words.size() != 2)) {
91!
UNCOV
485
    fatal_error("A hexagonal lattice with <n_axial> must have <pitch> "
×
486
                "specified by 2 numbers.");
487
  } else if (!is_3d_ && (pitch_words.size() != 1)) {
91!
UNCOV
488
    fatal_error("A hexagonal lattice without <n_axial> must have <pitch> "
×
489
                "specified by 1 number.");
490
  }
491
  pitch_[0] = stod(pitch_words[0]);
91✔
492
  if (is_3d_) {
91✔
493
    pitch_[1] = stod(pitch_words[1]);
32✔
494
  }
495

496
  // Read the universes and make sure the correct number was specified.
497
  int n_univ = (3 * n_rings_ * n_rings_ - 3 * n_rings_ + 1) * n_axial_;
91✔
498
  std::string univ_str {get_node_value(lat_node, "universes")};
91✔
499
  vector<std::string> univ_words {split(univ_str)};
91✔
500
  if (univ_words.size() != n_univ) {
91!
UNCOV
501
    fatal_error(fmt::format(
×
502
      "Expected {} universes for a hexagonal lattice with {} rings and {} "
503
      "axial levels but {} were specified.",
UNCOV
504
      n_univ, n_rings_, n_axial_, univ_words.size()));
×
505
  }
506

507
  // Parse the universes.
508
  // Universes in hexagonal lattices are stored in a manner that represents
509
  // a skewed coordinate system: (x, alpha) in case of 'y' orientation
510
  // and (alpha,y) in 'x' one rather than (x, y).  There is
511
  // no obvious, direct relationship between the order of universes in the
512
  // input and the order that they will be stored in the skewed array so
513
  // the following code walks a set of index values across the skewed array
514
  // in a manner that matches the input order.  Note that i_x = 0, i_a = 0
515
  // or i_a = 0, i_y = 0 corresponds to the center of the hexagonal lattice.
516
  universes_.resize((2 * n_rings_ - 1) * (2 * n_rings_ - 1) * n_axial_, C_NONE);
91✔
517
  if (orientation_ == Orientation::y) {
91✔
518
    fill_lattice_y(univ_words);
75✔
519
  } else {
520
    fill_lattice_x(univ_words);
16✔
521
  }
522
}
91✔
523

524
//==============================================================================
525

526
void HexLattice::fill_lattice_x(const vector<std::string>& univ_words)
16✔
527
{
528
  int input_index = 0;
16✔
529
  for (int m = 0; m < n_axial_; m++) {
48✔
530
    // Initialize lattice indecies.
531
    int i_a = -(n_rings_ - 1);
32✔
532
    int i_y = n_rings_ - 1;
32✔
533

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

538
      // Iterate over the input columns.
539
      for (int j = 0; j < k + n_rings_; j++) {
5,280✔
540
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
4,960✔
541
                   (2 * n_rings_ - 1) * (i_y + n_rings_ - 1) +
4,960✔
542
                   (i_a + n_rings_ - 1);
4,960✔
543
        universes_[indx] = std::stoi(univ_words[input_index]);
4,960✔
544
        input_index++;
4,960✔
545
        // Move to the next right neighbour cell
546
        i_a += 1;
4,960✔
547
      }
548

549
      // Return the lattice index to the start of the current row.
550
      i_a = -(n_rings_ - 1);
320✔
551
      i_y -= 1;
320✔
552
    }
553

554
    // Map the lower region from the centerline of cart to down side
555
    for (int k = 0; k < n_rings_; k++) {
384✔
556
      // Walk the index to the lower-right neighbor of the last row start.
557
      i_a = -(n_rings_ - 1) + k;
352✔
558

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

570
      // Return lattice index to start of current row.
571
      i_y -= 1;
352✔
572
    }
573
  }
574
}
16✔
575

576
//==============================================================================
577

578
void HexLattice::fill_lattice_y(const vector<std::string>& univ_words)
75✔
579
{
580
  int input_index = 0;
75✔
581
  for (int m = 0; m < n_axial_; m++) {
182✔
582
    // Initialize lattice indecies.
583
    int i_x = 1;
107✔
584
    int i_a = n_rings_ - 1;
107✔
585

586
    // Map upper triangular region of hexagonal lattice which is found in the
587
    // first n_rings-1 rows of the input.
588
    for (int k = 0; k < n_rings_ - 1; k++) {
662✔
589
      // Walk the index to lower-left neighbor of last row start.
590
      i_x -= 1;
555✔
591

592
      // Iterate over the input columns.
593
      for (int j = 0; j < k + 1; j++) {
3,286✔
594
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
2,731✔
595
                   (2 * n_rings_ - 1) * (i_a + n_rings_ - 1) +
2,731✔
596
                   (i_x + n_rings_ - 1);
2,731✔
597
        universes_[indx] = std::stoi(univ_words[input_index]);
2,731✔
598
        input_index++;
2,731✔
599
        // Walk the index to the right neighbor (which is not adjacent).
600
        i_x += 2;
2,731✔
601
        i_a -= 1;
2,731✔
602
      }
603

604
      // Return the lattice index to the start of the current row.
605
      i_x -= 2 * (k + 1);
555✔
606
      i_a += (k + 1);
555✔
607
    }
608

609
    // Map the middle square region of the hexagonal lattice which is found in
610
    // the next 2*n_rings-1 rows of the input.
611
    for (int k = 0; k < 2 * n_rings_ - 1; k++) {
1,324✔
612
      if ((k % 2) == 0) {
1,217✔
613
        // Walk the index to the lower-left neighbor of the last row start.
614
        i_x -= 1;
662✔
615
      } else {
616
        // Walk the index to the lower-right neighbor of the last row start.
617
        i_x += 1;
555✔
618
        i_a -= 1;
555✔
619
      }
620

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

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

638
    // Map the lower triangular region of the hexagonal lattice.
639
    for (int k = 0; k < n_rings_ - 1; k++) {
662✔
640
      // Walk the index to the lower-right neighbor of the last row start.
641
      i_x += 1;
555✔
642
      i_a -= 1;
555✔
643

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

656
      // Return lattice index to start of current row.
657
      i_x -= 2 * (n_rings_ - k - 1);
555✔
658
      i_a += n_rings_ - k - 1;
555✔
659
    }
660
  }
661
}
75✔
662

663
//==============================================================================
664

665
const int32_t& HexLattice::operator[](const array<int, 3>& i_xyz)
29,285,960✔
666
{
667
  return universes_[get_flat_index(i_xyz)];
29,285,960✔
668
}
669

670
//==============================================================================
671

672
// The HexLattice iterators need their own versions b/c the universes array is
673
// "square", meaning that it is allocated with entries that are intentionally
674
// left empty. As such, the iterator indices need to skip the empty entries to
675
// get cell instances and geometry paths correct. See the image in the Theory
676
// and Methodology section on "Hexagonal Lattice Indexing" for a visual of where
677
// the empty positions are.
678
LatticeIter HexLattice::begin()
2,151✔
679
{
680
  return LatticeIter(*this, n_rings_ - 1);
2,151✔
681
}
682

683
ReverseLatticeIter HexLattice::rbegin()
231✔
684
{
685
  return ReverseLatticeIter(*this, universes_.size() - n_rings_);
231✔
686
}
687

688
int32_t& HexLattice::back()
11✔
689
{
690
  return universes_[universes_.size() - n_rings_];
11✔
691
}
692

693
LatticeIter HexLattice::end()
685,917✔
694
{
695
  return LatticeIter(*this, universes_.size() - n_rings_ + 1);
685,917✔
696
}
697

698
ReverseLatticeIter HexLattice::rend()
924✔
699
{
700
  return ReverseLatticeIter(*this, n_rings_ - 2);
924✔
701
}
702

703
//==============================================================================
704

705
bool HexLattice::are_valid_indices(const array<int, 3>& i_xyz) const
113,662,828✔
706
{
707
  // Check if (x, alpha, z) indices are valid, accounting for number of rings
708
  return ((i_xyz[0] >= 0) && (i_xyz[1] >= 0) && (i_xyz[2] >= 0) &&
226,743,250✔
709
          (i_xyz[0] < 2 * n_rings_ - 1) && (i_xyz[1] < 2 * n_rings_ - 1) &&
110,342,797✔
710
          (i_xyz[0] + i_xyz[1] > n_rings_ - 2) &&
109,569,145✔
711
          (i_xyz[0] + i_xyz[1] < 3 * n_rings_ - 2) && (i_xyz[2] < n_axial_));
226,743,250✔
712
}
713

714
//==============================================================================
715

716
std::pair<double, array<int, 3>> HexLattice::distance(
85,503,297✔
717
  Position r, Direction u, const array<int, 3>& i_xyz) const
718
{
719
  // Short description of the direction vectors used here.  The beta, gamma, and
720
  // delta vectors point towards the flat sides of each hexagonal tile.
721
  // Y - orientation:
722
  //   basis0 = (1, 0)
723
  //   basis1 = (-1/sqrt(3), 1)   = +120 degrees from basis0
724
  //   beta   = (sqrt(3)/2, 1/2)  = +30 degrees from basis0
725
  //   gamma  = (sqrt(3)/2, -1/2) = -60 degrees from beta
726
  //   delta  = (0, 1)            = +60 degrees from beta
727
  // X - orientation:
728
  //   basis0 = (1/sqrt(3), -1)
729
  //   basis1 = (0, 1)            = +120 degrees from basis0
730
  //   beta   = (1, 0)            = +30 degrees from basis0
731
  //   gamma  = (1/2, -sqrt(3)/2) = -60 degrees from beta
732
  //   delta  = (1/2, sqrt(3)/2)  = +60 degrees from beta
733
  // The z-axis is considered separately.
734
  double beta_dir;
735
  double gamma_dir;
736
  double delta_dir;
737
  if (orientation_ == Orientation::y) {
85,503,297✔
738
    beta_dir = u.x * std::sqrt(3.0) / 2.0 + u.y / 2.0;
70,869,348✔
739
    gamma_dir = u.x * std::sqrt(3.0) / 2.0 - u.y / 2.0;
70,869,348✔
740
    delta_dir = u.y;
70,869,348✔
741
  } else {
742
    beta_dir = u.x;
14,633,949✔
743
    gamma_dir = u.x / 2.0 - u.y * std::sqrt(3.0) / 2.0;
14,633,949✔
744
    delta_dir = u.x / 2.0 + u.y * std::sqrt(3.0) / 2.0;
14,633,949✔
745
  }
746

747
  // Note that hexagonal lattice distance calculations are performed
748
  // using the particle's coordinates relative to the neighbor lattice
749
  // cells, not relative to the particle's current cell.  This is done
750
  // because there is significant disagreement between neighboring cells
751
  // on where the lattice boundary is due to finite precision issues.
752

753
  // beta direction
754
  double d {INFTY};
85,503,297✔
755
  array<int, 3> lattice_trans;
756
  double edge = -copysign(0.5 * pitch_[0], beta_dir); // Oncoming edge
85,503,297✔
757
  Position r_t;
85,503,297✔
758
  if (beta_dir > 0) {
85,503,297✔
759
    const array<int, 3> i_xyz_t {i_xyz[0] + 1, i_xyz[1], i_xyz[2]};
42,766,713✔
760
    r_t = get_local_position(r, i_xyz_t);
42,766,713✔
761
  } else {
762
    const array<int, 3> i_xyz_t {i_xyz[0] - 1, i_xyz[1], i_xyz[2]};
42,736,584✔
763
    r_t = get_local_position(r, i_xyz_t);
42,736,584✔
764
  }
765
  double beta;
766
  if (orientation_ == Orientation::y) {
85,503,297✔
767
    beta = r_t.x * std::sqrt(3.0) / 2.0 + r_t.y / 2.0;
70,869,348✔
768
  } else {
769
    beta = r_t.x;
14,633,949✔
770
  }
771
  if ((std::abs(beta - edge) > FP_PRECISION) && beta_dir != 0) {
85,503,297!
772
    d = (edge - beta) / beta_dir;
85,503,297✔
773
    if (beta_dir > 0) {
85,503,297✔
774
      lattice_trans = {1, 0, 0};
42,766,713✔
775
    } else {
776
      lattice_trans = {-1, 0, 0};
42,736,584✔
777
    }
778
  }
779

780
  // gamma direction
781
  edge = -copysign(0.5 * pitch_[0], gamma_dir);
85,503,297✔
782
  if (gamma_dir > 0) {
85,503,297✔
783
    const array<int, 3> i_xyz_t {i_xyz[0] + 1, i_xyz[1] - 1, i_xyz[2]};
42,833,450✔
784
    r_t = get_local_position(r, i_xyz_t);
42,833,450✔
785
  } else {
786
    const array<int, 3> i_xyz_t {i_xyz[0] - 1, i_xyz[1] + 1, i_xyz[2]};
42,669,847✔
787
    r_t = get_local_position(r, i_xyz_t);
42,669,847✔
788
  }
789
  double gamma;
790
  if (orientation_ == Orientation::y) {
85,503,297✔
791
    gamma = r_t.x * std::sqrt(3.0) / 2.0 - r_t.y / 2.0;
70,869,348✔
792
  } else {
793
    gamma = r_t.x / 2.0 - r_t.y * std::sqrt(3.0) / 2.0;
14,633,949✔
794
  }
795
  if ((std::abs(gamma - edge) > FP_PRECISION) && gamma_dir != 0) {
85,503,297!
796
    double this_d = (edge - gamma) / gamma_dir;
85,503,297✔
797
    if (this_d < d) {
85,503,297✔
798
      if (gamma_dir > 0) {
42,791,749✔
799
        lattice_trans = {1, -1, 0};
21,484,342✔
800
      } else {
801
        lattice_trans = {-1, 1, 0};
21,307,407✔
802
      }
803
      d = this_d;
42,791,749✔
804
    }
805
  }
806

807
  // delta direction
808
  edge = -copysign(0.5 * pitch_[0], delta_dir);
85,503,297✔
809
  if (delta_dir > 0) {
85,503,297✔
810
    const array<int, 3> i_xyz_t {i_xyz[0], i_xyz[1] + 1, i_xyz[2]};
42,674,093✔
811
    r_t = get_local_position(r, i_xyz_t);
42,674,093✔
812
  } else {
813
    const array<int, 3> i_xyz_t {i_xyz[0], i_xyz[1] - 1, i_xyz[2]};
42,829,204✔
814
    r_t = get_local_position(r, i_xyz_t);
42,829,204✔
815
  }
816
  double delta;
817
  if (orientation_ == Orientation::y) {
85,503,297✔
818
    delta = r_t.y;
70,869,348✔
819
  } else {
820
    delta = r_t.x / 2.0 + r_t.y * std::sqrt(3.0) / 2.0;
14,633,949✔
821
  }
822
  if ((std::abs(delta - edge) > FP_PRECISION) && delta_dir != 0) {
85,503,297!
823
    double this_d = (edge - delta) / delta_dir;
85,503,297✔
824
    if (this_d < d) {
85,503,297✔
825
      if (delta_dir > 0) {
28,519,040✔
826
        lattice_trans = {0, 1, 0};
14,234,682✔
827
      } else {
828
        lattice_trans = {0, -1, 0};
14,284,358✔
829
      }
830
      d = this_d;
28,519,040✔
831
    }
832
  }
833

834
  // Top and bottom sides
835
  if (is_3d_) {
85,503,297✔
836
    double z = r.z;
20,213,028✔
837
    double z0 {copysign(0.5 * pitch_[1], u.z)};
20,213,028✔
838
    if ((std::abs(z - z0) > FP_PRECISION) && u.z != 0) {
20,213,028!
839
      double this_d = (z0 - z) / u.z;
20,213,028✔
840
      if (this_d < d) {
20,213,028✔
841
        d = this_d;
3,062,268✔
842
        if (u.z > 0) {
3,062,268✔
843
          lattice_trans = {0, 0, 1};
1,527,053✔
844
        } else {
845
          lattice_trans = {0, 0, -1};
1,535,215✔
846
        }
847
        d = this_d;
3,062,268✔
848
      }
849
    }
850
  }
851

852
  return {d, lattice_trans};
171,006,594✔
853
}
854

855
//==============================================================================
856

857
void HexLattice::get_indices(
11,900,471✔
858
  Position r, Direction u, array<int, 3>& result) const
859
{
860
  // Offset the xyz by the lattice center.
861
  Position r_o {r.x - center_.x, r.y - center_.y, r.z};
11,900,471✔
862
  if (is_3d_) {
11,900,471✔
863
    r_o.z -= center_.z;
2,484,878✔
864
  }
865

866
  // Index the z direction, accounting for coincidence
867
  result[2] = 0;
11,900,471✔
868
  if (is_3d_) {
11,900,471✔
869
    double iz_ {r_o.z / pitch_[1] + 0.5 * n_axial_};
2,484,878✔
870
    long iz_close {std::lround(iz_)};
2,484,878✔
871
    if (coincident(iz_, iz_close)) {
2,484,878✔
872
      result[2] = (u.z > 0) ? iz_close : iz_close - 1;
765,699✔
873
    } else {
874
      result[2] = std::floor(iz_);
1,719,179✔
875
    }
876
  }
877

878
  if (orientation_ == Orientation::y) {
11,900,471✔
879
    // Convert coordinates into skewed bases.  The (x, alpha) basis is used to
880
    // find the index of the global coordinates to within 4 cells.
881
    double alpha = r_o.y - r_o.x / std::sqrt(3.0);
11,078,199✔
882
    result[0] = std::floor(r_o.x / (0.5 * std::sqrt(3.0) * pitch_[0]));
11,078,199✔
883
    result[1] = std::floor(alpha / pitch_[0]);
11,078,199✔
884
  } else {
885
    // Convert coordinates into skewed bases.  The (alpha, y) basis is used to
886
    // find the index of the global coordinates to within 4 cells.
887
    double alpha = r_o.y - r_o.x * std::sqrt(3.0);
822,272✔
888
    result[0] = std::floor(-alpha / (std::sqrt(3.0) * pitch_[0]));
822,272✔
889
    result[1] = std::floor(r_o.y / (0.5 * std::sqrt(3.0) * pitch_[0]));
822,272✔
890
  }
891

892
  // Add offset to indices (the center cell is (i1, i2) = (0, 0) but
893
  // the array is offset so that the indices never go below 0).
894
  result[0] += n_rings_ - 1;
11,900,471✔
895
  result[1] += n_rings_ - 1;
11,900,471✔
896

897
  // Calculate the (squared) distance between the particle and the centers of
898
  // the four possible cells.  Regular hexagonal tiles form a Voronoi
899
  // tessellation so the xyz should be in the hexagonal cell that it is closest
900
  // to the center of.  This method is used over a method that uses the
901
  // remainders of the floor divisions above because it provides better finite
902
  // precision performance.  Squared distances are used because they are more
903
  // computationally efficient than normal distances.
904

905
  // COINCIDENCE CHECK
906
  // if a distance to center, d, is within the coincidence tolerance of the
907
  // current minimum distance, d_min, the particle is on an edge or vertex.
908
  // In this case, the dot product of the position vector and direction vector
909
  // for the current indices, dp, and the dot product for the currently selected
910
  // indices, dp_min, are compared. The cell which the particle is moving into
911
  // is kept (i.e. the cell with the lowest dot product as the vectors will be
912
  // completely opposed if the particle is moving directly toward the center of
913
  // the cell).
914
  int i1_chg;
915
  int i2_chg;
916
  double d_min {INFTY};
11,900,471✔
917
  double dp_min {INFTY};
11,900,471✔
918
  for (int i = 0; i < 2; i++) {
35,701,413✔
919
    for (int j = 0; j < 2; j++) {
71,402,826✔
920
      // get local coordinates
921
      const array<int, 3> i_xyz {result[0] + j, result[1] + i, 0};
47,601,884✔
922
      Position r_t = get_local_position(r, i_xyz);
47,601,884✔
923
      // calculate distance
924
      double d = r_t.x * r_t.x + r_t.y * r_t.y;
47,601,884✔
925
      // check for coincidence. Because the numerical error incurred
926
      // in hex geometry is higher than other geometries, the relative
927
      // coincidence is checked here so that coincidence is successfully
928
      // detected on large hex lattice with particles far from the origin
929
      // which have rounding errors larger than the FP_COINCIDENT thresdhold.
930
      bool on_boundary = coincident(1.0, d_min / d);
47,601,884✔
931
      if (d < d_min || on_boundary) {
47,601,884✔
932
        // normalize r_t and find dot product
933
        r_t /= std::sqrt(d);
29,822,133✔
934
        double dp = u.x * r_t.x + u.y * r_t.y;
29,822,133✔
935
        // do not update values if particle is on a
936
        // boundary and not moving into this cell
937
        if (on_boundary && dp > dp_min)
29,822,133✔
938
          continue;
2,050,257✔
939
        // update values
940
        d_min = d;
27,771,876✔
941
        i1_chg = j;
27,771,876✔
942
        i2_chg = i;
27,771,876✔
943
        dp_min = dp;
27,771,876✔
944
      }
945
    }
946
  }
947

948
  // update outgoing indices
949
  result[0] += i1_chg;
11,900,471✔
950
  result[1] += i2_chg;
11,900,471✔
951
}
11,900,471✔
952

953
int HexLattice::get_flat_index(const array<int, 3>& i_xyz) const
29,285,960✔
954
{
955
  return (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * i_xyz[2] +
29,285,960✔
956
         (2 * n_rings_ - 1) * i_xyz[1] + i_xyz[0];
29,285,960✔
957
}
958

959
//==============================================================================
960

961
Position HexLattice::get_local_position(
337,758,718✔
962
  Position r, const array<int, 3>& i_xyz) const
963
{
964
  if (orientation_ == Orientation::y) {
337,758,718✔
965
    // x_l = x_g - (center + pitch_x*cos(30)*index_x)
966
    r.x -=
286,269,500✔
967
      center_.x + std::sqrt(3.0) / 2.0 * (i_xyz[0] - n_rings_ + 1) * pitch_[0];
286,269,500✔
968
    // y_l = y_g - (center + pitch_x*index_x + pitch_y*sin(30)*index_y)
969
    r.y -= (center_.y + (i_xyz[1] - n_rings_ + 1) * pitch_[0] +
286,269,500✔
970
            (i_xyz[0] - n_rings_ + 1) * pitch_[0] / 2.0);
286,269,500✔
971
  } else {
972
    // x_l = x_g - (center + pitch_x*index_a + pitch_y*sin(30)*index_y)
973
    r.x -= (center_.x + (i_xyz[0] - n_rings_ + 1) * pitch_[0] +
51,489,218✔
974
            (i_xyz[1] - n_rings_ + 1) * pitch_[0] / 2.0);
51,489,218✔
975
    // y_l = y_g - (center + pitch_y*cos(30)*index_y)
976
    r.y -=
51,489,218✔
977
      center_.y + std::sqrt(3.0) / 2.0 * (i_xyz[1] - n_rings_ + 1) * pitch_[0];
51,489,218✔
978
  }
979

980
  if (is_3d_) {
337,758,718✔
981
    r.z -= center_.z - (0.5 * n_axial_ - i_xyz[2] - 0.5) * pitch_[1];
78,611,665✔
982
  }
983

984
  return r;
337,758,718✔
985
}
986

987
//==============================================================================
988

989
bool HexLattice::is_valid_index(int indx) const
387,424✔
990
{
991
  int nx {2 * n_rings_ - 1};
387,424✔
992
  int ny {2 * n_rings_ - 1};
387,424✔
993
  int iz = indx / (nx * ny);
387,424✔
994
  int iy = (indx - nx * ny * iz) / nx;
387,424✔
995
  int ix = indx - nx * ny * iz - nx * iy;
387,424✔
996
  array<int, 3> i_xyz {ix, iy, iz};
387,424✔
997
  return are_valid_indices(i_xyz);
774,848✔
998
}
999

1000
//==============================================================================
1001

1002
int32_t& HexLattice::offset(int map, const array<int, 3>& i_xyz)
77,204,831✔
1003
{
1004
  int nx {2 * n_rings_ - 1};
77,204,831✔
1005
  int ny {2 * n_rings_ - 1};
77,204,831✔
1006
  int nz {n_axial_};
77,204,831✔
1007
  return offsets_[nx * ny * nz * map + nx * ny * i_xyz[2] + nx * i_xyz[1] +
77,204,831✔
1008
                  i_xyz[0]];
154,409,662✔
1009
}
1010

UNCOV
1011
int32_t HexLattice::offset(int map, int indx) const
×
1012
{
UNCOV
1013
  int nx {2 * n_rings_ - 1};
×
UNCOV
1014
  int ny {2 * n_rings_ - 1};
×
UNCOV
1015
  int nz {n_axial_};
×
UNCOV
1016
  return offsets_[nx * ny * nz * map + indx];
×
1017
}
1018

1019
//==============================================================================
1020

1021
std::string HexLattice::index_to_string(int indx) const
231✔
1022
{
1023
  int nx {2 * n_rings_ - 1};
231✔
1024
  int ny {2 * n_rings_ - 1};
231✔
1025
  int iz {indx / (nx * ny)};
231✔
1026
  int iy {(indx - nx * ny * iz) / nx};
231✔
1027
  int ix {indx - nx * ny * iz - nx * iy};
231✔
1028
  std::string out {std::to_string(ix - n_rings_ + 1)};
231✔
1029
  out += ',';
231✔
1030
  out += std::to_string(iy - n_rings_ + 1);
231✔
1031
  if (is_3d_) {
231!
UNCOV
1032
    out += ',';
×
1033
    out += std::to_string(iz);
×
1034
  }
1035
  return out;
231✔
1036
}
×
1037

1038
//==============================================================================
1039

1040
void HexLattice::to_hdf5_inner(hid_t lat_group) const
55✔
1041
{
1042
  // Write basic lattice information.
1043
  write_string(lat_group, "type", "hexagonal", false);
55✔
1044
  write_dataset(lat_group, "n_rings", n_rings_);
55✔
1045
  write_dataset(lat_group, "n_axial", n_axial_);
55✔
1046
  if (orientation_ == Orientation::y) {
55✔
1047
    write_string(lat_group, "orientation", "y", false);
44✔
1048
  } else {
1049
    write_string(lat_group, "orientation", "x", false);
11✔
1050
  }
1051
  if (is_3d_) {
55✔
1052
    write_dataset(lat_group, "pitch", pitch_);
22✔
1053
    write_dataset(lat_group, "center", center_);
22✔
1054
  } else {
1055
    array<double, 1> pitch_short {{pitch_[0]}};
33✔
1056
    write_dataset(lat_group, "pitch", pitch_short);
33✔
1057
    array<double, 2> center_short {{center_[0], center_[1]}};
33✔
1058
    write_dataset(lat_group, "center", center_short);
33✔
1059
  }
1060

1061
  // Write the universe ids.
1062
  hsize_t nx {static_cast<hsize_t>(2 * n_rings_ - 1)};
55✔
1063
  hsize_t ny {static_cast<hsize_t>(2 * n_rings_ - 1)};
55✔
1064
  hsize_t nz {static_cast<hsize_t>(n_axial_)};
55✔
1065
  vector<int> out(nx * ny * nz);
55✔
1066

1067
  for (int m = 0; m < nz; m++) {
143✔
1068
    for (int k = 0; k < ny; k++) {
1,364✔
1069
      for (int j = 0; j < nx; j++) {
26,004✔
1070
        int indx = nx * ny * m + nx * k + j;
24,728✔
1071
        if (j + k < n_rings_ - 1) {
24,728✔
1072
          // This array position is never used; put a -1 to indicate this.
1073
          out[indx] = -1;
3,080✔
1074
        } else if (j + k > 3 * n_rings_ - 3) {
21,648✔
1075
          // This array position is never used; put a -1 to indicate this.
1076
          out[indx] = -1;
3,080✔
1077
        } else {
1078
          out[indx] = model::universes[universes_[indx]]->id_;
18,568✔
1079
        }
1080
      }
1081
    }
1082
  }
1083

1084
  hsize_t dims[3] {nz, ny, nx};
55✔
1085
  write_int(lat_group, 3, dims, "universes", out.data(), false);
55✔
1086
}
55✔
1087

1088
//==============================================================================
1089
// Non-method functions
1090
//==============================================================================
1091

1092
void read_lattices(pugi::xml_node node)
7,722✔
1093
{
1094
  for (pugi::xml_node lat_node : node.children("lattice")) {
9,501✔
1095
    model::lattices.push_back(make_unique<RectLattice>(lat_node));
1,779✔
1096
  }
1097
  for (pugi::xml_node lat_node : node.children("hex_lattice")) {
7,813✔
1098
    model::lattices.push_back(make_unique<HexLattice>(lat_node));
91✔
1099
  }
1100

1101
  // Fill the lattice map.
1102
  for (int i_lat = 0; i_lat < model::lattices.size(); i_lat++) {
9,592✔
1103
    int id = model::lattices[i_lat]->id_;
1,870✔
1104
    auto in_map = model::lattice_map.find(id);
1,870✔
1105
    if (in_map == model::lattice_map.end()) {
1,870!
1106
      model::lattice_map[id] = i_lat;
1,870✔
1107
    } else {
UNCOV
1108
      fatal_error(
×
UNCOV
1109
        fmt::format("Two or more lattices use the same unique ID: {}", id));
×
1110
    }
1111
  }
1112
}
7,722✔
1113

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