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

openmc-dev / openmc / 15477579070

05 Jun 2025 09:23PM UTC coverage: 85.103% (-0.1%) from 85.2%
15477579070

Pull #3402

github

web-flow
Merge 1b72f3b34 into 4943fa363
Pull Request #3402: Add transformation boundary condition

64 of 112 new or added lines in 4 files covered. (57.14%)

702 existing lines in 33 files now uncovered.

52213 of 61353 relevant lines covered (85.1%)

36805311.13 hits per line

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

71.65
/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/container_util.h"
13
#include "openmc/error.h"
14
#include "openmc/external/quartic_solver.h"
15
#include "openmc/hdf5_interface.h"
16
#include "openmc/math_functions.h"
17
#include "openmc/random_lcg.h"
18
#include "openmc/settings.h"
19
#include "openmc/string_utils.h"
20
#include "openmc/xml_interface.h"
21

22
namespace openmc {
23

24
//==============================================================================
25
// Global variables
26
//==============================================================================
27

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

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

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

48
  // Copy the coefficients
49
  int i = 0;
36,336✔
50
  for (auto c : coeffs) {
110,964✔
51
    *c = coeffs_file[i++];
74,628✔
52
  }
53
}
36,336✔
54

55
//==============================================================================
56
// Surface implementation
57
//==============================================================================
58

59
Surface::Surface() {} // empty constructor
810✔
60

61
Surface::Surface(pugi::xml_node surf_node)
36,336✔
62
{
63
  if (check_for_node(surf_node, "id")) {
36,336✔
64
    id_ = std::stoi(get_node_value(surf_node, "id"));
36,336✔
65
    if (contains(settings::source_write_surf_id, id_) ||
71,895✔
66
        settings::source_write_surf_id.empty()) {
35,559✔
67
      surf_source_ = true;
35,327✔
68
    }
69
  } else {
70
    fatal_error("Must specify id of surface in geometry XML file.");
×
71
  }
72

73
  if (check_for_node(surf_node, "name")) {
36,336✔
74
    name_ = get_node_value(surf_node, "name", false);
8,402✔
75
  }
76

77
  if (check_for_node(surf_node, "boundary")) {
36,336✔
78
    std::string surf_bc = get_node_value(surf_node, "boundary", true, true);
21,442✔
79

80
    if (surf_bc == "transmission" || surf_bc == "transmit" || surf_bc.empty()) {
21,442✔
81
      // Leave the bc_ a nullptr
82
    } else if (surf_bc == "vacuum") {
21,442✔
83
      bc_ = make_unique<VacuumBC>();
10,335✔
84
    } else if (surf_bc == "reflective" || surf_bc == "reflect" ||
11,597✔
85
               surf_bc == "reflecting") {
490✔
86
      bc_ = make_unique<ReflectiveBC>();
10,617✔
87
    } else if (surf_bc == "white") {
490✔
88
      bc_ = make_unique<WhiteBC>();
96✔
89
    } else if (surf_bc == "periodic") {
394✔
90
      // Periodic BCs are handled separately
91
    } else if (surf_bc == "transformation") {
96✔
92
      int vector_size_exp = 12;
96✔
93
      vector<double> dir_trans(vector_size_exp);
96✔
94
      vector<double> pos_trans(vector_size_exp);
96✔
95

96
      if (check_for_node(surf_node, "direction_transformation")) {
96✔
97
        dir_trans =
98
          get_node_array<double>(surf_node, "direction_transformation");
96✔
99
        if (dir_trans.size() != vector_size_exp) {
96✔
NEW
100
          fatal_error(fmt::format(
×
101
            "Transformation on surface {} expects direction matrix size {} "
102
            "but was given size {}",
NEW
103
            id_, vector_size_exp, dir_trans.size()));
×
104
        }
105
      } else {
NEW
106
        dir_trans = {
×
NEW
107
          1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0};
×
108
      }
109

110
      if (check_for_node(surf_node, "position_transformation")) {
96✔
111
        pos_trans =
NEW
112
          get_node_array<double>(surf_node, "position_transformation");
×
NEW
113
        if (pos_trans.size() != vector_size_exp) {
×
NEW
114
          fatal_error(
×
NEW
115
            fmt::format("Transformation on surface {} expects position matrix "
×
116
                        "size {} but was given size {}",
NEW
117
              id_, vector_size_exp, pos_trans.size()));
×
118
        }
119
      } else {
120
        pos_trans = {
96✔
121
          1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0};
96✔
122
      }
123
      bc_ = make_unique<TransformationBC>(dir_trans, pos_trans);
96✔
124
    } else {
96✔
125
      fatal_error(fmt::format("Unknown boundary condition \"{}\" specified "
×
126
                              "on surface {}",
127
        surf_bc, id_));
×
128
    }
129

130
    if (check_for_node(surf_node, "albedo") && bc_) {
21,442✔
131
      double surf_alb = std::stod(get_node_value(surf_node, "albedo"));
96✔
132

133
      if (surf_alb < 0.0)
96✔
134
        fatal_error(fmt::format("Surface {} has an albedo of {}. "
×
135
                                "Albedo values must be positive.",
136
          id_, surf_alb));
×
137

138
      if (surf_alb > 1.0)
96✔
139
        warning(fmt::format("Surface {} has an albedo of {}. "
×
140
                            "Albedos greater than 1 may cause "
141
                            "unphysical behaviour.",
142
          id_, surf_alb));
×
143

144
      bc_->set_albedo(surf_alb);
96✔
145
    }
146
  }
21,442✔
147
}
36,336✔
148

149
bool Surface::sense(Position r, Direction u) const
2,147,483,647✔
150
{
151
  // Evaluate the surface equation at the particle's coordinates to determine
152
  // which side the particle is on.
153
  const double f = evaluate(r);
2,147,483,647✔
154

155
  // Check which side of surface the point is on.
156
  if (std::abs(f) < FP_COINCIDENT) {
2,147,483,647✔
157
    // Particle may be coincident with this surface. To determine the sense, we
158
    // look at the direction of the particle relative to the surface normal (by
159
    // default in the positive direction) via their dot product.
160
    return u.dot(normal(r)) > 0.0;
2,318,645✔
161
  }
162
  return f > 0.0;
2,147,483,647✔
163
}
164

165
Direction Surface::reflect(Position r, Direction u, GeometryState* p) const
607,981,458✔
166
{
167
  // Determine projection of direction onto normal and squared magnitude of
168
  // normal.
169
  Direction n = normal(r);
607,981,458✔
170

171
  // Reflect direction according to normal.
172
  return u.reflect(n);
1,215,962,916✔
173
}
174

175
Direction Surface::diffuse_reflect(
1,016,983✔
176
  Position r, Direction u, uint64_t* seed) const
177
{
178
  // Diffuse reflect direction according to the normal.
179
  // cosine distribution
180

181
  Direction n = this->normal(r);
1,016,983✔
182
  n /= n.norm();
1,016,983✔
183
  const double projection = n.dot(u);
1,016,983✔
184

185
  // sample from inverse function, u=sqrt(rand) since p(u)=2u, so F(u)=u^2
186
  const double mu =
187
    (projection >= 0.0) ? -std::sqrt(prn(seed)) : std::sqrt(prn(seed));
1,016,983✔
188

189
  // sample azimuthal distribution uniformly
190
  u = rotate_angle(n, mu, nullptr, seed);
1,016,983✔
191

192
  // normalize the direction
193
  return u / u.norm();
2,033,966✔
194
}
195

