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

openmc-dev / openmc / 24227695169

10 Apr 2026 05:16AM UTC coverage: 81.399% (+0.06%) from 81.34%
24227695169

Pull #3566

github

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

17904 of 25783 branches covered (69.44%)

Branch coverage included in aggregate %.

388 of 443 new or added lines in 10 files covered. (87.58%)

58573 of 68170 relevant lines covered (85.92%)

44469925.31 hits per line

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

79.49
/src/surface.cpp
1
#include "openmc/surface.h"
2

3
#include <cmath>
4
#include <complex>
5
#include <initializer_list>
6
#include <set>
7
#include <utility>
8

9
#include <fmt/core.h>
10

11
#include "openmc/array.h"
12
#include "openmc/cell.h"
13
#include "openmc/container_util.h"
14
#include "openmc/error.h"
15
#include "openmc/external/quartic_solver.h"
16
#include "openmc/hdf5_interface.h"
17
#include "openmc/math_functions.h"
18
#include "openmc/random_lcg.h"
19
#include "openmc/settings.h"
20
#include "openmc/string_utils.h"
21
#include "openmc/xml_interface.h"
22

23
namespace openmc {
24

25
//==============================================================================
26
// Global variables
27
//==============================================================================
28

29
namespace model {
30
std::unordered_map<int, int> surface_map;
31
vector<unique_ptr<Surface>> surfaces;
32
} // namespace model
33

34
//==============================================================================
35
// Helper functions for reading the "coeffs" node of an XML surface element
36
//==============================================================================
37

38
void read_coeffs(
45,969✔
39
  pugi::xml_node surf_node, int surf_id, std::initializer_list<double*> coeffs)
40
{
41
  // Check the given number of coefficients.
42
  auto coeffs_file = get_node_array<double>(surf_node, "coeffs");
45,969✔
43
  if (coeffs_file.size() != coeffs.size()) {
45,969!
44
    fatal_error(
×
45
      fmt::format("Surface {} expects {} coefficient but was given {}", surf_id,
×
46
        coeffs.size(), coeffs_file.size()));
×
47
  }
48

49
  // Copy the coefficients
50
  int i = 0;
45,969✔
51
  for (auto c : coeffs) {
139,554✔
52
    *c = coeffs_file[i++];
93,585✔
53
  }
54
}
45,969✔
55

56
//==============================================================================
57
// Surface implementation
58
//==============================================================================
59

60
Surface::Surface() {} // empty constructor
890✔
61

62
Surface::Surface(pugi::xml_node surf_node)
45,969✔
63
{
64
  if (check_for_node(surf_node, "id")) {
45,969!
65
    id_ = std::stoi(get_node_value(surf_node, "id"));
91,938✔
66
    if (contains(settings::source_write_surf_id, id_) ||
91,938✔
67
        settings::source_write_surf_id.empty()) {
45,181✔
68
      surf_source_ = true;
44,927✔
69
    }
70
  } else {
71
    fatal_error("Must specify id of surface in geometry XML file.");
×
72
  }
73

74
  if (check_for_node(surf_node, "name")) {
45,969✔
75
    name_ = get_node_value(surf_node, "name", false);
10,996✔
76
  }
77

78
  if (check_for_node(surf_node, "boundary")) {
45,969✔
79
    std::string surf_bc = get_node_value(surf_node, "boundary", true, true);
26,197✔
80

81
    if (surf_bc == "transmission" || surf_bc == "transmit" || surf_bc.empty()) {
52,394!
82
      // Leave the bc_ a nullptr
83
    } else if (surf_bc == "vacuum") {
26,197✔
84
      bc_ = make_unique<VacuumBC>();
12,665✔
85
    } else if (surf_bc == "reflective" || surf_bc == "reflect" ||
14,168!
86
               surf_bc == "reflecting") {
636!
87
      bc_ = make_unique<ReflectiveBC>();
12,896✔
88
    } else if (surf_bc == "white") {
636✔
89
      bc_ = make_unique<WhiteBC>();
90✔
90
    } else if (surf_bc == "periodic") {
546!
91
      // Periodic BCs are handled separately
92
    } else {
93
      fatal_error(fmt::format("Unknown boundary condition \"{}\" specified "
×
94
                              "on surface {}",
95
        surf_bc, id_));
×
96
    }
97

98
    if (check_for_node(surf_node, "albedo") && bc_) {
26,197!
99
      double surf_alb = std::stod(get_node_value(surf_node, "albedo"));
180✔
100

101
      if (surf_alb < 0.0)
90!
102
        fatal_error(fmt::format("Surface {} has an albedo of {}. "
×
103
                                "Albedo values must be positive.",
104
          id_, surf_alb));
×
105

106
      if (surf_alb > 1.0)
90!
107
        warning(fmt::format("Surface {} has an albedo of {}. "
×
108
                            "Albedos greater than 1 may cause "
109
                            "unphysical behaviour.",
110
          id_, surf_alb));
×
111

112
      bc_->set_albedo(surf_alb);
90✔
113
    }
114
  }
26,197✔
115
}
45,969✔
116

117
bool Surface::sense(Position r, Direction u) const
2,147,483,647✔
118
{
119
  // Evaluate the surface equation at the particle's coordinates to determine
120
  // which side the particle is on.
121
  const double f = evaluate(r);
2,147,483,647✔
122

123
  // Check which side of surface the point is on.
124
  if (std::abs(f) < FP_COINCIDENT) {
2,147,483,647✔
125
    // Particle may be coincident with this surface. To determine the sense, we
126
    // look at the direction of the particle relative to the surface normal (by
127
    // default in the positive direction) via their dot product.
128
    return u.dot(normal(r)) > 0.0;
10,560,903✔
129
  }
130
  return f > 0.0;
2,147,483,647✔
131
}
132

133
Direction Surface::reflect(Position r, Direction u, GeometryState* p) const
686,483,794✔
134
{
135
  // Determine projection of direction onto normal and squared magnitude of
136
  // normal.
137
  Direction n = normal(r);
686,483,794✔
138

139
  // Reflect direction according to normal.
140
  return u.reflect(n);
686,483,794✔
141
}
142

143
Direction Surface::diffuse_reflect(
1,007,149✔
144
  Position r, Direction u, uint64_t* seed) const
145
{
146
  // Diffuse reflect direction according to the normal.
147
  // cosine distribution
148

149
  Direction n = this->normal(r);
1,007,149✔
150
  n /= n.norm();
1,007,149✔
151
  const double projection = n.dot(u);
1,007,149✔
152

153
  // sample from inverse function, u=sqrt(rand) since p(u)=2u, so F(u)=u^2
154
  const double mu =
1,007,149✔
155
    (projection >= 0.0) ? -std::sqrt(prn(seed)) : std::sqrt(prn(seed));
1,007,149✔
156

157
  // sample azimuthal distribution uniformly
158
  u = rotate_angle(n, mu, nullptr, seed);
1,007,149✔
159

160
  // normalize the direction
161
  return u / u.norm();
1,007,149✔
162
}
163

164
void Surface::to_hdf5(hid_t group_id) const
36,423✔
165
{
166
  hid_t surf_group = create_group(group_id, fmt::format("surface {}", id_));
36,423✔
167

168
  if (geom_type() == GeometryType::DAG) {
36,423✔
169
    write_string(surf_group, "geom_type", "dagmc", false);
1,346!
170
  } else if (geom_type() == GeometryType::CSG) {
35,750!
171
    write_string(surf_group, "geom_type", "csg", false);
35,750✔
172

173
    if (bc_) {
35,750✔
174
      write_string(surf_group, "boundary_type", bc_->type(), false);
21,794✔
175
      bc_->to_hdf5(surf_group);
21,794✔
176

177
      // write periodic surface ID
178
      if (bc_->type() == "periodic") {
21,794✔
179
        auto pbc = dynamic_cast<PeriodicBC*>(bc_.get());
458!
180
        Surface& surf1 {*model::surfaces[pbc->i_surf()]};
458!
181
        Surface& surf2 {*model::surfaces[pbc->j_surf()]};
458!
182

183
        if (id_ == surf1.id_) {
458!
184
          write_dataset(surf_group, "periodic_surface_id", surf2.id_);
458✔
185
        } else {
186
          write_dataset(surf_group, "periodic_surface_id", surf1.id_);
×
187
        }
188
      }
189
    } else {
190
      write_string(surf_group, "boundary_type", "transmission", false);
27,912✔
191
    }
192
  }
193

194
  if (!name_.empty()) {
36,423✔
195
    write_string(surf_group, "name", name_, false);
8,838✔
196
  }
197

198
  to_hdf5_inner(surf_group);
36,423✔
199

200
  close_group(surf_group);
36,423✔
201
}
36,423✔
202

203
//==============================================================================
204
// Generic functions for x-, y-, and z-, planes.
205
//==============================================================================
206

207
// The template parameter indicates the axis normal to the plane.
208
template<int i>
209
double axis_aligned_plane_distance(
2,147,483,647✔
210
  Position r, Direction u, bool coincident, double offset)
211
{
212
  const double f = offset - r[i];
2,147,483,647✔
213
  if (coincident || std::abs(f) < FP_COINCIDENT || u[i] == 0.0)
2,147,483,647✔
214
    return INFTY;
215
  const double d = f / u[i];
2,147,483,647✔
216
  if (d < 0.0)
2,147,483,647✔
217
    return INFTY;
2,147,483,647✔
218
  return d;
219
}
220

221
//==============================================================================
222
// SurfaceXPlane implementation
223
//==============================================================================
224

225
SurfaceXPlane::SurfaceXPlane(pugi::xml_node surf_node) : Surface(surf_node)
11,688✔
226
{
227
  read_coeffs(surf_node, id_, {&x0_});
11,688✔
228
}
11,688✔
229

230
double SurfaceXPlane::evaluate(Position r) const
1,724,559,095✔
231
{
232
  return r.x - x0_;
1,724,559,095✔
233
}
234

235
double SurfaceXPlane::distance(Position r, Direction u, bool coincident) const
2,147,483,647✔
236
{
237
  return axis_aligned_plane_distance<0>(r, u, coincident, x0_);
2,147,483,647✔
238
}
239

240
Direction SurfaceXPlane::normal(Position r) const
303,954,157✔
241
{
242
  return {1., 0., 0.};
303,954,157✔
243
}
244

245
void SurfaceXPlane::to_hdf5_inner(hid_t group_id) const
8,805✔
246
{
247
  write_string(group_id, "type", "x-plane", false);
8,805✔
248
  array<double, 1> coeffs {{x0_}};
8,805✔
249
  write_dataset(group_id, "coefficients", coeffs);
8,805✔
250
}
8,805✔
251

252
BoundingBox SurfaceXPlane::bounding_box(bool pos_side) const
242✔
253
{
254
  if (pos_side) {
242✔
255
    return {{x0_, -INFTY, -INFTY}, {INFTY, INFTY, INFTY}};
121✔
256
  } else {
257
    return {{-INFTY, -INFTY, -INFTY}, {x0_, INFTY, INFTY}};
121✔
258
  }
259
}
260

NEW
261
bool SurfaceXPlane::triso_in_mesh(
×
262
  vector<double> mesh_center, vector<double> lattice_pitch) const
263
{
NEW
264
  return false;
×
265
}
266

267
//==============================================================================
268
// SurfaceYPlane implementation
269
//==============================================================================
NEW
270
bool SurfaceYPlane::triso_in_mesh(
×
271
  vector<double> mesh_center, vector<double> lattice_pitch) const
272
{
NEW
273
  return false;
×
274
}
275
SurfaceYPlane::SurfaceYPlane(pugi::xml_node surf_node) : Surface(surf_node)
9,720✔
276
{
277
  read_coeffs(surf_node, id_, {&y0_});
9,720✔
278
}
9,720✔
279

280
double SurfaceYPlane::evaluate(Position r) const
1,713,331,740✔
281
{
282
  return r.y - y0_;
1,713,331,740✔
283
}
284

285
double SurfaceYPlane::distance(Position r, Direction u, bool coincident) const
2,147,483,647✔
286
{
287
  return axis_aligned_plane_distance<1>(r, u, coincident, y0_);
2,147,483,647✔
288
}
289

290
Direction SurfaceYPlane::normal(Position r) const
323,277,972✔
291
{
292
  return {0., 1., 0.};
323,277,972✔
293
}
294

295
void SurfaceYPlane::to_hdf5_inner(hid_t group_id) const
8,048✔
296
{
297
  write_string(group_id, "type", "y-plane", false);
8,048✔
298
  array<double, 1> coeffs {{y0_}};
8,048✔
299
  write_dataset(group_id, "coefficients", coeffs);
8,048✔
300
}
8,048✔
301

302
BoundingBox SurfaceYPlane::bounding_box(bool pos_side) const
242✔
303
{
304
  if (pos_side) {
242✔
305
    return {{-INFTY, y0_, -INFTY}, {INFTY, INFTY, INFTY}};
121✔
306
  } else {
307
    return {{-INFTY, -INFTY, -INFTY}, {INFTY, y0_, INFTY}};
121✔
308
  }
309
}
310

311
//==============================================================================
312
// SurfaceZPlane implementation
313
//==============================================================================
NEW
314
bool SurfaceZPlane::triso_in_mesh(
×
315
  vector<double> mesh_center, vector<double> lattice_pitch) const
316
{
NEW
317
  return false;
×
318
}
319
SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) : Surface(surf_node)
7,095✔
320
{
321
  read_coeffs(surf_node, id_, {&z0_});
7,095✔
322
}
7,095✔
323