196
void Surface::to_hdf5(hid_t group_id) const
28,824✔
197
{
198
  hid_t surf_group = create_group(group_id, fmt::format("surface {}", id_));
57,648✔
199

200
  if (geom_type() == GeometryType::DAG) {
28,824✔
201
    write_string(surf_group, "geom_type", "dagmc", false);
594✔
202
  } else if (geom_type() == GeometryType::CSG) {
28,230✔
203
    write_string(surf_group, "geom_type", "csg", false);
28,230✔
204

205
    if (bc_) {
28,230✔
206
      write_string(surf_group, "boundary_type", bc_->type(), false);
16,880✔
207
      bc_->to_hdf5(surf_group);
16,880✔
208

209
      // write periodic surface ID
210
      if (bc_->type() == "periodic") {
16,880✔
211
        auto pbc = dynamic_cast<PeriodicBC*>(bc_.get());
238✔
212
        Surface& surf1 {*model::surfaces[pbc->i_surf()]};
238✔
213
        Surface& surf2 {*model::surfaces[pbc->j_surf()]};
238✔
214

215
        if (id_ == surf1.id_) {
238✔
216
          write_dataset(surf_group, "periodic_surface_id", surf2.id_);
119✔
217
        } else {
218
          write_dataset(surf_group, "periodic_surface_id", surf1.id_);
119✔
219
        }
220
      }
221
    } else {
222
      write_string(surf_group, "boundary_type", "transmission", false);
11,350✔
223
    }
224
  }
225

226
  if (!name_.empty()) {
28,824✔
227
    write_string(surf_group, "name", name_, false);
6,231✔
228
  }
229

230
  to_hdf5_inner(surf_group);
28,824✔
231

232
  close_group(surf_group);
28,824✔
233
}
28,824✔
234

235
//==============================================================================
236
// Generic functions for x-, y-, and z-, planes.
237
//==============================================================================
238

239
// The template parameter indicates the axis normal to the plane.
240
template<int i>
241
double axis_aligned_plane_distance(
2,147,483,647✔
242
  Position r, Direction u, bool coincident, double offset)
243
{
244
  const double f = offset - r[i];
2,147,483,647✔
245
  if (coincident || std::abs(f) < FP_COINCIDENT || u[i] == 0.0)
2,147,483,647✔
246
    return INFTY;
606,934,712✔
247
  const double d = f / u[i];
2,147,483,647✔
248
  if (d < 0.0)
2,147,483,647✔
249
    return INFTY;
2,147,483,647✔
250
  return d;
2,147,483,647✔
251
}
252

2,147,483,647✔
253
//==============================================================================
254
// SurfaceXPlane implementation
255
//==============================================================================
2,147,483,647✔
256

2,147,483,647✔
257
SurfaceXPlane::SurfaceXPlane(pugi::xml_node surf_node) : Surface(surf_node)
57,430,390✔
258
{
2,147,483,647✔
259
  read_coeffs(surf_node, id_, {&x0_});
2,147,483,647✔
260
}
1,149,884,944✔
261

1,229,333,889✔
262
double SurfaceXPlane::evaluate(Position r) const
263
{
2,147,483,647✔
264
  return r.x - x0_;
265
}
266

2,147,483,647✔
267
double SurfaceXPlane::distance(Position r, Direction u, bool coincident) const
2,147,483,647✔
268
{
286,489,277✔
269
  return axis_aligned_plane_distance<0>(r, u, coincident, x0_);
2,147,483,647✔
270
}
2,147,483,647✔
271

2,147,483,647✔
272
Direction SurfaceXPlane::normal(Position r) const
2,147,483,647✔
273
{
274
  return {1., 0., 0.};
2,147,483,647✔
275
}
276

277
void SurfaceXPlane::to_hdf5_inner(hid_t group_id) const
2,147,483,647✔
278
{
2,147,483,647✔
279
  write_string(group_id, "type", "x-plane", false);
263,015,045✔
280
  array<double, 1> coeffs {{x0_}};
2,147,483,647✔
281
  write_dataset(group_id, "coefficients", coeffs);
2,147,483,647✔
282
}
2,147,483,647✔
283

2,147,483,647✔
284
BoundingBox SurfaceXPlane::bounding_box(bool pos_side) const
285
{
286
  if (pos_side) {
287
    return {x0_, INFTY, -INFTY, INFTY, -INFTY, INFTY};
288
  } else {
289
    return {-INFTY, x0_, -INFTY, INFTY, -INFTY, INFTY};
290
  }
8,665✔
291
}
292

8,665✔
293
//==============================================================================
8,665✔
294
// SurfaceYPlane implementation
295
//==============================================================================
1,523,226,541✔
296

297
SurfaceYPlane::SurfaceYPlane(pugi::xml_node surf_node) : Surface(surf_node)
1,523,226,541✔
298
{
299
  read_coeffs(surf_node, id_, {&y0_});
300
}
2,147,483,647✔
301

302
double SurfaceYPlane::evaluate(Position r) const
2,147,483,647✔
303
{
304
  return r.y - y0_;
305
}
263,704,367✔
306

307
double SurfaceYPlane::distance(Position r, Direction u, bool coincident) const
263,704,367✔
308
{
309
  return axis_aligned_plane_distance<1>(r, u, coincident, y0_);
310
}
6,677✔
311

312
Direction SurfaceYPlane::normal(Position r) const
6,677✔
313
{
6,677✔
314
  return {0., 1., 0.};
6,677✔
315
}
6,677✔
316

317
void SurfaceYPlane::to_hdf5_inner(hid_t group_id) const
242✔
318
{
319
  write_string(group_id, "type", "y-plane", false);
242✔
320
  array<double, 1> coeffs {{y0_}};
121✔
321
  write_dataset(group_id, "coefficients", coeffs);
322
}
121✔
323

324
BoundingBox SurfaceYPlane::bounding_box(bool pos_side) const
325
{
326
  if (pos_side) {
327
    return {-INFTY, INFTY, y0_, INFTY, -INFTY, INFTY};
328
  } else {
329
    return {-INFTY, INFTY, -INFTY, y0_, -INFTY, INFTY};
330
  }
7,721✔
331
}
332

7,721✔
333
//==============================================================================
7,721✔
334
// SurfaceZPlane implementation
335
//==============================================================================
1,520,962,224✔
336

337
SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) : Surface(surf_node)
1,520,962,224✔
338
{
339
  read_coeffs(surf_node, id_, {&z0_});
340
}
2,147,483,647✔
341

342
double SurfaceZPlane::evaluate(Position r) const
2,147,483,647✔
343
{
344
  return r.z - z0_;
345
}
287,959,644✔
346

347
double SurfaceZPlane::distance(Position r, Direction u, bool coincident) const
287,959,644✔
348
{
349
  return axis_aligned_plane_distance<2>(r, u, coincident, z0_);
350
}
6,085✔
351

352
Direction SurfaceZPlane::normal(Position r) const
6,085✔
353
{
6,085✔
354
  return {0., 0., 1.};
6,085✔
355
}
6,085✔
356

357
void SurfaceZPlane::to_hdf5_inner(hid_t group_id) const
242✔
358
{
359
  write_string(group_id, "type", "z-plane", false);
242✔
360
  array<double, 1> coeffs {{z0_}};
121✔
361
  write_dataset(group_id, "coefficients", coeffs);
362
}
121✔
363

364
BoundingBox SurfaceZPlane::bounding_box(bool pos_side) const
365
{
366
  if (pos_side) {
367
    return {-INFTY, INFTY, -INFTY, INFTY, z0_, INFTY};
368
  } else {
369
    return {-INFTY, INFTY, -INFTY, INFTY, -INFTY, z0_};
370
  }
5,843✔
371
}
372

5,843✔
373
//==============================================================================
5,843✔
374
// SurfacePlane implementation
375
//==============================================================================
496,260,306✔
376

377
SurfacePlane::SurfacePlane(pugi::xml_node surf_node) : Surface(surf_node)
496,260,306✔
378
{
379
  read_coeffs(surf_node, id_, {&A_, &B_, &C_, &D_});
380
}
2,147,483,647✔
381

382
double SurfacePlane::evaluate(Position r) const
2,147,483,647✔
383
{
384
  return A_ * r.x + B_ * r.y + C_ * r.z - D_;
385
}
58,100,723✔
386

387
double SurfacePlane::distance(Position r, Direction u, bool coincident) const
58,100,723✔
388
{
389
  const double f = A_ * r.x + B_ * r.y + C_ * r.z - D_;
390
  const double projection = A_ * u.x + B_ * u.y + C_ * u.z;
4,899✔
391
  if (coincident || std::abs(f) < FP_COINCIDENT || projection == 0.0) {
392
    return INFTY;
4,899✔
393
  } else {
4,899✔
394
    const double d = -f / projection;
4,899✔
395
    if (d < 0.0)
4,899✔
396
      return INFTY;
UNCOV
397
    return d;
×
398
  }
UNCOV
399
}
×
UNCOV
400

×
401
Direction SurfacePlane::normal(Position r) const
UNCOV
402
{
×
403
  return {A_, B_, C_};
404
}
405

406
void SurfacePlane::to_hdf5_inner(hid_t group_id) const
407
{
408
  write_string(group_id, "type", "plane", false);
409
  array<double, 4> coeffs {{A_, B_, C_, D_}};
410
  write_dataset(group_id, "coefficients", coeffs);
1,512✔
411
}
412

1,512✔
413
//==============================================================================
1,512✔
414
// Generic functions for x-, y-, and z-, cylinders
415
//==============================================================================
201,758,156✔
416

417
// The template parameters indicate the axes perpendicular to the axis of the
201,758,156✔
418
// cylinder.  offset1 and offset2 should correspond with i1 and i2,
419
// respectively.
420
template<int i1, int i2>
578,807,832✔
421
double axis_aligned_cylinder_evaluate(
422
  Position r, double offset1, double offset2, double radius)
578,807,832✔
423
{
578,807,832✔
424
  const double r1 = r.get<i1>() - offset1;
578,807,832✔
425
  const double r2 = r.get<i2>() - offset2;
35,639,394✔
426
  return r1 * r1 + r2 * r2 - radius * radius;
427
}
543,168,438✔
428

543,168,438✔
429
// The first template parameter indicates which axis the cylinder is aligned to.
253,690,068✔
430
// The other two parameters indicate the other two axes.  offset1 and offset2
289,478,370✔
431
// should correspond with i2 and i3, respectively.
432
template<int i1, int i2, int i3>
433
double axis_aligned_cylinder_distance(Position r, Direction u, bool coincident,
434
  double offset1, double offset2, double radius)
5,790,648✔
435
{
436
  const double a = 1.0 - u.get<i1>() * u.get<i1>(); // u^2 + v^2
5,790,648✔
437
  if (a == 0.0)
438
    return INFTY;
439

1,038✔
440
  const double r2 = r.get<i2>() - offset1;
441
  const double r3 = r.get<i3>() - offset2;
1,038✔
442
  const double k = r2 * u.get<i2>() + r3 * u.get<i3>();
1,038✔
443
  const double c = r2 * r2 + r3 * r3 - radius * radius;
1,038✔
444
  const double quad = k * k - a * c;
1,038✔
445

446
  if (quad < 0.0) {
447
    // No intersection with cylinder.
448
    return INFTY;
449

450
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
451
    // Particle is on the cylinder, thus one distance is positive/negative
452
    // and the other is zero. The sign of k determines if we are facing in or
453
    // out.
454
    if (k >= 0.0) {
1,417,241,288✔
455
      return INFTY;
456
    } else {
457
      return (-k + sqrt(quad)) / a;
1,417,241,288✔
458
    }
1,417,241,288✔
459

1,417,241,288✔
460
  } else if (c < 0.0) {
461
    // Particle is inside the cylinder, thus one distance must be negative
1,415,880,805✔
462
    // and one must be positive. The positive distance will be the one with
463
    // negative sign on sqrt(quad).
464
    return (-k + sqrt(quad)) / a;
1,415,880,805✔
465

1,415,880,805✔
466
  } else {
1,415,880,805✔
467
    // Particle is outside the cylinder, thus both distances are either
UNCOV
468
    // positive or negative. If positive, the smaller distance is the one
×
469
    // with positive sign on sqrt(quad).
470
    const double d = (-k - sqrt(quad)) / a;
UNCOV
471
    if (d < 0.0)
×
UNCOV
472
      return INFTY;
×
UNCOV
473
    return d;
×
474
  }
475
}
1,360,483✔
476

477
// The first template parameter indicates which axis the cylinder is aligned to.
478
// The other two parameters indicate the other two axes.  offset1 and offset2
1,360,483✔
479
// should correspond with i2 and i3, respectively.
1,360,483✔
480
template<int i1, int i2, int i3>
1,360,483✔
481
Direction axis_aligned_cylinder_normal(
482
  Position r, double offset1, double offset2)
483
{
484
  Direction u;
485
  u.get<i2>() = 2.0 * (r.get<i2>() - offset1);
486
  u.get<i3>() = 2.0 * (r.get<i3>() - offset2);
487
  u.get<i1>() = 0.0;
1,645,973,345✔
488
  return u;
489
}
490

1,645,973,345✔
491
//==============================================================================
1,645,973,345✔
492
// SurfaceXCylinder implementation
809,996✔
493
//==============================================================================
494

1,645,163,349✔
495
SurfaceXCylinder::SurfaceXCylinder(pugi::xml_node surf_node)
1,645,163,349✔
496
  : Surface(surf_node)
1,645,163,349✔
497
{
1,645,163,349✔
498
  read_coeffs(surf_node, id_, {&y0_, &z0_, &radius_});
1,645,163,349✔
499
}
500

1,645,163,349✔
501
double SurfaceXCylinder::evaluate(Position r) const
502
{
270,410,097✔
503
  return axis_aligned_cylinder_evaluate<1, 2>(r, y0_, z0_, radius_);
504
}
1,374,753,252✔
505

506
double SurfaceXCylinder::distance(
507
  Position r, Direction u, bool coincident) const
508
{
624,888,711✔
509
  return axis_aligned_cylinder_distance<0, 1, 2>(
313,533,497✔
510
    r, u, coincident, y0_, z0_, radius_);
511
}
311,355,214✔
512

513
Direction SurfaceXCylinder::normal(Position r) const
514
{
749,864,541✔
515
  return axis_aligned_cylinder_normal<0, 1, 2>(r, y0_, z0_);
516
}
517

518
void SurfaceXCylinder::to_hdf5_inner(hid_t group_id) const
304,255,112✔
519
{
520
  write_string(group_id, "type", "x-cylinder", false);
521
  array<double, 3> coeffs {{y0_, z0_, radius_}};
522
  write_dataset(group_id, "coefficients", coeffs);
523
}
524

445,609,429✔
525
BoundingBox SurfaceXCylinder::bounding_box(bool pos_side) const
445,609,429✔
526
{
64,088,061✔
527
  if (!pos_side) {
381,521,368✔
528
    return {-INFTY, INFTY, y0_ - radius_, y0_ + radius_, z0_ - radius_,
529
      z0_ + radius_};
530
  } else {
1,644,890,109✔
531
    return {};
532
  }
533
}
1,644,890,109✔
534
//==============================================================================
1,644,890,109✔
535
// SurfaceYCylinder implementation
369,996✔
536
//==============================================================================
537

1,644,520,113✔
538
SurfaceYCylinder::SurfaceYCylinder(pugi::xml_node surf_node)
1,644,520,113✔
539
  : Surface(surf_node)
1,644,520,113✔
540
{
1,644,520,113✔
541
  read_coeffs(surf_node, id_, {&x0_, &z0_, &radius_});
1,644,520,113✔
542
}
543

1,644,520,113✔
544
double SurfaceYCylinder::evaluate(Position r) const
545
{
270,410,097✔
546
  return axis_aligned_cylinder_evaluate<0, 2>(r, x0_, z0_, radius_);
547
}
1,374,110,016✔
548

549
double SurfaceYCylinder::distance(
550
  Position r, Direction u, bool coincident) const
551
{
624,888,711✔
552
  return axis_aligned_cylinder_distance<1, 0, 2>(
313,533,497✔
553
    r, u, coincident, x0_, z0_, radius_);
554
}
311,355,214✔
555

556
Direction SurfaceYCylinder::normal(Position r) const
557
{
749,221,305✔
558
  return axis_aligned_cylinder_normal<1, 0, 2>(r, x0_, z0_);
559
}
560

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