324
double SurfaceZPlane::evaluate(Position r) const
513,537,090✔
325
{
326
  return r.z - z0_;
513,537,090✔
327
}
328

329
double SurfaceZPlane::distance(Position r, Direction u, bool coincident) const
2,147,483,647✔
330
{
331
  return axis_aligned_plane_distance<2>(r, u, coincident, z0_);
2,147,483,647✔
332
}
333

334
Direction SurfaceZPlane::normal(Position r) const
57,423,688✔
335
{
336
  return {0., 0., 1.};
57,423,688✔
337
}
338

339
void SurfaceZPlane::to_hdf5_inner(hid_t group_id) const
6,105✔
340
{
341
  write_string(group_id, "type", "z-plane", false);
6,105✔
342
  array<double, 1> coeffs {{z0_}};
6,105✔
343
  write_dataset(group_id, "coefficients", coeffs);
6,105✔
344
}
6,105✔
345

346
BoundingBox SurfaceZPlane::bounding_box(bool pos_side) const
×
347
{
348
  if (pos_side) {
×
349
    return {{-INFTY, -INFTY, z0_}, {INFTY, INFTY, INFTY}};
×
350
  } else {
351
    return {{-INFTY, -INFTY, -INFTY}, {INFTY, INFTY, z0_}};
×
352
  }
353
}
354

355
//==============================================================================
356
// SurfacePlane implementation
357
//==============================================================================
NEW
358
bool SurfacePlane::triso_in_mesh(
×
359
  vector<double> mesh_center, vector<double> lattice_pitch) const
360
{
NEW
361
  return false;
×
362
}
363

364
SurfacePlane::SurfacePlane(pugi::xml_node surf_node) : Surface(surf_node)
1,964✔
365
{
366
  read_coeffs(surf_node, id_, {&A_, &B_, &C_, &D_});
1,964✔
367
}
1,964✔
368

369
double SurfacePlane::evaluate(Position r) const
348,121,488✔
370
{
371
  return A_ * r.x + B_ * r.y + C_ * r.z - D_;
348,121,488✔
372
}
373

374
double SurfacePlane::distance(Position r, Direction u, bool coincident) const
804,024,646✔
375
{
376
  const double f = A_ * r.x + B_ * r.y + C_ * r.z - D_;
804,024,646✔
377
  const double projection = A_ * u.x + B_ * u.y + C_ * u.z;
804,024,646✔
378
  if (coincident || std::abs(f) < FP_COINCIDENT || projection == 0.0) {
804,024,646!
379
    return INFTY;
380
  } else {
381
    const double d = -f / projection;
738,333,694✔
382
    if (d < 0.0)
738,333,694✔
383
      return INFTY;
384
    return d;
401,797,705✔
385
  }
386
}
387

388
Direction SurfacePlane::normal(Position r) const
5,715,879✔
389
{
390
  return {A_, B_, C_};
5,715,879✔
391
}
392

393
void SurfacePlane::to_hdf5_inner(hid_t group_id) const
1,480✔
394
{
395
  write_string(group_id, "type", "plane", false);
1,480✔
396
  array<double, 4> coeffs {{A_, B_, C_, D_}};
1,480✔
397
  write_dataset(group_id, "coefficients", coeffs);
1,480✔
398
}
1,480✔
399

400
//==============================================================================
401
// Generic functions for x-, y-, and z-, cylinders
402
//==============================================================================
403

404
// The template parameters indicate the axes perpendicular to the axis of the
405
// cylinder.  offset1 and offset2 should correspond with i1 and i2,
406
// respectively.
407
template<int i1, int i2>
408
double axis_aligned_cylinder_evaluate(
1,866,445,986✔
409
  Position r, double offset1, double offset2, double radius)
410
{
411
  const double r1 = r.get<i1>() - offset1;
1,866,445,986✔
412
  const double r2 = r.get<i2>() - offset2;
1,866,445,986✔
413
  return r1 * r1 + r2 * r2 - radius * radius;
1,866,445,986✔
414
}
415

416
// The first template parameter indicates which axis the cylinder is aligned to.
417
// The other two parameters indicate the other two axes.  offset1 and offset2
418
// should correspond with i2 and i3, respectively.
419
template<int i1, int i2, int i3>
420
double axis_aligned_cylinder_distance(Position r, Direction u, bool coincident,
2,067,747,021✔
421
  double offset1, double offset2, double radius)
422
{
423
  const double a = 1.0 - u.get<i1>() * u.get<i1>(); // u^2 + v^2
2,067,747,021✔
424
  if (a == 0.0)
2,067,747,021✔
425
    return INFTY;
426

427
  const double r2 = r.get<i2>() - offset1;
2,066,937,025✔
428
  const double r3 = r.get<i3>() - offset2;
2,066,937,025✔
429
  const double k = r2 * u.get<i2>() + r3 * u.get<i3>();
2,066,937,025✔
430
  const double c = r2 * r2 + r3 * r3 - radius * radius;
2,066,937,025✔
431
  const double quad = k * k - a * c;
2,066,937,025✔
432

433
  if (quad < 0.0) {
2,066,937,025✔
434
    // No intersection with cylinder.
435
    return INFTY;
436

437
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
1,740,869,376✔
438
    // Particle is on the cylinder, thus one distance is positive/negative
439
    // and the other is zero. The sign of k determines if we are facing in or
440
    // out.
441
    if (k >= 0.0) {
802,591,661✔
442
      return INFTY;
443
    } else {
444
      return (-k + sqrt(quad)) / a;
400,109,971✔
445
    }
446

447
  } else if (c < 0.0) {
938,277,715✔
448
    // Particle is inside the cylinder, thus one distance must be negative
449
    // and one must be positive. The positive distance will be the one with
450
    // negative sign on sqrt(quad).
451
    return (-k + sqrt(quad)) / a;
384,057,577✔
452

453
  } else {
454
    // Particle is outside the cylinder, thus both distances are either
455
    // positive or negative. If positive, the smaller distance is the one
456
    // with positive sign on sqrt(quad).
457
    const double d = (-k - sqrt(quad)) / a;
554,220,138✔
458
    if (d < 0.0)
554,220,138✔
459
      return INFTY;
460
    return d;
480,307,803✔
461
  }
462
}
463

464
// The first template parameter indicates which axis the cylinder is aligned to.
465
// The other two parameters indicate the other two axes.  offset1 and offset2
466
// should correspond with i2 and i3, respectively.
467
template<int i1, int i2, int i3>
468
Direction axis_aligned_cylinder_normal(
2,353,294✔
469
  Position r, double offset1, double offset2)
470
{
471
  Direction u;
2,353,294✔
472
  u.get<i2>() = 2.0 * (r.get<i2>() - offset1);
2,353,294✔
473
  u.get<i3>() = 2.0 * (r.get<i3>() - offset2);
2,353,294✔
474
  u.get<i1>() = 0.0;
475
  return u;
476
}
477

478
//==============================================================================
479
// SurfaceXCylinder implementation
480
//==============================================================================
481

NEW
482
bool SurfaceXCylinder::triso_in_mesh(
×
483
  vector<double> mesh_center, vector<double> lattice_pitch) const
484
{
NEW
485
  return false;
×
486
}
487

488
SurfaceXCylinder::SurfaceXCylinder(pugi::xml_node surf_node)
45✔
489
  : Surface(surf_node)
45✔
490
{
491
  read_coeffs(surf_node, id_, {&y0_, &z0_, &radius_});
45✔
492
}
45✔
493

494
double SurfaceXCylinder::evaluate(Position r) const
1,526,121✔
495
{
496
  return axis_aligned_cylinder_evaluate<1, 2>(r, y0_, z0_, radius_);
1,526,121✔
497
}
498

499
double SurfaceXCylinder::distance(
1,746,932✔
500
  Position r, Direction u, bool coincident) const
501
{
502
  return axis_aligned_cylinder_distance<0, 1, 2>(
1,746,932✔
503
    r, u, coincident, y0_, z0_, radius_);
1,746,932✔
504
}
505

506
Direction SurfaceXCylinder::normal(Position r) const
398,222✔
507
{
508
  return axis_aligned_cylinder_normal<0, 1, 2>(r, y0_, z0_);
398,222✔
509
}
510

511
void SurfaceXCylinder::to_hdf5_inner(hid_t group_id) const
33✔
512
{
513
  write_string(group_id, "type", "x-cylinder", false);
33✔
514
  array<double, 3> coeffs {{y0_, z0_, radius_}};
33✔
515
  write_dataset(group_id, "coefficients", coeffs);
33✔
516
}
33✔
517

518
BoundingBox SurfaceXCylinder::bounding_box(bool pos_side) const
×
519
{
520
  if (!pos_side) {
×
521
    return {{-INFTY, y0_ - radius_, z0_ - radius_},
×
522
      {INFTY, y0_ + radius_, z0_ + radius_}};
×
523
  } else {
524
    return {};
×
525
  }
526
}
527
//==============================================================================
528
// SurfaceYCylinder implementation
529
//==============================================================================
530

NEW
531
bool SurfaceYCylinder::triso_in_mesh(
×
532
  vector<double> mesh_center, vector<double> lattice_pitch) const
533
{
NEW
534
  return false;
×
535
}
536

537
SurfaceYCylinder::SurfaceYCylinder(pugi::xml_node surf_node)
15✔
538
  : Surface(surf_node)
15✔
539
{
540
  read_coeffs(surf_node, id_, {&x0_, &z0_, &radius_});
15✔
541
}
15✔
542

543
double SurfaceYCylinder::evaluate(Position r) const
170,984✔
544
{
545
  return axis_aligned_cylinder_evaluate<0, 2>(r, x0_, z0_, radius_);
170,984✔
546
}
547

548
double SurfaceYCylinder::distance(
659,769✔
549
  Position r, Direction u, bool coincident) const
550
{
551
  return axis_aligned_cylinder_distance<1, 0, 2>(
659,769✔
552
    r, u, coincident, x0_, z0_, radius_);
659,769✔
553
}
554

555
Direction SurfaceYCylinder::normal(Position r) const
×
556
{
557
  return axis_aligned_cylinder_normal<1, 0, 2>(r, x0_, z0_);
×
558
}
559

560
void SurfaceYCylinder::to_hdf5_inner(hid_t group_id) const
11✔
561
{
562
  write_string(group_id, "type", "y-cylinder", false);
11✔
563
  array<double, 3> coeffs {{x0_, z0_, radius_}};
11✔
564
  write_dataset(group_id, "coefficients", coeffs);
11✔
565
}
11✔
566

567
BoundingBox SurfaceYCylinder::bounding_box(bool pos_side) const
×
568
{
569
  if (!pos_side) {
×
570
    return {{x0_ - radius_, -INFTY, z0_ - radius_},
×
571
      {x0_ + radius_, INFTY, z0_ + radius_}};
×
572
  } else {
573
    return {};
×
574
  }
575
}
576

577
//==============================================================================
578
// SurfaceZCylinder implementation
579
//==============================================================================
580

NEW
581
bool SurfaceZCylinder::triso_in_mesh(
×
582
  vector<double> mesh_center, vector<double> lattice_pitch) const
583
{
NEW
584
  return false;
×
585
}
586

587
SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node)
5,256✔
588
  : Surface(surf_node)
5,256✔
589
{
590
  read_coeffs(surf_node, id_, {&x0_, &y0_, &radius_});
5,256✔
591
}
5,256✔
592

593
double SurfaceZCylinder::evaluate(Position r) const
1,864,748,881✔
594
{
595
  return axis_aligned_cylinder_evaluate<0, 1>(r, x0_, y0_, radius_);
1,864,748,881✔
596
}
597

598
double SurfaceZCylinder::distance(
2,065,340,320✔
599
  Position r, Direction u, bool coincident) const
600
{
601
  return axis_aligned_cylinder_distance<2, 0, 1>(
2,065,340,320✔
602
    r, u, coincident, x0_, y0_, radius_);
2,065,340,320✔
603
}
604

605
Direction SurfaceZCylinder::normal(Position r) const
1,955,072✔
606
{
607
  return axis_aligned_cylinder_normal<2, 0, 1>(r, x0_, y0_);
1,955,072✔
608
}
609

610
void SurfaceZCylinder::to_hdf5_inner(hid_t group_id) const
4,089✔
611
{
612
  write_string(group_id, "type", "z-cylinder", false);
4,089✔
613
  array<double, 3> coeffs {{x0_, y0_, radius_}};
4,089✔
614
  write_dataset(group_id, "coefficients", coeffs);
4,089✔
615
}
4,089✔
616

617
BoundingBox SurfaceZCylinder::bounding_box(bool pos_side) const
44✔
618
{
619
  if (!pos_side) {
44✔
620
    return {{x0_ - radius_, y0_ - radius_, -INFTY},
22✔
621
      {x0_ + radius_, y0_ + radius_, INFTY}};
22✔
622
  } else {
623
    return {};
22✔
624
  }
625
}
626

627
//==============================================================================
628
// SurfaceSphere implementation
629
//==============================================================================
630

631
SurfaceSphere::SurfaceSphere(pugi::xml_node surf_node) : Surface(surf_node)
9,934✔
632
{
633
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_});
9,934✔
634
}
9,934✔
635