445,609,429✔
568
BoundingBox SurfaceYCylinder::bounding_box(bool pos_side) const
445,609,429✔
569
{
64,088,061✔
570
  if (!pos_side) {
381,521,368✔
571
    return {x0_ - radius_, x0_ + radius_, -INFTY, INFTY, z0_ - radius_,
572
      z0_ + radius_};
573
  } else {
×
574
    return {};
575
  }
UNCOV
576
}
×
577

×
UNCOV
578
//==============================================================================
×
579
// SurfaceZCylinder implementation
UNCOV
580
//==============================================================================
×
581

×
582
SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node)
×
UNCOV
583
  : Surface(surf_node)
×
584
{
×
585
  read_coeffs(surf_node, id_, {&x0_, &y0_, &radius_});
UNCOV
586
}
×
587

UNCOV
588
double SurfaceZCylinder::evaluate(Position r) const
×
589
{
UNCOV
590
  return axis_aligned_cylinder_evaluate<0, 1>(r, x0_, y0_, radius_);
×
591
}
592

593
double SurfaceZCylinder::distance(
UNCOV
594
  Position r, Direction u, bool coincident) const
×
UNCOV
595
{
×
596
  return axis_aligned_cylinder_distance<2, 0, 1>(
597
    r, u, coincident, x0_, y0_, radius_);
×
598
}
599

600
Direction SurfaceZCylinder::normal(Position r) const
×
601
{
602
  return axis_aligned_cylinder_normal<2, 0, 1>(r, x0_, y0_);
603
}
UNCOV
604

×
605
void SurfaceZCylinder::to_hdf5_inner(hid_t group_id) const
606
{
607
  write_string(group_id, "type", "z-cylinder", false);
608
  array<double, 3> coeffs {{x0_, y0_, radius_}};
609
  write_dataset(group_id, "coefficients", coeffs);
UNCOV
610
}
×
UNCOV
611

×
UNCOV
612
BoundingBox SurfaceZCylinder::bounding_box(bool pos_side) const
×
UNCOV
613
{
×
614
  if (!pos_side) {
615
    return {x0_ - radius_, x0_ + radius_, y0_ - radius_, y0_ + radius_, -INFTY,
616
      INFTY};
1,083,236✔
617
  } else {
618
    return {};
619
  }
1,083,236✔
620
}
1,083,236✔
621

440,000✔
622
//==============================================================================
623
// SurfaceSphere implementation
643,236✔
624
//==============================================================================
643,236✔
625

643,236✔
626
SurfaceSphere::SurfaceSphere(pugi::xml_node surf_node) : Surface(surf_node)
643,236✔
627
{
643,236✔
628
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_});
629
}
643,236✔
630

UNCOV
631
double SurfaceSphere::evaluate(Position r) const
×
632
{
633
  const double x = r.x - x0_;
643,236✔
634
  const double y = r.y - y0_;
635
  const double z = r.z - z0_;
636
  return x * x + y * y + z * z - radius_ * radius_;
UNCOV
637
}
×
UNCOV
638

×
639
double SurfaceSphere::distance(Position r, Direction u, bool coincident) const
640
{
×
641
  const double x = r.x - x0_;
642
  const double y = r.y - y0_;
643
  const double z = r.z - z0_;
643,236✔
644
  const double k = x * u.x + y * u.y + z * u.z;
645
  const double c = x * x + y * y + z * z - radius_ * radius_;
646
  const double quad = k * k - c;
647

643,236✔
648
  if (quad < 0.0) {
649
    // No intersection with sphere.
650
    return INFTY;
651

652
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
UNCOV
653
    // Particle is on the sphere, thus one distance is positive/negative and
×
UNCOV
654
    // the other is zero. The sign of k determines if we are facing in or out.
×
UNCOV
655
    if (k >= 0.0) {
×
UNCOV
656
      return INFTY;
×
657
    } else {
658
      return -k + sqrt(quad);
659
    }
660

661
  } else if (c < 0.0) {
662
    // Particle is inside the sphere, thus one distance must be negative and
663
    // one must be positive. The positive distance will be the one with
664
    // negative sign on sqrt(quad)
1,387,667✔
665
    return -k + sqrt(quad);
666

667
  } else {
1,387,667✔
668
    // Particle is outside the sphere, thus both distances are either positive
1,387,667✔
669
    // or negative. If positive, the smaller distance is the one with positive
1,387,667✔
670
    // sign on sqrt(quad).
1,387,667✔
671
    const double d = -k - sqrt(quad);
1,387,667✔
672
    if (d < 0.0)
673
      return INFTY;
1,387,667✔
674
    return d;
675
  }
676
}
1,387,667✔
677

1,387,667✔
678
Direction SurfaceSphere::normal(Position r) const
1,387,667✔
679
{
1,387,667✔
680
  return {2.0 * (r.x - x0_), 2.0 * (r.y - y0_), 2.0 * (r.z - z0_)};
1,387,667✔
681
}
682

×
683
void SurfaceSphere::to_hdf5_inner(hid_t group_id) const
684
{
685
  write_string(group_id, "type", "sphere", false);
×
UNCOV
686
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_}};
×
UNCOV
687
  write_dataset(group_id, "coefficients", coeffs);
×
UNCOV
688
}
×
UNCOV
689

×
690
BoundingBox SurfaceSphere::bounding_box(bool pos_side) const
UNCOV
691
{
×
692
  if (!pos_side) {
693
    return {x0_ - radius_, x0_ + radius_, y0_ - radius_, y0_ + radius_,
UNCOV
694
      z0_ - radius_, z0_ + radius_};
×
UNCOV
695
  } else {
×
UNCOV
696
    return {};
×
UNCOV
697
  }
×
UNCOV
698
}
×
699

700
//==============================================================================
701
// Generic functions for x-, y-, and z-, cones
702
//==============================================================================
703

704
// The first template parameter indicates which axis the cone is aligned to.
705
// The other two parameters indicate the other two axes.  offset1, offset2,
32✔
706
// and offset3 should correspond with i1, i2, and i3, respectively.
32✔
707
template<int i1, int i2, int i3>
708
double axis_aligned_cone_evaluate(
32✔
709
  Position r, double offset1, double offset2, double offset3, double radius_sq)
32✔
710
{
711
  const double r1 = r.get<i1>() - offset1;
1,360,483✔
712
  const double r2 = r.get<i2>() - offset2;
713
  const double r3 = r.get<i3>() - offset3;
1,360,483✔
714
  return r2 * r2 + r3 * r3 - radius_sq * r1 * r1;
715
}
716

1,083,236✔
717
// The first template parameter indicates which axis the cone is aligned to.
718
// The other two parameters indicate the other two axes.  offset1, offset2,
719
// and offset3 should correspond with i1, i2, and i3, respectively.
1,083,236✔
720
template<int i1, int i2, int i3>
1,083,236✔
721
double axis_aligned_cone_distance(Position r, Direction u, bool coincident,
722
  double offset1, double offset2, double offset3, double radius_sq)
UNCOV
723
{
×
724
  const double r1 = r.get<i1>() - offset1;
725
  const double r2 = r.get<i2>() - offset2;
×
726
  const double r3 = r.get<i3>() - offset3;
727
  const double a = u.get<i2>() * u.get<i2>() + u.get<i3>() * u.get<i3>() -
728
                   radius_sq * u.get<i1>() * u.get<i1>();
22✔
729
  const double k =
730
    r2 * u.get<i2>() + r3 * u.get<i3>() - radius_sq * r1 * u.get<i1>();
22✔
731
  const double c = r2 * r2 + r3 * r3 - radius_sq * r1 * r1;
22✔
732
  double quad = k * k - a * c;
22✔
733

22✔
734
  double d;
735

×
736
  if (quad < 0.0) {
UNCOV
737
    // No intersection with cone.
×
738
    return INFTY;
×
UNCOV
739

×
740
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
741
    // Particle is on the cone, thus one distance is positive/negative
×
742
    // and the other is zero. The sign of k determines if we are facing in or
743
    // out.
744
    if (k >= 0.0) {
745
      d = (-k - sqrt(quad)) / a;
746
    } else {
747
      d = (-k + sqrt(quad)) / a;
UNCOV
748
    }
×
749

×
750
  } else {
UNCOV
751
    // Calculate both solutions to the quadratic.
×
752
    quad = sqrt(quad);
753
    d = (-k - quad) / a;
UNCOV
754
    const double b = (-k + quad) / a;
×
755

UNCOV
756
    // Determine the smallest positive solution.
×
757
    if (d < 0.0) {
758
      if (b > 0.0)
UNCOV
759
        d = b;
×
760
    } else {
761
      if (b > 0.0) {
762
        if (b < d)
×
UNCOV
763
          d = b;
×
764
      }
765
    }
UNCOV
766
  }
×
767

768
  // If the distance was negative, set boundary distance to infinity.
×
769
  if (d <= 0.0)
770
    return INFTY;
771
  return d;
×
772
}
UNCOV
773