636
double SurfaceSphere::evaluate(Position r) const
682,649,637✔
637
{
638
  const double x = r.x - x0_;
682,649,637✔
639
  const double y = r.y - y0_;
682,649,637✔
640
  const double z = r.z - z0_;
682,649,637✔
641
  return x * x + y * y + z * z - radius_ * radius_;
682,649,637✔
642
}
643

644
double SurfaceSphere::distance(Position r, Direction u, bool coincident) const
827,880,974✔
645
{
646
  const double x = r.x - x0_;
827,880,974✔
647
  const double y = r.y - y0_;
827,880,974✔
648
  const double z = r.z - z0_;
827,880,974✔
649
  const double k = x * u.x + y * u.y + z * u.z;
827,880,974✔
650
  const double c = x * x + y * y + z * z - radius_ * radius_;
827,880,974✔
651
  const double quad = k * k - c;
827,880,974✔
652

653
  if (quad < 0.0) {
827,880,974✔
654
    // No intersection with sphere.
655
    return INFTY;
656

657
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
598,327,382!
658
    // Particle is on the sphere, thus one distance is positive/negative and
659
    // the other is zero. The sign of k determines if we are facing in or out.
660
    if (k >= 0.0) {
112,022,749✔
661
      return INFTY;
662
    } else {
663
      return -k + sqrt(quad);
63,555,400✔
664
    }
665

666
  } else if (c < 0.0) {
486,304,633✔
667
    // Particle is inside the sphere, thus one distance must be negative and
668
    // one must be positive. The positive distance will be the one with
669
    // negative sign on sqrt(quad)
670
    return -k + sqrt(quad);
425,481,576✔
671

672
  } else {
673
    // Particle is outside the sphere, thus both distances are either positive
674
    // or negative. If positive, the smaller distance is the one with positive
675
    // sign on sqrt(quad).
676
    const double d = -k - sqrt(quad);
60,823,057✔
677
    if (d < 0.0)
60,823,057✔
678
      return INFTY;
679
    return d;
49,778,201✔
680
  }
681
}
682

683
Direction SurfaceSphere::normal(Position r) const
54,509,829✔
684
{
685
  return {2.0 * (r.x - x0_), 2.0 * (r.y - y0_), 2.0 * (r.z - z0_)};
54,509,829✔
686
}
687

688
void SurfaceSphere::to_hdf5_inner(hid_t group_id) const
6,959✔
689
{
690
  write_string(group_id, "type", "sphere", false);
6,959✔
691
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_}};
6,959✔
692
  write_dataset(group_id, "coefficients", coeffs);
6,959✔
693
}
6,959✔
694

695
BoundingBox SurfaceSphere::bounding_box(bool pos_side) const
×
696
{
697
  if (!pos_side) {
×
698
    return {{x0_ - radius_, y0_ - radius_, z0_ - radius_},
×
699
      {x0_ + radius_, y0_ + radius_, z0_ + radius_}};
×
700
  } else {
701
    return {};
×
702
  }
703
}
704

705
void SurfaceSphere::connect_to_triso_base(int triso_index, std::string key)
3,915✔
706
{
707
  if (key == "base") {
3,915✔
708
    triso_base_index_ = triso_index;
2,415✔
709
    is_triso_surface_ = true;
2,415✔
710
  } else if (key == "particle") {
1,500!
711
    triso_particle_index_ = triso_index;
1,500✔
712
  }
713
}
3,915✔
714

715
vector<double> SurfaceSphere::get_center() const
18,015,551✔
716
{
717
  return {x0_, y0_, z0_};
18,015,551✔
718
}
719

720
double SurfaceSphere::get_radius() const
18,014,051✔
721
{
722
  return radius_;
18,014,051✔
723
}
724

725
bool SurfaceSphere::triso_in_mesh(
19,140✔
726
  vector<double> mesh_center, vector<double> lattice_pitch) const
727
{
728
  double dis_x;
19,140✔
729
  double dis_y;
19,140✔
730
  double dis_z;
19,140✔
731
  double x_min = mesh_center[0] - lattice_pitch[0] / 2;
19,140✔
732
  double x_max = mesh_center[0] + lattice_pitch[0] / 2;
19,140✔
733
  double y_min = mesh_center[1] - lattice_pitch[1] / 2;
19,140✔
734
  double y_max = mesh_center[1] + lattice_pitch[1] / 2;
19,140✔
735
  double z_min = mesh_center[2] - lattice_pitch[2] / 2;
19,140✔
736
  double z_max = mesh_center[2] + lattice_pitch[2] / 2;
19,140✔
737
  if (x0_ >= x_min && x0_ <= x_max) {
19,140✔
738
    dis_x = 0;
739
  } else if (x0_ < x_min) {
11,010✔
740
    dis_x = pow(x_min - x0_, 2);
5,820✔
741
  } else {
742
    dis_x = pow(x_max - x0_, 2);
5,190✔
743
  }
744

745
  if (y0_ >= y_min && y0_ <= y_max) {
19,140✔
746
    dis_y = 0;
747
  } else if (y0_ < y_min) {
10,890✔
748
    dis_y = pow(y_min - y0_, 2);
5,280✔
749
  } else {
750
    dis_y = pow(y_max - y0_, 2);
5,610✔
751
  }
752

753
  if (z0_ >= z_min && z0_ <= z_max) {
19,140✔
754
    dis_z = 0;
755
  } else if (z0_ < z_min) {
10,950✔
756
    dis_z = pow(z_min - z0_, 2);
5,955✔
757
  } else {
758
    dis_z = pow(z_max - z0_, 2);
4,995✔
759
  }
760

761
  if (sqrt(dis_x + dis_y + dis_z) < radius_) {
19,140✔
762
    return true;
763
  } else {
764
    return false;
16,725✔
765
  }
766
}
767

768
//==============================================================================
769
// Generic functions for x-, y-, and z-, cones
770
//==============================================================================
771

772
// The first template parameter indicates which axis the cone is aligned to.
773
// The other two parameters indicate the other two axes.  offset1, offset2,
774
// and offset3 should correspond with i1, i2, and i3, respectively.
775
template<int i1, int i2, int i3>
776
double axis_aligned_cone_evaluate(
325,853✔
777
  Position r, double offset1, double offset2, double offset3, double radius_sq)
778
{
779
  const double r1 = r.get<i1>() - offset1;
325,853✔
780
  const double r2 = r.get<i2>() - offset2;
325,853✔
781
  const double r3 = r.get<i3>() - offset3;
325,853✔
782
  return r2 * r2 + r3 * r3 - radius_sq * r1 * r1;
325,853✔
783
}
784

785
// The first template parameter indicates which axis the cone is aligned to.
786
// The other two parameters indicate the other two axes.  offset1, offset2,
787
// and offset3 should correspond with i1, i2, and i3, respectively.
788
template<int i1, int i2, int i3>
789
double axis_aligned_cone_distance(Position r, Direction u, bool coincident,
978,021✔
790
  double offset1, double offset2, double offset3, double radius_sq)
791
{
792
  const double r1 = r.get<i1>() - offset1;
978,021✔
793
  const double r2 = r.get<i2>() - offset2;
978,021✔
794
  const double r3 = r.get<i3>() - offset3;
978,021✔
795
  const double a = u.get<i2>() * u.get<i2>() + u.get<i3>() * u.get<i3>() -
978,021✔
796
                   radius_sq * u.get<i1>() * u.get<i1>();
978,021✔
797
  const double k =
978,021✔
798
    r2 * u.get<i2>() + r3 * u.get<i3>() - radius_sq * r1 * u.get<i1>();
978,021✔
799
  const double c = r2 * r2 + r3 * r3 - radius_sq * r1 * r1;
978,021✔
800
  double quad = k * k - a * c;
978,021✔
801

802
  double d;
803

804
  if (quad < 0.0) {
978,021!
805
    // No intersection with cone.
806
    return INFTY;
807

808
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
978,021!
809
    // Particle is on the cone, thus one distance is positive/negative
810
    // and the other is zero. The sign of k determines if we are facing in or
811
    // out.
812
    if (k >= 0.0) {
59,510!
813
      d = (-k - sqrt(quad)) / a;
×
814
    } else {
815
      d = (-k + sqrt(quad)) / a;
59,510✔
816
    }
817

818
  } else {
819
    // Calculate both solutions to the quadratic.
820
    quad = sqrt(quad);
918,511✔
821
    d = (-k - quad) / a;
918,511✔
822
    const double b = (-k + quad) / a;
918,511✔
823

824
    // Determine the smallest positive solution.
825
    if (d < 0.0) {
918,511✔
826
      if (b > 0.0)
780,043✔
827
        d = b;
665,104✔
828
    } else {
829
      if (b > 0.0) {
138,468!
830
        if (b < d)
138,468!
831
          d = b;
138,468✔
832
      }
833
    }
834
  }
835

836
  // If the distance was negative, set boundary distance to infinity.
837
  if (d <= 0.0)
978,021✔
838
    return INFTY;
136,422✔
839
  return d;
840
}
841

842
// The first template parameter indicates which axis the cone is aligned to.
843
// The other two parameters indicate the other two axes.  offset1, offset2,
844
// and offset3 should correspond with i1, i2, and i3, respectively.
845
template<int i1, int i2, int i3>
846
Direction axis_aligned_cone_normal(
59,510✔
847
  Position r, double offset1, double offset2, double offset3, double radius_sq)
848
{
849
  Direction u;
850
  u.get<i1>() = -2.0 * radius_sq * (r.get<i1>() - offset1);
59,510✔
851
  u.get<i2>() = 2.0 * (r.get<i2>() - offset2);
59,510✔
852
  u.get<i3>() = 2.0 * (r.get<i3>() - offset3);
59,510✔
853
  return u;
854
}
855

856
//==============================================================================
857
// SurfaceXCone implementation
858
//==============================================================================
NEW
859
bool SurfaceXCone::triso_in_mesh(
×
860
  vector<double> mesh_center, vector<double> lattice_pitch) const
861
{
NEW
862
  return false;
×
863
}
864
SurfaceXCone::SurfaceXCone(pugi::xml_node surf_node) : Surface(surf_node)
×
865
{
866
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
×
867
}
×
868

869
double SurfaceXCone::evaluate(Position r) const
×
870
{
871
  return axis_aligned_cone_evaluate<0, 1, 2>(r, x0_, y0_, z0_, radius_sq_);
×
872
}
873

874
double SurfaceXCone::distance(Position r, Direction u, bool coincident) const
×
875
{
876
  return axis_aligned_cone_distance<0, 1, 2>(
×
877
    r, u, coincident, x0_, y0_, z0_, radius_sq_);
×
878
}
879

880
Direction SurfaceXCone::normal(Position r) const
×
881
{
882
  return axis_aligned_cone_normal<0, 1, 2>(r, x0_, y0_, z0_, radius_sq_);
×
883
}
884

885
void SurfaceXCone::to_hdf5_inner(hid_t group_id) const
×
886
{
887
  write_string(group_id, "type", "x-cone", false);
×
888
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
×
889
  write_dataset(group_id, "coefficients", coeffs);
×
890
}
×
891

892
//==============================================================================
893
// SurfaceYCone implementation
894
//==============================================================================
NEW
895
bool SurfaceYCone::triso_in_mesh(
×
896
  vector<double> mesh_center, vector<double> lattice_pitch) const
897
{
NEW
898
  return false;
×
899
}
900
SurfaceYCone::SurfaceYCone(pugi::xml_node surf_node) : Surface(surf_node)
×
901
{
902
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
×
903
}
×
904

905
double SurfaceYCone::evaluate(Position r) const
×
906
{
907
  return axis_aligned_cone_evaluate<1, 0, 2>(r, y0_, x0_, z0_, radius_sq_);
×
908
}
909

910
double SurfaceYCone::distance(Position r, Direction u, bool coincident) const
×
911
{
912
  return axis_aligned_cone_distance<1, 0, 2>(
×
913
    r, u, coincident, y0_, x0_, z0_, radius_sq_);
×
914
}
915

916
Direction SurfaceYCone::normal(Position r) const
×
917
{
918
  return axis_aligned_cone_normal<1, 0, 2>(r, y0_, x0_, z0_, radius_sq_);
×
919
}
920

921
void SurfaceYCone::to_hdf5_inner(hid_t group_id) const
×
922
{
923
  write_string(group_id, "type", "y-cone", false);
×
924
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
×
925
  write_dataset(group_id, "coefficients", coeffs);
×
926
}
×
927

928
//==============================================================================
929
// SurfaceZCone implementation
930
//==============================================================================
NEW
931
bool SurfaceZCone::triso_in_mesh(
×
932
  vector<double> mesh_center, vector<double> lattice_pitch) const
933
{
NEW
934
  return false;
×
935
}
936

937
SurfaceZCone::SurfaceZCone(pugi::xml_node surf_node) : Surface(surf_node)
15✔
938
{
939
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
15✔
940
}
15✔
941

942
double SurfaceZCone::evaluate(Position r) const
325,853✔
943
{
944
  return axis_aligned_cone_evaluate<2, 0, 1>(r, z0_, x0_, y0_, radius_sq_);
325,853✔
945
}
946

947
double SurfaceZCone::distance(Position r, Direction u, bool coincident) const
978,021✔
948
{
949
  return axis_aligned_cone_distance<2, 0, 1>(
978,021✔
950
    r, u, coincident, z0_, x0_, y0_, radius_sq_);
978,021✔
951
}
952

953
Direction SurfaceZCone::normal(Position r) const
59,510✔
954
{
955
  return axis_aligned_cone_normal<2, 0, 1>(r, z0_, x0_, y0_, radius_sq_);
59,510✔
956
}
957

958
void SurfaceZCone::to_hdf5_inner(hid_t group_id) const
11✔
959
{
960
  write_string(group_id, "type", "z-cone", false);
11✔
961
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
11✔
962
  write_dataset(group_id, "coefficients", coeffs);
11✔
963
}
11✔
964

965
//==============================================================================
966
// SurfaceQuadric implementation
967
//==============================================================================
NEW
968
bool SurfaceQuadric::triso_in_mesh(
×
969
  vector<double> mesh_center, vector<double> lattice_pitch) const
970
{
NEW
971
  return false;
×
972
}
973
SurfaceQuadric::SurfaceQuadric(pugi::xml_node surf_node) : Surface(surf_node)
15✔
974
{
975
  read_coeffs(
30✔
976
    surf_node, id_, {&A_, &B_, &C_, &D_, &E_, &F_, &G_, &H_, &J_, &K_});
15✔
977
}
15✔
978

979
double SurfaceQuadric::evaluate(Position r) const
177,400✔
980
{
981
  const double x = r.x;
177,400✔
982
  const double y = r.y;
177,400✔
983
  const double z = r.z;
177,400✔
984
  return x * (A_ * x + D_ * y + G_) + y * (B_ * y + E_ * z + H_) +
177,400✔
985
         z * (C_ * z + F_ * x + J_) + K_;
177,400✔
986
}
987

988
double SurfaceQuadric::distance(
316,679✔
989
  Position r, Direction ang, bool coincident) const
990
{
991
  const double& x = r.x;
316,679✔
992
  const double& y = r.y;
316,679✔
993
  const double& z = r.z;
316,679✔
994
  const double& u = ang.x;
316,679✔
995
  const double& v = ang.y;
316,679✔
996
  const double& w = ang.z;
316,679✔
997

998
  const double a =
316,679✔
999
    A_ * u * u + B_ * v * v + C_ * w * w + D_ * u * v + E_ * v * w + F_ * u * w;
316,679✔
1000
  const double k = A_ * u * x + B_ * v * y + C_ * w * z +
316,679✔
1001
                   0.5 * (D_ * (u * y + v * x) + E_ * (v * z + w * y) +
316,679✔
1002
                           F_ * (w * x + u * z) + G_ * u + H_ * v + J_ * w);
316,679✔
1003
  const double c = A_ * x * x + B_ * y * y + C_ * z * z + D_ * x * y +
316,679✔
1004
                   E_ * y * z + F_ * x * z + G_ * x + H_ * y + J_ * z + K_;
316,679✔
1005
  double quad = k * k - a * c;
316,679✔
1006

1007
  double d;
316,679✔
1008

1009
  if (quad < 0.0) {
316,679!
1010
    // No intersection with surface.
1011
    return INFTY;
1012

1013
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
316,679!
1014
    // Particle is on the surface, thus one distance is positive/negative and
1015
    // the other is zero. The sign of k determines which distance is zero and
1016
    // which is not. Additionally, if a is zero, it means the particle is on
1017
    // a plane-like surface.
1018
    if (a == 0.0) {
36,058!
1019
      d = INFTY; // see the below explanation
1020
    } else if (k >= 0.0) {
36,058!
1021
      d = (-k - sqrt(quad)) / a;
×
1022
    } else {
1023
      d = (-k + sqrt(quad)) / a;
36,058✔
1024
    }
1025

1026
  } else if (a == 0.0) {
280,621!
1027
    // Given the orientation of the particle, the quadric looks like a plane in
1028
    // this case, and thus we have only one solution despite potentially having
1029
    // quad > 0.0. While the term under the square root may be real, in one
1030
    // case of the +/- of the quadratic formula, 0/0 results, and in another, a
1031
    // finite value over 0 results. Applying L'Hopital's to the 0/0 case gives
1032
    // the below. Alternatively this can be found by simply putting a=0 in the
1033
    // equation ax^2 + bx + c = 0.
1034
    d = -0.5 * c / k;
×
1035
  } else {
1036
    // Calculate both solutions to the quadratic.
1037
    quad = sqrt(quad);
280,621✔
1038
    d = (-k - quad) / a;
280,621✔
1039
    double b = (-k + quad) / a;
280,621✔
1040

1041
    // Determine the smallest positive solution.
1042
    if (d < 0.0) {
280,621!
1043
      if (b > 0.0)
280,621!
1044
        d = b;
280,621✔
1045
    } else {
1046
      if (b > 0.0) {
×
1047
        if (b < d)
×
1048
          d = b;
×
1049
      }
1050
    }
1051
  }
1052

1053
  // If the distance was negative, set boundary distance to infinity.
1054
  if (d <= 0.0)
316,679!
1055
    return INFTY;
×
1056
  return d;
1057
}
1058