×
UNCOV
774
// The first template parameter indicates which axis the cone is aligned to.
×
UNCOV
775
// The other two parameters indicate the other two axes.  offset1, offset2,
×
776
// and offset3 should correspond with i1, i2, and i3, respectively.
777
template<int i1, int i2, int i3>
UNCOV
778
Direction axis_aligned_cone_normal(
×
779
  Position r, double offset1, double offset2, double offset3, double radius_sq)
UNCOV
780
{
×
UNCOV
781
  Direction u;
×
UNCOV
782
  u.get<i1>() = -2.0 * radius_sq * (r.get<i1>() - offset1);
×
783
  u.get<i2>() = 2.0 * (r.get<i2>() - offset2);
UNCOV
784
  u.get<i3>() = 2.0 * (r.get<i3>() - offset3);
×
785
  return u;
786
}
787

788
//==============================================================================
789
// SurfaceXCone implementation
790
//==============================================================================
791

792
SurfaceXCone::SurfaceXCone(pugi::xml_node surf_node) : Surface(surf_node)
4,549✔
793
{
4,549✔
794
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
795
}
4,549✔
796

4,549✔
797
double SurfaceXCone::evaluate(Position r) const
798
{
1,415,880,805✔
799
  return axis_aligned_cone_evaluate<0, 1, 2>(r, x0_, y0_, z0_, radius_sq_);
800
}
1,415,880,805✔
801

802
double SurfaceXCone::distance(Position r, Direction u, bool coincident) const
803
{
1,644,890,109✔
804
  return axis_aligned_cone_distance<0, 1, 2>(
805
    r, u, coincident, x0_, y0_, z0_, radius_sq_);
806
}
1,644,890,109✔
807

1,644,890,109✔
808
Direction SurfaceXCone::normal(Position r) const
809
{
810
  return axis_aligned_cone_normal<0, 1, 2>(r, x0_, y0_, z0_, radius_sq_);
1,387,667✔
811
}
812

1,387,667✔
813
void SurfaceXCone::to_hdf5_inner(hid_t group_id) const
814
{
815
  write_string(group_id, "type", "x-cone", false);
3,301✔
816
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
817
  write_dataset(group_id, "coefficients", coeffs);
3,301✔
818
}
3,301✔
819

3,301✔
820
//==============================================================================
3,301✔
821
// SurfaceYCone implementation
822
//==============================================================================
44✔
823

824
SurfaceYCone::SurfaceYCone(pugi::xml_node surf_node) : Surface(surf_node)
44✔
825
{
22✔
826
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
22✔
827
}
828

22✔
829
double SurfaceYCone::evaluate(Position r) const
830
{
831
  return axis_aligned_cone_evaluate<1, 0, 2>(r, y0_, x0_, z0_, radius_sq_);
832
}
833

834
double SurfaceYCone::distance(Position r, Direction u, bool coincident) const
835
{
836
  return axis_aligned_cone_distance<1, 0, 2>(
7,754✔
837
    r, u, coincident, y0_, x0_, z0_, radius_sq_);
838
}
7,754✔
839

7,754✔
840
Direction SurfaceYCone::normal(Position r) const
841
{
543,763,914✔
842
  return axis_aligned_cone_normal<1, 0, 2>(r, y0_, x0_, z0_, radius_sq_);
843
}
543,763,914✔
844

543,763,914✔
845
void SurfaceYCone::to_hdf5_inner(hid_t group_id) const
543,763,914✔
846
{
543,763,914✔
847
  write_string(group_id, "type", "y-cone", false);
848
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
849
  write_dataset(group_id, "coefficients", coeffs);
603,478,870✔
850
}
851

603,478,870✔
852
//==============================================================================
603,478,870✔
853
// SurfaceZCone implementation
603,478,870✔
854
//==============================================================================
603,478,870✔
855

603,478,870✔
856
SurfaceZCone::SurfaceZCone(pugi::xml_node surf_node) : Surface(surf_node)
603,478,870✔
857
{
858
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
603,478,870✔
859
}
860

116,891,903✔
861
double SurfaceZCone::evaluate(Position r) const
862
{
486,586,967✔
863
  return axis_aligned_cone_evaluate<2, 0, 1>(r, z0_, x0_, y0_, radius_sq_);
864
}
865

69,432,759✔
866
double SurfaceZCone::distance(Position r, Direction u, bool coincident) const
33,670,232✔
867
{
868
  return axis_aligned_cone_distance<2, 0, 1>(
35,762,527✔
869
    r, u, coincident, z0_, x0_, y0_, radius_sq_);
870
}
871

417,154,208✔
872
Direction SurfaceZCone::normal(Position r) const
873
{
874
  return axis_aligned_cone_normal<2, 0, 1>(r, z0_, x0_, y0_, radius_sq_);
875
}
373,361,231✔
876

877
void SurfaceZCone::to_hdf5_inner(hid_t group_id) const
878
{
879
  write_string(group_id, "type", "z-cone", false);
880
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
881
  write_dataset(group_id, "coefficients", coeffs);
43,792,977✔
882
}
43,792,977✔
883

9,393,581✔
884
//==============================================================================
34,399,396✔
885
// SurfaceQuadric implementation
886
//==============================================================================
887

888
SurfaceQuadric::SurfaceQuadric(pugi::xml_node surf_node) : Surface(surf_node)
7,892,918✔
889
{
890
  read_coeffs(
7,892,918✔
891
    surf_node, id_, {&A_, &B_, &C_, &D_, &E_, &F_, &G_, &H_, &J_, &K_});
892
}
893

5,988✔
894
double SurfaceQuadric::evaluate(Position r) const
895
{
5,988✔
896
  const double x = r.x;
5,988✔
897
  const double y = r.y;
5,988✔
898
  const double z = r.z;
5,988✔
899
  return x * (A_ * x + D_ * y + G_) + y * (B_ * y + E_ * z + H_) +
UNCOV
900
         z * (C_ * z + F_ * x + J_) + K_;
×
901
}
UNCOV
902

×
UNCOV
903
double SurfaceQuadric::distance(
×
UNCOV
904
  Position r, Direction ang, bool coincident) const