1059
Direction SurfaceQuadric::normal(Position r) const
36,058✔
1060
{
1061
  const double& x = r.x;
36,058✔
1062
  const double& y = r.y;
36,058✔
1063
  const double& z = r.z;
36,058✔
1064
  return {2.0 * A_ * x + D_ * y + F_ * z + G_,
36,058✔
1065
    2.0 * B_ * y + D_ * x + E_ * z + H_, 2.0 * C_ * z + E_ * y + F_ * x + J_};
36,058✔
1066
}
1067

1068
void SurfaceQuadric::to_hdf5_inner(hid_t group_id) const
11✔
1069
{
1070
  write_string(group_id, "type", "quadric", false);
11✔
1071
  array<double, 10> coeffs {{A_, B_, C_, D_, E_, F_, G_, H_, J_, K_}};
11✔
1072
  write_dataset(group_id, "coefficients", coeffs);
11✔
1073
}
11✔
1074

1075
//==============================================================================
1076
// Torus helper functions
1077
//==============================================================================
1078

1079
double torus_distance(double x1, double x2, double x3, double u1, double u2,
26,534,563✔
1080
  double u3, double A, double B, double C, bool coincident)
1081
{
1082
  // Coefficients for equation: (c2 t^2 + c1 t + c0)^2 = c2' t^2 + c1' t + c0'
1083
  double D = (C * C) / (B * B);
26,534,563✔
1084
  double c2 = u1 * u1 + u2 * u2 + D * u3 * u3;
26,534,563✔
1085
  double c1 = 2 * (u1 * x1 + u2 * x2 + D * u3 * x3);
26,534,563✔
1086
  double c0 = x1 * x1 + x2 * x2 + D * x3 * x3 + A * A - C * C;
26,534,563✔
1087
  double four_A2 = 4 * A * A;
26,534,563✔
1088
  double c2p = four_A2 * (u1 * u1 + u2 * u2);
26,534,563✔
1089
  double c1p = 2 * four_A2 * (u1 * x1 + u2 * x2);
26,534,563✔
1090
  double c0p = four_A2 * (x1 * x1 + x2 * x2);
26,534,563✔
1091

1092
  // Coefficient for equation: a t^4 + b t^3 + c t^2 + d t + e = 0. If the point
1093
  // is coincident, the 'e' coefficient should be zero. Explicitly setting it to
1094
  // zero helps avoid numerical issues below with root finding.
1095
  double coeff[5];
26,534,563✔
1096
  coeff[0] = coincident ? 0.0 : c0 * c0 - c0p;
26,534,563✔
1097
  coeff[1] = 2 * c0 * c1 - c1p;
26,534,563✔
1098
  coeff[2] = c1 * c1 + 2 * c0 * c2 - c2p;
26,534,563✔
1099
  coeff[3] = 2 * c1 * c2;
26,534,563✔
1100
  coeff[4] = c2 * c2;
26,534,563✔
1101

1102
  std::complex<double> roots[4];
26,534,563✔
1103
  oqs::quartic_solver(coeff, roots);
26,534,563✔
1104

1105
  // Find smallest positive, real root. In the case where the particle is
1106
  // coincident with the surface, we are sure to have one root very close to
1107
  // zero but possibly small and positive. A tolerance is set to discard that
1108
  // zero.
1109
  double distance = INFTY;
26,534,563✔
1110
  double cutoff = coincident ? TORUS_TOL : 0.0;
26,534,563✔
1111
  for (int i = 0; i < 4; ++i) {
132,672,815✔
1112
    if (roots[i].imag() == 0) {
106,138,252✔
1113
      double root = roots[i].real();
25,944,028✔
1114
      if (root > cutoff && root < distance) {
25,944,028✔
1115
        // Avoid roots corresponding to internal surfaces
1116
        double s1 = x1 + u1 * root;
10,411,995✔
1117
        double s2 = x2 + u2 * root;
10,411,995✔
1118
        double s3 = x3 + u3 * root;
10,411,995✔
1119
        double check = D * s3 * s3 + s1 * s1 + s2 * s2 + A * A - C * C;
10,411,995✔
1120
        if (check >= 0) {
10,411,995!
1121
          distance = root;
10,411,995✔
1122
        }
1123
      }
1124
    }
1125
  }
1126
  return distance;
26,534,563✔
1127
}
1128

1129
//==============================================================================
1130
// SurfaceXTorus implementation
1131
//==============================================================================
NEW
1132
bool SurfaceXTorus::triso_in_mesh(
×
1133
  vector<double> mesh_center, vector<double> lattice_pitch) const
1134
{
NEW
1135
  return false;
×
1136
}
1137
SurfaceXTorus::SurfaceXTorus(pugi::xml_node surf_node) : Surface(surf_node)
59✔
1138
{
1139
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
59✔
1140
}
59✔
1141

1142
void SurfaceXTorus::to_hdf5_inner(hid_t group_id) const
55✔
1143
{
1144
  write_string(group_id, "type", "x-torus", false);
55✔
1145
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
55✔
1146
  write_dataset(group_id, "coefficients", coeffs);
55✔
1147
}
55✔
1148

1149
double SurfaceXTorus::evaluate(Position r) const
811,263✔
1150
{
1151
  double x = r.x - x0_;
811,263✔
1152
  double y = r.y - y0_;
811,263✔
1153
  double z = r.z - z0_;
811,263✔
1154
  return (x * x) / (B_ * B_) +
1,622,526✔
1155
         std::pow(std::sqrt(y * y + z * z) - A_, 2) / (C_ * C_) - 1.;
811,263✔
1156
}
1157

1158
double SurfaceXTorus::distance(Position r, Direction u, bool coincident) const
8,348,615✔
1159
{
1160
  double x = r.x - x0_;
8,348,615✔
1161
  double y = r.y - y0_;
8,348,615✔
1162
  double z = r.z - z0_;
8,348,615✔
1163
  return torus_distance(y, z, x, u.y, u.z, u.x, A_, B_, C_, coincident);
8,348,615✔
1164
}
1165

1166
Direction SurfaceXTorus::normal(Position r) const
×
1167
{
1168
  // reduce the expansion of the full form for torus
1169
  double x = r.x - x0_;
×
1170
  double y = r.y - y0_;
×
1171
  double z = r.z - z0_;
×
1172

1173
  // f(x,y,z) = x^2/B^2 + (sqrt(y^2 + z^2) - A)^2/C^2 - 1
1174
  // ∂f/∂x = 2x/B^2
1175
  // ∂f/∂y = 2y(g - A)/(g*C^2) where g = sqrt(y^2 + z^2)
1176
  // ∂f/∂z = 2z(g - A)/(g*C^2)
1177
  // Multiplying by g*C^2*B^2 / 2 gives:
1178
  double g = std::sqrt(y * y + z * z);
×
1179
  double nx = C_ * C_ * g * x;
×
1180
  double ny = y * (g - A_) * B_ * B_;
×
1181
  double nz = z * (g - A_) * B_ * B_;
×
1182
  Direction n(nx, ny, nz);
×
1183
  return n / n.norm();
×
1184
}
1185

1186
//==============================================================================
1187
// SurfaceYTorus implementation
1188
//==============================================================================
NEW
1189
bool SurfaceYTorus::triso_in_mesh(
×
1190
  vector<double> mesh_center, vector<double> lattice_pitch) const
1191
{
NEW
1192
  return false;
×
1193
}
1194
SurfaceYTorus::SurfaceYTorus(pugi::xml_node surf_node) : Surface(surf_node)
59✔
1195
{
1196
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
59✔
1197
}
59✔
1198

1199
void SurfaceYTorus::to_hdf5_inner(hid_t group_id) const
55✔
1200
{
1201
  write_string(group_id, "type", "y-torus", false);
55✔
1202
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
55✔
1203
  write_dataset(group_id, "coefficients", coeffs);
55✔
1204
}
55✔
1205

1206
double SurfaceYTorus::evaluate(Position r) const
777,535✔
1207
{
1208
  double x = r.x - x0_;
777,535✔
1209
  double y = r.y - y0_;
777,535✔
1210
  double z = r.z - z0_;
777,535✔
1211
  return (y * y) / (B_ * B_) +
1,555,070✔
1212
         std::pow(std::sqrt(x * x + z * z) - A_, 2) / (C_ * C_) - 1.;
777,535✔
1213
}
1214

1215
double SurfaceYTorus::distance(Position r, Direction u, bool coincident) const
8,413,900✔
1216
{
1217
  double x = r.x - x0_;
8,413,900✔
1218
  double y = r.y - y0_;
8,413,900✔
1219
  double z = r.z - z0_;
8,413,900✔
1220
  return torus_distance(x, z, y, u.x, u.z, u.y, A_, B_, C_, coincident);
8,413,900✔
1221
}
1222

1223
Direction SurfaceYTorus::normal(Position r) const
×
1224
{
1225
  // reduce the expansion of the full form for torus
1226
  double x = r.x - x0_;
×
1227
  double y = r.y - y0_;
×
1228
  double z = r.z - z0_;
×
1229

1230
  // f(x,y,z) = y^2/B^2 + (sqrt(x^2 + z^2) - A)^2/C^2 - 1
1231
  // ∂f/∂x = 2x(g - A)/(g*C^2) where g = sqrt(x^2 + z^2)
1232
  // ∂f/∂y = 2y/B^2
1233
  // ∂f/∂z = 2z(g - A)/(g*C^2)
1234
  // Multiplying by g*C^2*B^2 / 2 gives:
1235
  double g = std::sqrt(x * x + z * z);
×
1236
  double nx = x * (g - A_) * B_ * B_;
×
1237
  double ny = C_ * C_ * g * y;
×
1238
  double nz = z * (g - A_) * B_ * B_;
×
1239
  Direction n(nx, ny, nz);
×
1240
  return n / n.norm();
×
1241
}
1242

1243
//==============================================================================
1244
// SurfaceZTorus implementation
1245
//==============================================================================
NEW
1246
bool SurfaceZTorus::triso_in_mesh(
×
1247
  vector<double> mesh_center, vector<double> lattice_pitch) const
1248
{
NEW
1249
  return false;
×
1250
}
1251

1252
SurfaceZTorus::SurfaceZTorus(pugi::xml_node surf_node) : Surface(surf_node)
104✔
1253
{
1254
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
104✔
1255
}
104✔
1256

1257
void SurfaceZTorus::to_hdf5_inner(hid_t group_id) const
88✔
1258
{
1259
  write_string(group_id, "type", "z-torus", false);
88✔
1260
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
88✔
1261
  write_dataset(group_id, "coefficients", coeffs);
88✔
1262
}
88✔
1263

1264
double SurfaceZTorus::evaluate(Position r) const
1,308,989✔
1265
{
1266
  double x = r.x - x0_;
1,308,989✔
1267
  double y = r.y - y0_;
1,308,989✔
1268
  double z = r.z - z0_;
1,308,989✔
1269
  return (z * z) / (B_ * B_) +
2,617,978✔
1270
         std::pow(std::sqrt(x * x + y * y) - A_, 2) / (C_ * C_) - 1.;
1,308,989✔
1271
}
1272

1273
double SurfaceZTorus::distance(Position r, Direction u, bool coincident) const
9,772,048✔
1274
{
1275
  double x = r.x - x0_;
9,772,048✔
1276
  double y = r.y - y0_;
9,772,048✔
1277
  double z = r.z - z0_;
9,772,048✔
1278
  return torus_distance(x, y, z, u.x, u.y, u.z, A_, B_, C_, coincident);
9,772,048✔
1279
}
1280

1281
Direction SurfaceZTorus::normal(Position r) const
×
1282
{
1283
  // reduce the expansion of the full form for torus
1284
  double x = r.x - x0_;
×
1285
  double y = r.y - y0_;
×
1286
  double z = r.z - z0_;
×
1287

1288
  // f(x,y,z) = z^2/B^2 + (sqrt(x^2 + y^2) - A)^2/C^2 - 1
1289
  // ∂f/∂x = 2x(g - A)/(g*C^2) where g = sqrt(x^2 + y^2)
1290
  // ∂f/∂y = 2y(g - A)/(g*C^2)
1291
  // ∂f/∂z = 2z/B^2
1292
  // Multiplying by g*C^2*B^2 / 2 gives:
1293
  double g = std::sqrt(x * x + y * y);
×
1294
  double nx = x * (g - A_) * B_ * B_;
×
1295
  double ny = y * (g - A_) * B_ * B_;
×
1296
  double nz = C_ * C_ * g * z;
×
1297
  Position n(nx, ny, nz);
×
1298
  return n / n.norm();
×
1299
}
1300

1301
//==============================================================================
1302

1303
void read_surfaces(pugi::xml_node node,
8,228✔
1304
  std::set<std::pair<int, int>>& periodic_pairs,
1305
  std::unordered_map<int, double>& albedo_map,
1306
  std::unordered_map<int, int>& periodic_sense_map)
1307
{
1308
  // Count the number of surfaces
1309
  int n_surfaces = 0;
8,228✔
1310
  for (pugi::xml_node surf_node : node.children("surface")) {
53,306✔
1311
    n_surfaces++;
45,078✔
1312
  }
1313

1314
  // Loop over XML surface elements and populate the array.  Keep track of
1315
  // periodic surfaces and their albedos.
1316
  model::surfaces.reserve(n_surfaces);
8,228✔
1317
  {
8,228✔
1318
    pugi::xml_node surf_node;
8,228✔
1319
    int i_surf;
8,228✔
1320
    for (surf_node = node.child("surface"), i_surf = 0; surf_node;
53,306✔
1321
         surf_node = surf_node.next_sibling("surface"), i_surf++) {
45,078✔
1322
      std::string surf_type = get_node_value(surf_node, "type", true, true);
45,078✔
1323

1324
      // Allocate and initialize the new surface
1325

1326
      if (surf_type == "x-plane") {
45,078✔
1327
        model::surfaces.push_back(make_unique<SurfaceXPlane>(surf_node));
10,797✔
1328

1329
      } else if (surf_type == "y-plane") {
34,281✔
1330
        model::surfaces.push_back(make_unique<SurfaceYPlane>(surf_node));
9,720✔
1331

1332
      } else if (surf_type == "z-plane") {
24,561✔
1333
        model::surfaces.push_back(make_unique<SurfaceZPlane>(surf_node));
7,095✔
1334

1335
      } else if (surf_type == "plane") {
17,466✔
1336
        model::surfaces.push_back(make_unique<SurfacePlane>(surf_node));
1,964✔
1337

1338
      } else if (surf_type == "x-cylinder") {
15,502✔
1339
        model::surfaces.push_back(make_unique<SurfaceXCylinder>(surf_node));
45✔
1340

1341
      } else if (surf_type == "y-cylinder") {
15,457✔
1342
        model::surfaces.push_back(make_unique<SurfaceYCylinder>(surf_node));
15✔
1343

1344
      } else if (surf_type == "z-cylinder") {
15,442✔
1345
        model::surfaces.push_back(make_unique<SurfaceZCylinder>(surf_node));
5,256✔
1346

1347
      } else if (surf_type == "sphere") {
10,186✔
1348
        model::surfaces.push_back(make_unique<SurfaceSphere>(surf_node));
9,934✔
1349

1350
      } else if (surf_type == "x-cone") {
252!
1351
        model::surfaces.push_back(make_unique<SurfaceXCone>(surf_node));
×
1352

1353
      } else if (surf_type == "y-cone") {
252!
1354
        model::surfaces.push_back(make_unique<SurfaceYCone>(surf_node));
×
1355

1356
      } else if (surf_type == "z-cone") {
252✔
1357
        model::surfaces.push_back(make_unique<SurfaceZCone>(surf_node));
15✔
1358

1359
      } else if (surf_type == "quadric") {
237✔
1360
        model::surfaces.push_back(make_unique<SurfaceQuadric>(surf_node));
15✔
1361

1362
      } else if (surf_type == "x-torus") {
222✔
1363
        model::surfaces.push_back(std::make_unique<SurfaceXTorus>(surf_node));
59✔
1364

1365
      } else if (surf_type == "y-torus") {
163✔
1366
        model::surfaces.push_back(std::make_unique<SurfaceYTorus>(surf_node));
59✔
1367

1368
      } else if (surf_type == "z-torus") {
104!
1369
        model::surfaces.push_back(std::make_unique<SurfaceZTorus>(surf_node));
104✔
1370

1371
      } else {
1372
        fatal_error(fmt::format("Invalid surface type, \"{}\"", surf_type));
×
1373
      }
1374

1375
      // Check for a periodic surface
1376
      if (check_for_node(surf_node, "boundary")) {
45,078✔
1377
        std::string surf_bc = get_node_value(surf_node, "boundary", true, true);
26,197✔
1378
        if (surf_bc == "periodic") {
26,197✔
1379
          periodic_sense_map[model::surfaces.back()->id_] = 0;
546✔
1380
          // Check for surface albedo. Skip sanity check as it is already done
1381
          // in the Surface class's constructor.
1382
          if (check_for_node(surf_node, "albedo")) {
546!
1383
            albedo_map[model::surfaces.back()->id_] =
×
1384
              std::stod(get_node_value(surf_node, "albedo"));
×
1385
          }
1386
          if (check_for_node(surf_node, "periodic_surface_id")) {
546✔
1387
            int i_periodic =
356✔
1388
              std::stoi(get_node_value(surf_node, "periodic_surface_id"));
712✔
1389
            int lo_id = std::min(model::surfaces.back()->id_, i_periodic);
356✔
1390
            int hi_id = std::max(model::surfaces.back()->id_, i_periodic);
356✔
1391
            periodic_pairs.insert({lo_id, hi_id});
356✔
1392
          } else {
1393
            periodic_pairs.insert({model::surfaces.back()->id_, -1});
190✔
1394
          }
1395
        }
1396
      }
26,197✔
1397
    }
45,078✔
1398
  }
1399

1400
  // Fill the surface map
1401
  for (int i_surf = 0; i_surf < model::surfaces.size(); i_surf++) {
53,306✔
1402
    int id = model::surfaces[i_surf]->id_;
45,078!
1403
    auto in_map = model::surface_map.find(id);
45,078!
1404
    if (in_map == model::surface_map.end()) {
45,078!
1405
      model::surface_map[id] = i_surf;
45,078✔
1406
    } else {
1407
      fatal_error(
×
1408
        fmt::format("Two or more surfaces use the same unique ID: {}", id));
×
1409
    }
1410
  }
1411
}
8,228✔
1412