×
905
{
UNCOV
906
  const double& x = r.x;
×
907
  const double& y = r.y;
908
  const double& z = r.z;
909
  const double& u = ang.x;
910
  const double& v = ang.y;
911
  const double& w = ang.z;
912

913
  const double a =
914
    A_ * u * u + B_ * v * v + C_ * w * w + D_ * u * v + E_ * v * w + F_ * u * w;
915
  const double k = A_ * u * x + B_ * v * y + C_ * w * z +
916
                   0.5 * (D_ * (u * y + v * x) + E_ * (v * z + w * y) +
917
                           F_ * (w * x + u * z) + G_ * u + H_ * v + J_ * w);
918
  const double c = A_ * x * x + B_ * y * y + C_ * z * z + D_ * x * y +
322,586✔
919
                   E_ * y * z + F_ * x * z + G_ * x + H_ * y + J_ * z + K_;
920
  double quad = k * k - a * c;
921

322,586✔
922
  double d;
322,586✔
923

322,586✔
924
  if (quad < 0.0) {
322,586✔
925
    // No intersection with surface.
926
    return INFTY;
322,586✔
927

928
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
929
    // Particle is on the surface, thus one distance is positive/negative and
322,586✔
930
    // the other is zero. The sign of k determines which distance is zero and
322,586✔
931
    // which is not. Additionally, if a is zero, it means the particle is on
322,586✔
932
    // a plane-like surface.
322,586✔
933
    if (a == 0.0) {
934
      d = INFTY; // see the below explanation
×
935
    } else if (k >= 0.0) {
936
      d = (-k - sqrt(quad)) / a;
UNCOV
937
    } else {
×
UNCOV
938
      d = (-k + sqrt(quad)) / a;
×
UNCOV
939
    }
×
UNCOV
940

×
941
  } else if (a == 0.0) {
UNCOV
942
    // Given the orientation of the particle, the quadric looks like a plane in
×
943
    // this case, and thus we have only one solution despite potentially having
944
    // quad > 0.0. While the term under the square root may be real, in one
UNCOV
945
    // case of the +/- of the quadratic formula, 0/0 results, and in another, a
×
UNCOV
946
    // finite value over 0 results. Applying L'Hopital's to the 0/0 case gives
×
UNCOV
947
    // the below. Alternatively this can be found by simply putting a=0 in the
×
UNCOV
948
    // equation ax^2 + bx + c = 0.
×
949
    d = -0.5 * c / k;
950
  } else {
951
    // Calculate both solutions to the quadratic.
952
    quad = sqrt(quad);
953
    d = (-k - quad) / a;
954
    double b = (-k + quad) / a;
955

963,358✔
956
    // Determine the smallest positive solution.
957
    if (d < 0.0) {
958
      if (b > 0.0)
963,358✔
959
        d = b;
963,358✔
960
    } else {
963,358✔
961
      if (b > 0.0) {
963,358✔
962
        if (b < d)
963,358✔
963
          d = b;
963,358✔
964
      }
963,358✔
965
    }
963,358✔
966
  }
963,358✔
967

968
  // If the distance was negative, set boundary distance to infinity.
969
  if (d <= 0.0)
970
    return INFTY;
963,358✔
971
  return d;
UNCOV
972
}
×
973

974
Direction SurfaceQuadric::normal(Position r) const
963,358✔
975
{
976
  const double& x = r.x;
977
  const double& y = r.y;
978
  const double& z = r.z;
60,566✔
UNCOV
979
  return {2.0 * A_ * x + D_ * y + F_ * z + G_,
×
980
    2.0 * B_ * y + D_ * x + E_ * z + H_, 2.0 * C_ * z + E_ * y + F_ * x + J_};
981
}
60,566✔
982

983
void SurfaceQuadric::to_hdf5_inner(hid_t group_id) const
984
{
985
  write_string(group_id, "type", "quadric", false);
986
  array<double, 10> coeffs {{A_, B_, C_, D_, E_, F_, G_, H_, J_, K_}};
902,792✔
987
  write_dataset(group_id, "coefficients", coeffs);
902,792✔
988
}
902,792✔
989

990
//==============================================================================
991
// Torus helper functions
902,792✔
992
//==============================================================================
766,524✔
993

650,507✔
994
double torus_distance(double x1, double x2, double x3, double u1, double u2,
995
  double u3, double A, double B, double C, bool coincident)
136,268✔
996
{
136,268✔
997
  // Coefficients for equation: (c2 t^2 + c1 t + c0)^2 = c2' t^2 + c1' t + c0'
136,268✔
998
  double D = (C * C) / (B * B);
999
  double c2 = u1 * u1 + u2 * u2 + D * u3 * u3;
1000
  double c1 = 2 * (u1 * x1 + u2 * x2 + D * u3 * x3);
1001
  double c0 = x1 * x1 + x2 * x2 + D * x3 * x3 + A * A - C * C;
1002
  double four_A2 = 4 * A * A;
1003
  double c2p = four_A2 * (u1 * u1 + u2 * u2);
963,358✔
1004
  double c1p = 2 * four_A2 * (u1 * x1 + u2 * x2);
137,467✔
1005
  double c0p = four_A2 * (x1 * x1 + x2 * x2);
825,891✔
1006

1007
  // Coefficient for equation: a t^4 + b t^3 + c t^2 + d t + e = 0. If the point
963,358✔
1008
  // is coincident, the 'e' coefficient should be zero. Explicitly setting it to
1009
  // zero helps avoid numerical issues below with root finding.
1010
  double coeff[5];
963,358✔
1011
  coeff[0] = coincident ? 0.0 : c0 * c0 - c0p;
963,358✔
1012
  coeff[1] = 2 * c0 * c1 - c1p;
963,358✔
1013
  coeff[2] = c1 * c1 + 2 * c0 * c2 - c2p;
963,358✔
1014
  coeff[3] = 2 * c1 * c2;
963,358✔
1015
  coeff[4] = c2 * c2;
963,358✔
1016

963,358✔
1017
  std::complex<double> roots[4];
963,358✔
1018
  oqs::quartic_solver(coeff, roots);
963,358✔
1019

1020
  // Find smallest positive, real root. In the case where the particle is
1021
  // coincident with the surface, we are sure to have one root very close to
1022
  // zero but possibly small and positive. A tolerance is set to discard that
963,358✔
1023
  // zero.
UNCOV
1024
  double distance = INFTY;
×
1025
  double cutoff = coincident ? TORUS_TOL : 0.0;
1026
  for (int i = 0; i < 4; ++i) {
963,358✔
1027
    if (roots[i].imag() == 0) {
1028
      double root = roots[i].real();
1029
      if (root > cutoff && root < distance) {
1030
        // Avoid roots corresponding to internal surfaces
60,566✔
UNCOV
1031
        double s1 = x1 + u1 * root;
×
1032
        double s2 = x2 + u2 * root;
1033
        double s3 = x3 + u3 * root;
60,566✔
1034
        double check = D * s3 * s3 + s1 * s1 + s2 * s2 + A * A - C * C;
1035
        if (check >= 0) {
1036
          distance = root;
1037
        }
1038
      }
902,792✔
1039
    }
902,792✔
1040
  }
902,792✔
1041
  return distance;
1042
}
1043

902,792✔
1044
//==============================================================================
766,524✔
1045
// SurfaceXTorus implementation
650,507✔
1046
//==============================================================================
1047

136,268✔
1048
SurfaceXTorus::SurfaceXTorus(pugi::xml_node surf_node) : Surface(surf_node)
136,268✔
1049
{
136,268✔
1050
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1051
}
1052

1053
void SurfaceXTorus::to_hdf5_inner(hid_t group_id) const
1054
{
1055
  write_string(group_id, "type", "x-torus", false);
963,358✔
1056
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
137,467✔
1057
  write_dataset(group_id, "coefficients", coeffs);
825,891✔
1058
}
UNCOV
1059

×
1060
double SurfaceXTorus::evaluate(Position r) const
1061
{
UNCOV
1062
  double x = r.x - x0_;
×
1063
  double y = r.y - y0_;
×
UNCOV
1064
  double z = r.z - z0_;
×
1065
  return (x * x) / (B_ * B_) +
×
UNCOV
1066
         std::pow(std::sqrt(y * y + z * z) - A_, 2) / (C_ * C_) - 1.;
×
UNCOV
1067
}
×
UNCOV
1068

×
1069
double SurfaceXTorus::distance(Position r, Direction u, bool coincident) const
×
1070
{
×
1071
  double x = r.x - x0_;
1072
  double y = r.y - y0_;
1073
  double z = r.z - z0_;
UNCOV
1074
  return torus_distance(y, z, x, u.y, u.z, u.x, A_, B_, C_, coincident);
×
1075
}
UNCOV
1076

×
1077
Direction SurfaceXTorus::normal(Position r) const
1078
{
×
1079
  // reduce the expansion of the full form for torus
1080
  double x = r.x - x0_;
1081
  double y = r.y - y0_;
1082
  double z = r.z - z0_;
×
1083

×
1084
  // f(x,y,z) = x^2/B^2 + (sqrt(y^2 + z^2) - A)^2/C^2 - 1
UNCOV
1085
  // ∂f/∂x = 2x/B^2
×
1086
  // ∂f/∂y = 2y(g - A)/(g*C^2) where g = sqrt(y^2 + z^2)
1087
  // ∂f/∂z = 2z(g - A)/(g*C^2)
1088
  // Multiplying by g*C^2*B^2 / 2 gives:
1089
  double g = std::sqrt(y * y + z * z);
UNCOV
1090
  double nx = C_ * C_ * g * x;
×
UNCOV
1091
  double ny = y * (g - A_) * B_ * B_;
×
UNCOV
1092
  double nz = z * (g - A_) * B_ * B_;
×
1093
  Direction n(nx, ny, nz);
1094
  return n / n.norm();
1095
}
×
1096

×
UNCOV
1097
//==============================================================================
×
1098
// SurfaceYTorus implementation
UNCOV
1099
//==============================================================================
×
UNCOV
1100

×
1101
SurfaceYTorus::SurfaceYTorus(pugi::xml_node surf_node) : Surface(surf_node)
×
1102
{
1103
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1104
}
1105

1106
void SurfaceYTorus::to_hdf5_inner(hid_t group_id) const
1107
{
×
1108
  write_string(group_id, "type", "y-torus", false);
×
1109
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
×
1110
  write_dataset(group_id, "coefficients", coeffs);
UNCOV
1111
}
×
1112

1113
double SurfaceYTorus::evaluate(Position r) const
UNCOV
1114
{
×
1115
  double x = r.x - x0_;
×
UNCOV
1116
  double y = r.y - y0_;
×
1117
  double z = r.z - z0_;
×
UNCOV
1118
  return (y * y) / (B_ * B_) +
×
UNCOV
1119
         std::pow(std::sqrt(x * x + z * z) - A_, 2) / (C_ * C_) - 1.;
×
UNCOV
1120
}
×
1121

×
1122
double SurfaceYTorus::distance(Position r, Direction u, bool coincident) const
×
1123
{
1124
  double x = r.x - x0_;
1125
  double y = r.y - y0_;
UNCOV
1126
  double z = r.z - z0_;
×
1127
  return torus_distance(x, z, y, u.x, u.z, u.y, A_, B_, C_, coincident);
UNCOV
1128
}
×
1129

1130
Direction SurfaceYTorus::normal(Position r) const
×
1131
{
1132
  // reduce the expansion of the full form for torus
1133
  double x = r.x - x0_;
1134
  double y = r.y - y0_;
×
1135
  double z = r.z - z0_;
×
1136

UNCOV
1137
  // f(x,y,z) = y^2/B^2 + (sqrt(x^2 + z^2) - A)^2/C^2 - 1
×
1138
  // ∂f/∂x = 2x(g - A)/(g*C^2) where g = sqrt(x^2 + z^2)
1139
  // ∂f/∂y = 2y/B^2
1140
  // ∂f/∂z = 2z(g - A)/(g*C^2)
1141
  // Multiplying by g*C^2*B^2 / 2 gives:
UNCOV
1142
  double g = std::sqrt(x * x + z * z);
×
UNCOV
1143
  double nx = x * (g - A_) * B_ * B_;
×
UNCOV
1144
  double ny = C_ * C_ * g * y;
×
1145
  double nz = z * (g - A_) * B_ * B_;
1146
  Direction n(nx, ny, nz);
1147
  return n / n.norm();
×
1148
}
×
UNCOV
1149

×
1150
//==============================================================================
UNCOV
1151
// SurfaceZTorus implementation
×
UNCOV
1152
//==============================================================================
×
UNCOV
1153

×
1154
SurfaceZTorus::SurfaceZTorus(pugi::xml_node surf_node) : Surface(surf_node)
1155
{
1156
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1157
}
1158

UNCOV
1159
void SurfaceZTorus::to_hdf5_inner(hid_t group_id) const
×
UNCOV
1160
{
×
UNCOV
1161
  write_string(group_id, "type", "z-torus", false);
×
1162
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
1163
  write_dataset(group_id, "coefficients", coeffs);
1164
}
1165

1166
double SurfaceZTorus::evaluate(Position r) const
1167
{
1168
  double x = r.x - x0_;
60,566✔
1169
  double y = r.y - y0_;
1170
  double z = r.z - z0_;
1171
  return (z * z) / (B_ * B_) +
60,566✔
1172
         std::pow(std::sqrt(x * x + y * y) - A_, 2) / (C_ * C_) - 1.;
60,566✔
1173
}
60,566✔
1174

60,566✔
1175
double SurfaceZTorus::distance(Position r, Direction u, bool coincident) const
60,566✔
1176
{
1177
  double x = r.x - x0_;
60,566✔
1178
  double y = r.y - y0_;
1179
  double z = r.z - z0_;
1180
  return torus_distance(x, y, z, u.x, u.y, u.z, A_, B_, C_, coincident);
60,566✔
1181
}
60,566✔
1182

60,566✔
1183
Direction SurfaceZTorus::normal(Position r) const
60,566✔
1184
{
60,566✔
1185
  // reduce the expansion of the full form for torus
1186
  double x = r.x - x0_;
×
1187
  double y = r.y - y0_;
1188
  double z = r.z - z0_;
1189

×
UNCOV
1190
  // f(x,y,z) = z^2/B^2 + (sqrt(x^2 + y^2) - A)^2/C^2 - 1
×
UNCOV
1191
  // ∂f/∂x = 2x(g - A)/(g*C^2) where g = sqrt(x^2 + y^2)
×
UNCOV
1192
  // ∂f/∂y = 2y(g - A)/(g*C^2)
×
UNCOV
1193
  // ∂f/∂z = 2z/B^2
×
1194
  // Multiplying by g*C^2*B^2 / 2 gives:
UNCOV
1195
  double g = std::sqrt(x * x + y * y);
×
1196
  double nx = x * (g - A_) * B_ * B_;
1197
  double ny = y * (g - A_) * B_ * B_;
1198
  double nz = C_ * C_ * g * z;
×
UNCOV
1199
  Position n(nx, ny, nz);
×
UNCOV
1200
  return n / n.norm();
×
1201
}
×
UNCOV
1202

×
1203
//==============================================================================
1204