1413
void prepare_boundary_conditions(std::set<std::pair<int, int>>& periodic_pairs,
8,226✔
1414
  std::unordered_map<int, double>& albedo_map,
1415
  std::unordered_map<int, int>& periodic_sense_map)
1416
{
1417
  // Fill the senses map for periodic surfaces
1418
  auto n_periodic = periodic_sense_map.size();
8,226✔
1419
  for (const auto& cell : model::cells) {
8,483✔
1420
    if (n_periodic == 0)
8,352✔
1421
      break; // Early exit once all periodic surfaces found
1422

1423
    for (auto s : cell->surfaces()) {
1,335✔
1424
      auto surf_idx = std::abs(s) - 1;
1,078✔
1425
      auto id = model::surfaces[surf_idx]->id_;
1,078✔
1426

1427
      if (periodic_sense_map.count(id)) {
1,078✔
1428
        periodic_sense_map[id] = std::copysign(1, s);
546✔
1429
        --n_periodic;
546✔
1430
      }
1431
    }
257✔
1432
  }
1433

1434
  // Resolve unpaired periodic surfaces.  A lambda function is used with
1435
  // std::find_if to identify the unpaired surfaces.
1436
  auto is_unresolved_pair = [](const std::pair<int, int> p) {
8,594✔
1437
    return p.second == -1;
368✔
1438
  };
1439
  auto first_unresolved = std::find_if(
8,226✔
1440
    periodic_pairs.begin(), periodic_pairs.end(), is_unresolved_pair);
1441
  if (first_unresolved != periodic_pairs.end()) {
8,226✔
1442
    // Found one unpaired surface; search for a second one
1443
    auto next_elem = first_unresolved;
95✔
1444
    next_elem++;
95!
1445
    auto second_unresolved =
95!
1446
      std::find_if(next_elem, periodic_pairs.end(), is_unresolved_pair);
95!
1447
    if (second_unresolved == periodic_pairs.end()) {
95!
1448
      fatal_error("Found only one periodic surface without a specified partner."
×
1449
                  " Please specify the partner for each periodic surface.");
1450
    }
1451

1452
    // Make sure there isn't a third unpaired surface
1453
    next_elem = second_unresolved;
95✔
1454
    next_elem++;
95!
1455
    auto third_unresolved =
95!
1456
      std::find_if(next_elem, periodic_pairs.end(), is_unresolved_pair);
95!
1457
    if (third_unresolved != periodic_pairs.end()) {
95!
1458
      fatal_error(
×
1459
        "Found at least three periodic surfaces without a specified "
1460
        "partner. Please specify the partner for each periodic surface.");
1461
    }
1462

1463
    // Add the completed pair and remove the old, unpaired entries
1464
    int lo_id = std::min(first_unresolved->first, second_unresolved->first);
95!
1465
    int hi_id = std::max(first_unresolved->first, second_unresolved->first);
95!
1466
    periodic_pairs.insert({lo_id, hi_id});
95✔
1467
    periodic_pairs.erase(first_unresolved);
95✔
1468
    periodic_pairs.erase(second_unresolved);
95✔
1469
  }
1470

1471
  // Assign the periodic boundary conditions with albedos
1472
  for (auto periodic_pair : periodic_pairs) {
8,499✔
1473
    int i_surf = model::surface_map[periodic_pair.first];
273✔
1474
    int j_surf = model::surface_map[periodic_pair.second];
273✔
1475
    Surface& surf1 {*model::surfaces[i_surf]};
273✔
1476
    Surface& surf2 {*model::surfaces[j_surf]};
273✔
1477

1478
    // Compute the dot product of the surface normals
1479
    Direction norm1 = surf1.normal({0, 0, 0});
273✔
1480
    Direction norm2 = surf2.normal({0, 0, 0});
273✔
1481
    norm1 /= norm1.norm();
273✔
1482
    norm2 /= norm2.norm();
273✔
1483
    double dot_prod = norm1.dot(norm2);
273✔
1484

1485
    // If the dot product is 1 (to within floating point precision) then the
1486
    // planes are parallel which indicates a translational periodic boundary
1487
    // condition.  Otherwise, it is a rotational periodic BC.
1488
    if (std::abs(1.0 - dot_prod) < FP_PRECISION) {
273✔
1489
      surf1.bc_ = make_unique<TranslationalPeriodicBC>(i_surf, j_surf);
113✔
1490
      surf2.bc_ = make_unique<TranslationalPeriodicBC>(j_surf, i_surf);
113✔
1491
    } else {
1492
      // check that both normals have at least one 0 component
1493
      if (std::abs(norm1.x) > FP_PRECISION &&
160✔
1494
          std::abs(norm1.y) > FP_PRECISION &&
160!
1495
          std::abs(norm1.z) > FP_PRECISION) {
115!
1496
        fatal_error(fmt::format(
×
1497
          "The normal ({}) of the periodic surface ({}) does not contain any "
1498
          "component with a zero value. A RotationalPeriodicBC requires one "
1499
          "component which is zero for both plane normals.",
1500
          norm1, i_surf));
1501
      }
1502
      if (std::abs(norm2.x) > FP_PRECISION &&
160✔
1503
          std::abs(norm2.y) > FP_PRECISION &&
160!
1504
          std::abs(norm2.z) > FP_PRECISION) {
115!
1505
        fatal_error(fmt::format(
×
1506
          "The normal ({}) of the periodic surface ({}) does not contain any "
1507
          "component with a zero value. A RotationalPeriodicBC requires one "
1508
          "component which is zero for both plane normals.",
1509
          norm2, j_surf));
1510
      }
1511
      // find common zero component, which indicates the periodic axis
1512
      RotationalPeriodicBC::PeriodicAxis axis;
160✔
1513
      if (std::abs(norm1.x) <= FP_PRECISION &&
160✔
1514
          std::abs(norm2.x) <= FP_PRECISION) {
15!
1515
        axis = RotationalPeriodicBC::PeriodicAxis::x;
15✔
1516
      } else if (std::abs(norm1.y) <= FP_PRECISION &&
145✔
1517
                 std::abs(norm2.y) <= FP_PRECISION) {
30✔
1518
        axis = RotationalPeriodicBC::PeriodicAxis::y;
15✔
1519
      } else if (std::abs(norm1.z) <= FP_PRECISION &&
130!
1520
                 std::abs(norm2.z) <= FP_PRECISION) {
130!
1521
        axis = RotationalPeriodicBC::PeriodicAxis::z;
130✔
1522
      } else {
1523
        fatal_error(fmt::format(
×
1524
          "There is no component which is 0.0 in both normal vectors. This "
1525
          "indicates that the two planes are not periodic about the X, Y, or Z "
1526
          "axis, which is not supported."));
1527
      }
1528
      auto i_sign = periodic_sense_map[periodic_pair.first];
160✔
1529
      auto j_sign = periodic_sense_map[periodic_pair.second];
160✔
1530
      surf1.bc_ = make_unique<RotationalPeriodicBC>(
160✔
1531
        i_sign * (i_surf + 1), j_sign * (j_surf + 1), axis);
160✔
1532
      surf2.bc_ = make_unique<RotationalPeriodicBC>(
160✔
1533
        j_sign * (j_surf + 1), i_sign * (i_surf + 1), axis);
320✔
1534
    }
1535

1536
    // If albedo data is present in albedo map, set the boundary albedo.
1537
    if (albedo_map.count(surf1.id_)) {
273!
1538
      surf1.bc_->set_albedo(albedo_map[surf1.id_]);
×
1539
    }
1540
    if (albedo_map.count(surf2.id_)) {
273!
1541
      surf2.bc_->set_albedo(albedo_map[surf2.id_]);
273✔
1542
    }
1543
  }
1544
}
8,226✔
1545

1546
void free_memory_surfaces()
8,357✔
1547
{
1548
  model::surfaces.clear();
8,357✔
1549
  model::surface_map.clear();
8,357✔
1550
}
8,357✔
1551

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