1205
void read_surfaces(pugi::xml_node node)
1206
{
1207
  // Count the number of surfaces
1208
  int n_surfaces = 0;
NEW
1209
  for (pugi::xml_node surf_node : node.children("surface")) {
×
1210
    n_surfaces++;
UNCOV
1211
  }
×
1212

1213
  // Loop over XML surface elements and populate the array.  Keep track of
1214
  // periodic surfaces and their albedos.
×
1215
  model::surfaces.reserve(n_surfaces);
UNCOV
1216
  std::set<std::pair<int, int>> periodic_pairs;
×
1217
  std::unordered_map<int, double> albedo_map;
1218
  {
1219
    pugi::xml_node surf_node;
×
1220
    int i_surf;
1221
    for (surf_node = node.child("surface"), i_surf = 0; surf_node;
×
UNCOV
1222
      surf_node = surf_node.next_sibling("surface"), i_surf++) {
×
1223
      std::string surf_type = get_node_value(surf_node, "type", true, true);
1224

UNCOV
1225
      // Allocate and initialize the new surface
×
1226

UNCOV
1227
      if (surf_type == "x-plane") {
×
1228
        model::surfaces.push_back(make_unique<SurfaceXPlane>(surf_node));
1229

1230
      } else if (surf_type == "y-plane") {
×
1231
        model::surfaces.push_back(make_unique<SurfaceYPlane>(surf_node));
UNCOV
1232

×
1233
      } else if (surf_type == "z-plane") {
×
UNCOV
1234
        model::surfaces.push_back(make_unique<SurfaceZPlane>(surf_node));
×
1235

1236
      } else if (surf_type == "plane") {
1237
        model::surfaces.push_back(make_unique<SurfacePlane>(surf_node));
1238

1239
      } else if (surf_type == "x-cylinder") {
1240
        model::surfaces.push_back(make_unique<SurfaceXCylinder>(surf_node));
1241

×
1242
      } else if (surf_type == "y-cylinder") {
UNCOV
1243
        model::surfaces.push_back(make_unique<SurfaceYCylinder>(surf_node));
×
1244

1245
      } else if (surf_type == "z-cylinder") {
1246
        model::surfaces.push_back(make_unique<SurfaceZCylinder>(surf_node));
×
1247

UNCOV
1248
      } else if (surf_type == "sphere") {
×
1249
        model::surfaces.push_back(make_unique<SurfaceSphere>(surf_node));
1250

1251
      } else if (surf_type == "x-cone") {
×
1252
        model::surfaces.push_back(make_unique<SurfaceXCone>(surf_node));
1253

×
UNCOV
1254
      } else if (surf_type == "y-cone") {
×
1255
        model::surfaces.push_back(make_unique<SurfaceYCone>(surf_node));
1256

UNCOV
1257
      } else if (surf_type == "z-cone") {
×
1258
        model::surfaces.push_back(make_unique<SurfaceZCone>(surf_node));
UNCOV
1259

×
1260
      } else if (surf_type == "quadric") {
1261
        model::surfaces.push_back(make_unique<SurfaceQuadric>(surf_node));
UNCOV
1262

×
1263
      } else if (surf_type == "x-torus") {
UNCOV
1264
        model::surfaces.push_back(std::make_unique<SurfaceXTorus>(surf_node));
×
UNCOV
1265

×
UNCOV
1266
      } else if (surf_type == "y-torus") {
×
1267
        model::surfaces.push_back(std::make_unique<SurfaceYTorus>(surf_node));
1268

1269
      } else if (surf_type == "z-torus") {
1270
        model::surfaces.push_back(std::make_unique<SurfaceZTorus>(surf_node));
1271

1272
      } else {
1273
        fatal_error(fmt::format("Invalid surface type, \"{}\"", surf_type));
16✔
1274
      }
1275

16✔
1276
      // Check for a periodic surface
16✔
1277
      if (check_for_node(surf_node, "boundary")) {
1278
        std::string surf_bc = get_node_value(surf_node, "boundary", true, true);
322,586✔
1279
        if (surf_bc == "periodic") {
1280
          // Check for surface albedo. Skip sanity check as it is already done
322,586✔
1281
          // in the Surface class's constructor.
1282
          if (check_for_node(surf_node, "albedo")) {
1283
            albedo_map[model::surfaces.back()->id_] =
963,358✔
1284
              std::stod(get_node_value(surf_node, "albedo"));
1285
          }
963,358✔
1286
          if (check_for_node(surf_node, "periodic_surface_id")) {
963,358✔
1287
            int i_periodic =
1288
              std::stoi(get_node_value(surf_node, "periodic_surface_id"));
1289
            int lo_id = std::min(model::surfaces.back()->id_, i_periodic);
60,566✔
1290
            int hi_id = std::max(model::surfaces.back()->id_, i_periodic);
1291
            periodic_pairs.insert({lo_id, hi_id});
60,566✔
1292
          } else {
1293
            periodic_pairs.insert({model::surfaces.back()->id_, -1});
1294
          }
11✔
1295
        }
1296
      }
11✔
1297
    }
11✔
1298
  }
11✔
1299

11✔
1300
  // Fill the surface map
1301
  for (int i_surf = 0; i_surf < model::surfaces.size(); i_surf++) {
1302
    int id = model::surfaces[i_surf]->id_;
1303
    auto in_map = model::surface_map.find(id);
1304
    if (in_map == model::surface_map.end()) {
1305
      model::surface_map[id] = i_surf;
16✔
1306
    } else {
UNCOV
1307
      fatal_error(
×
1308
        fmt::format("Two or more surfaces use the same unique ID: {}", id));
16✔
1309
    }
16✔
1310
  }
1311

177,572✔
1312
  // Resolve unpaired periodic surfaces.  A lambda function is used with
1313
  // std::find_if to identify the unpaired surfaces.
177,572✔
1314
  auto is_unresolved_pair = [](const std::pair<int, int> p) {
177,572✔
1315
    return p.second == -1;
177,572✔
1316
  };
177,572✔
1317
  auto first_unresolved = std::find_if(
177,572✔
1318
    periodic_pairs.begin(), periodic_pairs.end(), is_unresolved_pair);
1319
  if (first_unresolved != periodic_pairs.end()) {
1320
    // Found one unpaired surface; search for a second one
312,510✔
1321
    auto next_elem = first_unresolved;
1322
    next_elem++;
1323
    auto second_unresolved =
312,510✔
1324
      std::find_if(next_elem, periodic_pairs.end(), is_unresolved_pair);
312,510✔
1325
    if (second_unresolved == periodic_pairs.end()) {
312,510✔
1326
      fatal_error("Found only one periodic surface without a specified partner."
312,510✔
1327
                  " Please specify the partner for each periodic surface.");
312,510✔
1328
    }
312,510✔
1329

1330
    // Make sure there isn't a third unpaired surface
312,510✔
1331
    next_elem = second_unresolved;
312,510✔
1332
    next_elem++;
312,510✔
1333
    auto third_unresolved =
312,510✔
1334
      std::find_if(next_elem, periodic_pairs.end(), is_unresolved_pair);
312,510✔
1335
    if (third_unresolved != periodic_pairs.end()) {
312,510✔
1336
      fatal_error(
312,510✔
1337
        "Found at least three periodic surfaces without a specified "
312,510✔
1338
        "partner. Please specify the partner for each periodic surface.");
1339
    }
1340

1341
    // Add the completed pair and remove the old, unpaired entries
312,510✔
1342
    int lo_id = std::min(first_unresolved->first, second_unresolved->first);
UNCOV
1343
    int hi_id = std::max(first_unresolved->first, second_unresolved->first);
×
1344
    periodic_pairs.insert({lo_id, hi_id});
1345
    periodic_pairs.erase(first_unresolved);
312,510✔
1346
    periodic_pairs.erase(second_unresolved);
1347
  }
1348

1349
  // Assign the periodic boundary conditions with albedos
1350
  for (auto periodic_pair : periodic_pairs) {
34,540✔
UNCOV
1351
    int i_surf = model::surface_map[periodic_pair.first];
×
1352
    int j_surf = model::surface_map[periodic_pair.second];
34,540✔
1353
    Surface& surf1 {*model::surfaces[i_surf]};
×
1354
    Surface& surf2 {*model::surfaces[j_surf]};
1355

34,540✔
1356
    // Compute the dot product of the surface normals
1357
    Direction norm1 = surf1.normal({0, 0, 0});
1358
    Direction norm2 = surf2.normal({0, 0, 0});
277,970✔
1359
    norm1 /= norm1.norm();
1360
    norm2 /= norm2.norm();
1361
    double dot_prod = norm1.dot(norm2);
1362

1363
    // If the dot product is 1 (to within floating point precision) then the
1364
    // planes are parallel which indicates a translational periodic boundary
1365
    // condition.  Otherwise, it is a rotational periodic BC.
1366
    if (std::abs(1.0 - dot_prod) < FP_PRECISION) {
×
1367
      surf1.bc_ = make_unique<TranslationalPeriodicBC>(i_surf, j_surf);
1368
      surf2.bc_ = make_unique<TranslationalPeriodicBC>(i_surf, j_surf);
1369
    } else {
277,970✔
1370
      surf1.bc_ = make_unique<RotationalPeriodicBC>(i_surf, j_surf);
277,970✔
1371
      surf2.bc_ = make_unique<RotationalPeriodicBC>(i_surf, j_surf);
277,970✔
1372
    }
1373

1374
    // If albedo data is present in albedo map, set the boundary albedo.
277,970✔
1375
    if (albedo_map.count(surf1.id_)) {
277,970✔
1376
      surf1.bc_->set_albedo(albedo_map[surf1.id_]);
277,970✔
1377
    }
UNCOV
1378
    if (albedo_map.count(surf2.id_)) {
×
UNCOV
1379
      surf2.bc_->set_albedo(albedo_map[surf2.id_]);
×
UNCOV
1380
    }
×
1381
  }
1382
}
1383

1384
void free_memory_surfaces()
1385
{
1386
  model::surfaces.clear();
312,510✔
UNCOV
1387
  model::surface_map.clear();
×
1388
}
312,510✔
1389

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