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

openmc-dev / openmc / 14981355920

12 May 2025 07:56PM UTC coverage: 85.149% (-0.05%) from 85.2%
14981355920

Pull #3402

github

web-flow
Merge b112c02e3 into f615441f0
Pull Request #3402: Add transformation boundary condition

66 of 117 new or added lines in 5 files covered. (56.41%)

234 existing lines in 3 files now uncovered.

52295 of 61416 relevant lines covered (85.15%)

36868346.1 hits per line

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

71.4
/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(
35,920✔
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");
35,920✔
42
  if (coeffs_file.size() != coeffs.size()) {
35,920✔
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;
35,920✔
50
  for (auto c : coeffs) {
109,713✔
51
    *c = coeffs_file[i++];
73,793✔
52
  }
53
}
35,920✔
54

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

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

61
Surface::Surface(pugi::xml_node surf_node)
35,920✔
62
{
63
  if (check_for_node(surf_node, "id")) {
35,920✔
64
    id_ = std::stoi(get_node_value(surf_node, "id"));
35,920✔
65
    if (contains(settings::source_write_surf_id, id_) ||
71,063✔
66
        settings::source_write_surf_id.empty()) {
35,143✔
67
      surf_source_ = true;
34,911✔
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")) {
35,920✔
74
    name_ = get_node_value(surf_node, "name", false);
8,358✔
75
  }
76

77
  if (check_for_node(surf_node, "boundary")) {
35,920✔
78
    std::string surf_bc = get_node_value(surf_node, "boundary", true, true);
21,102✔
79

80
    if (surf_bc == "transmission" || surf_bc == "transmit" || surf_bc.empty()) {
21,102✔
81
      // Leave the bc_ a nullptr
82
    } else if (surf_bc == "vacuum") {
21,102✔
83
      bc_ = make_unique<VacuumBC>();
10,255✔
84
    } else if (surf_bc == "reflective" || surf_bc == "reflect" ||
11,271✔
85
               surf_bc == "reflecting") {
424✔
86
      bc_ = make_unique<ReflectiveBC>();
10,423✔
87
    } else if (surf_bc == "white") {
424✔
88
      bc_ = make_unique<WhiteBC>();
96✔
89
    } else if (surf_bc == "periodic") {
328✔
90
      // Periodic BCs are handled separately
91
    } else if (surf_bc == "transformation" || surf_bc == "transform") {
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 = get_node_array<double>(surf_node, "direction_transformation");
96✔
98
        if (dir_trans.size() != vector_size_exp) {
96✔
NEW
99
          fatal_error(
×
NEW
100
            fmt::format(
×
101
              "Transformation on surface {} expects direction matrix size {} but was given size {}",
NEW
102
              id_, vector_size_exp, dir_trans.size()));
×
103
        }
104
      } else {
NEW
105
        dir_trans = {1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0};
×
106
      }
107
      if (check_for_node(surf_node, "position_transformation")) {
96✔
NEW
108
        pos_trans = get_node_array<double>(surf_node, "position_transformation");
×
NEW
109
        if (pos_trans.size() != vector_size_exp) {
×
NEW
110
          fatal_error(
×
NEW
111
            fmt::format(
×
112
              "Transformation on surface {} expects position matrix size {} but was given size {}",
NEW
113
              id_, vector_size_exp, pos_trans.size()));
×
114
        }
115
      } else {
116
        pos_trans = {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✔
117
      }
118
      bc_ = make_unique<TransformationBC>(dir_trans, pos_trans);
96✔
119
    } else {
96✔
NEW
120
      fatal_error(fmt::format("Unknown boundary condition \"{}\" specified "
×
121
                              "on surface {}",
NEW
122
        surf_bc, id_));
×
123
    }
124

125
    if (check_for_node(surf_node, "albedo") && bc_) {
21,102✔
126
      double surf_alb = std::stod(get_node_value(surf_node, "albedo"));
96✔
127

128
      if (surf_alb < 0.0)
96✔
UNCOV
129
        fatal_error(fmt::format("Surface {} has an albedo of {}. "
×
130
                                "Albedo values must be positive.",
UNCOV
131
          id_, surf_alb));
×
132

133
      if (surf_alb > 1.0)
96✔
UNCOV
134
        warning(fmt::format("Surface {} has an albedo of {}. "
×
135
                            "Albedos greater than 1 may cause "
136
                            "unphysical behaviour.",
UNCOV
137
          id_, surf_alb));
×
138

139
      bc_->set_albedo(surf_alb);
96✔
140
    }
141
  }
21,102✔
142
}
35,920✔
143

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

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

160
Direction Surface::reflect(Position r, Direction u, GeometryState* p) const
597,980,695✔
161
{
162
  // Determine projection of direction onto normal and squared magnitude of
163
  // normal.
164
  Direction n = normal(r);
597,980,695✔
165

166
  // Reflect direction according to normal.
167
  return u.reflect(n);
1,195,961,390✔
168
}
169

170
Direction Surface::diffuse_reflect(
1,016,983✔
171
  Position r, Direction u, uint64_t* seed) const
172
{
173
  // Diffuse reflect direction according to the normal.
174
  // cosine distribution
175

176
  Direction n = this->normal(r);
1,016,983✔
177
  n /= n.norm();
1,016,983✔
178
  const double projection = n.dot(u);
1,016,983✔
179

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

184
  // sample azimuthal distribution uniformly
185
  u = rotate_angle(n, mu, nullptr, seed);
1,016,983✔
186

187
  // normalize the direction
188
  return u / u.norm();
2,033,966✔
189
}
190

191
void Surface::to_hdf5(hid_t group_id) const
28,414✔
192
{
193
  hid_t surf_group = create_group(group_id, fmt::format("surface {}", id_));
56,828✔
194

195
  if (geom_type() == GeometryType::DAG) {
28,414✔
196
    write_string(surf_group, "geom_type", "dagmc", false);
594✔
197
  } else if (geom_type() == GeometryType::CSG) {
27,820✔
198
    write_string(surf_group, "geom_type", "csg", false);
27,820✔
199

200
    if (bc_) {
27,820✔
201
      write_string(surf_group, "boundary_type", bc_->type(), false);
16,546✔
202
      bc_->to_hdf5(surf_group);
16,546✔
203
    } else {
204
      write_string(surf_group, "boundary_type", "transmission", false);
11,274✔
205
    }
206
  }
207

208
  if (!name_.empty()) {
28,414✔
209
    write_string(surf_group, "name", name_, false);
6,187✔
210
  }
211

212
  to_hdf5_inner(surf_group);
28,414✔
213

214
  close_group(surf_group);
28,414✔
215
}
28,414✔
216

217
//==============================================================================
218
// Generic functions for x-, y-, and z-, planes.
219
//==============================================================================
220

221
// The template parameter indicates the axis normal to the plane.
222
template<int i>
223
double axis_aligned_plane_distance(
2,147,483,647✔
224
  Position r, Direction u, bool coincident, double offset)
225
{
226
  const double f = offset - r[i];
2,147,483,647✔
227
  if (coincident || std::abs(f) < FP_COINCIDENT || u[i] == 0.0)
2,147,483,647✔
228
    return INFTY;
598,548,804✔
229
  const double d = f / u[i];
2,147,483,647✔
230
  if (d < 0.0)
2,147,483,647✔
231
    return INFTY;
2,147,483,647✔
232
  return d;
2,147,483,647✔
233
}
234

2,147,483,647✔
235
//==============================================================================
236
// SurfaceXPlane implementation
237
//==============================================================================
2,147,483,647✔
238

2,147,483,647✔
239
SurfaceXPlane::SurfaceXPlane(pugi::xml_node surf_node) : Surface(surf_node)
53,463,853✔
240
{
2,147,483,647✔
241
  read_coeffs(surf_node, id_, {&x0_});
2,147,483,647✔
242
}
1,125,307,502✔
243

1,200,789,866✔
244
double SurfaceXPlane::evaluate(Position r) const
245
{
2,147,483,647✔
246
  return r.x - x0_;
247
}
248

2,147,483,647✔
249
double SurfaceXPlane::distance(Position r, Direction u, bool coincident) const
2,147,483,647✔
250
{
282,661,373✔
251
  return axis_aligned_plane_distance<0>(r, u, coincident, x0_);
2,147,483,647✔
252
}
2,147,483,647✔
253

2,147,483,647✔
254
Direction SurfaceXPlane::normal(Position r) const
2,147,483,647✔
255
{
256
  return {1., 0., 0.};
2,147,483,647✔
257
}
258

259
void SurfaceXPlane::to_hdf5_inner(hid_t group_id) const
2,147,483,647✔
260
{
2,147,483,647✔
261
  write_string(group_id, "type", "x-plane", false);
262,423,578✔
262
  array<double, 1> coeffs {{x0_}};
2,147,483,647✔
263
  write_dataset(group_id, "coefficients", coeffs);
2,147,483,647✔
264
}
2,147,483,647✔
265

2,147,483,647✔
266
BoundingBox SurfaceXPlane::bounding_box(bool pos_side) const
267
{
268
  if (pos_side) {
269
    return {x0_, INFTY, -INFTY, INFTY, -INFTY, INFTY};
270
  } else {
271
    return {-INFTY, x0_, -INFTY, INFTY, -INFTY, INFTY};
272
  }
8,575✔
273
}
274

8,575✔
275
//==============================================================================
8,575✔
276
// SurfaceYPlane implementation
277
//==============================================================================
1,505,495,619✔
278

279
SurfaceYPlane::SurfaceYPlane(pugi::xml_node surf_node) : Surface(surf_node)
1,505,495,619✔
280
{
281
  read_coeffs(surf_node, id_, {&y0_});
282
}
2,147,483,647✔
283

284
double SurfaceYPlane::evaluate(Position r) const
2,147,483,647✔
285
{
286
  return r.y - y0_;
287
}
262,779,446✔
288

289
double SurfaceYPlane::distance(Position r, Direction u, bool coincident) const
262,779,446✔
290
{
291
  return axis_aligned_plane_distance<1>(r, u, coincident, y0_);
292
}
6,589✔
293

294
Direction SurfaceYPlane::normal(Position r) const
6,589✔
295
{
6,589✔
296
  return {0., 1., 0.};
6,589✔
297
}
6,589✔
298

299
void SurfaceYPlane::to_hdf5_inner(hid_t group_id) const
242✔
300
{
301
  write_string(group_id, "type", "y-plane", false);
242✔
302
  array<double, 1> coeffs {{y0_}};
121✔
303
  write_dataset(group_id, "coefficients", coeffs);
304
}
121✔
305

306
BoundingBox SurfaceYPlane::bounding_box(bool pos_side) const
307
{
308
  if (pos_side) {
309
    return {-INFTY, INFTY, y0_, INFTY, -INFTY, INFTY};
310
  } else {
311
    return {-INFTY, INFTY, -INFTY, y0_, -INFTY, INFTY};
312
  }
7,631✔
313
}
314

7,631✔
315
//==============================================================================
7,631✔
316
// SurfaceZPlane implementation
317
//==============================================================================
1,506,419,826✔
318

319
SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) : Surface(surf_node)
1,506,419,826✔
320
{
321
  read_coeffs(surf_node, id_, {&z0_});
322
}
2,147,483,647✔
323

324
double SurfaceZPlane::evaluate(Position r) const
2,147,483,647✔
325
{
326
  return r.z - z0_;
327
}
283,797,582✔
328

329
double SurfaceZPlane::distance(Position r, Direction u, bool coincident) const
283,797,582✔
330
{
331
  return axis_aligned_plane_distance<2>(r, u, coincident, z0_);
332
}
5,997✔
333

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

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

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

5,757✔
355
//==============================================================================
5,757✔
356
// SurfacePlane implementation
357
//==============================================================================
481,839,114✔
358

359
SurfacePlane::SurfacePlane(pugi::xml_node surf_node) : Surface(surf_node)
481,839,114✔
360
{
361
  read_coeffs(surf_node, id_, {&A_, &B_, &C_, &D_});
362
}
2,147,483,647✔
363

364
double SurfacePlane::evaluate(Position r) const
2,147,483,647✔
365
{
366
  return A_ * r.x + B_ * r.y + C_ * r.z - D_;
367
}
53,797,124✔
368

369
double SurfacePlane::distance(Position r, Direction u, bool coincident) const
53,797,124✔
370
{
371
  const double f = A_ * r.x + B_ * r.y + C_ * r.z - D_;
372
  const double projection = A_ * u.x + B_ * u.y + C_ * u.z;
4,815✔
373
  if (coincident || std::abs(f) < FP_COINCIDENT || projection == 0.0) {
374
    return INFTY;
4,815✔
375
  } else {
4,815✔
376
    const double d = -f / projection;
4,815✔
377
    if (d < 0.0)
4,815✔
378
      return INFTY;
UNCOV
379
    return d;
×
380
  }
UNCOV
381
}
×
UNCOV
382

×
383
Direction SurfacePlane::normal(Position r) const
UNCOV
384
{
×
385
  return {A_, B_, C_};
386
}
387

388
void SurfacePlane::to_hdf5_inner(hid_t group_id) const
389
{
390
  write_string(group_id, "type", "plane", false);
391
  array<double, 4> coeffs {{A_, B_, C_, D_}};
392
  write_dataset(group_id, "coefficients", coeffs);
1,508✔
393
}
394

1,508✔
395
//==============================================================================
1,508✔
396
// Generic functions for x-, y-, and z-, cylinders
397
//==============================================================================
201,594,419✔
398

399
// The template parameters indicate the axes perpendicular to the axis of the
201,594,419✔
400
// cylinder.  offset1 and offset2 should correspond with i1 and i2,
401
// respectively.
402
template<int i1, int i2>
578,660,990✔
403
double axis_aligned_cylinder_evaluate(
404
  Position r, double offset1, double offset2, double radius)
578,660,990✔
405
{
578,660,990✔
406
  const double r1 = r.get<i1>() - offset1;
578,660,990✔
407
  const double r2 = r.get<i2>() - offset2;
35,616,614✔
408
  return r1 * r1 + r2 * r2 - radius * radius;
409
}
543,044,376✔
410

543,044,376✔
411
// The first template parameter indicates which axis the cylinder is aligned to.
253,639,474✔
412
// The other two parameters indicate the other two axes.  offset1 and offset2
289,404,902✔
413
// should correspond with i2 and i3, respectively.
414
template<int i1, int i2, int i3>
415
double axis_aligned_cylinder_distance(Position r, Direction u, bool coincident,
416
  double offset1, double offset2, double radius)
5,787,327✔
417
{
418
  const double a = 1.0 - u.get<i1>() * u.get<i1>(); // u^2 + v^2
5,787,327✔
419
  if (a == 0.0)
420
    return INFTY;
421

1,034✔
422
  const double r2 = r.get<i2>() - offset1;
423
  const double r3 = r.get<i3>() - offset2;
1,034✔
424
  const double k = r2 * u.get<i2>() + r3 * u.get<i3>();
1,034✔
425
  const double c = r2 * r2 + r3 * r3 - radius * radius;
1,034✔
426
  const double quad = k * k - a * c;
1,034✔
427

428
  if (quad < 0.0) {
429
    // No intersection with cylinder.
430
    return INFTY;
431

432
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
433
    // Particle is on the cylinder, thus one distance is positive/negative
434
    // and the other is zero. The sign of k determines if we are facing in or
435
    // out.
436
    if (k >= 0.0) {
1,400,615,048✔
437
      return INFTY;
438
    } else {
439
      return (-k + sqrt(quad)) / a;
1,400,615,048✔
440
    }
1,400,615,048✔
441

1,400,615,048✔
442
  } else if (c < 0.0) {
443
    // Particle is inside the cylinder, thus one distance must be negative
1,399,254,561✔
444
    // and one must be positive. The positive distance will be the one with
445
    // negative sign on sqrt(quad).
446
    return (-k + sqrt(quad)) / a;
1,399,254,561✔
447

1,399,254,561✔
448
  } else {
1,399,254,561✔
449
    // Particle is outside the cylinder, thus both distances are either
UNCOV
450
    // positive or negative. If positive, the smaller distance is the one
×
451
    // with positive sign on sqrt(quad).
452
    const double d = (-k - sqrt(quad)) / a;
UNCOV
453
    if (d < 0.0)
×
454
      return INFTY;
×
UNCOV
455
    return d;
×
456
  }
457
}
1,360,487✔
458

459
// The first template parameter indicates which axis the cylinder is aligned to.
460
// The other two parameters indicate the other two axes.  offset1 and offset2
1,360,487✔
461
// should correspond with i2 and i3, respectively.
1,360,487✔
462
template<int i1, int i2, int i3>
1,360,487✔
463
Direction axis_aligned_cylinder_normal(
464
  Position r, double offset1, double offset2)
465
{
466
  Direction u;
467
  u.get<i2>() = 2.0 * (r.get<i2>() - offset1);
468
  u.get<i3>() = 2.0 * (r.get<i3>() - offset2);
469
  u.get<i1>() = 0.0;
1,623,947,045✔
470
  return u;
471
}
472

1,623,947,045✔
473
//==============================================================================
1,623,947,045✔
474
// SurfaceXCylinder implementation
809,996✔
475
//==============================================================================
476

1,623,137,049✔
477
SurfaceXCylinder::SurfaceXCylinder(pugi::xml_node surf_node)
1,623,137,049✔
478
  : Surface(surf_node)
1,623,137,049✔
479
{
1,623,137,049✔
480
  read_coeffs(surf_node, id_, {&y0_, &z0_, &radius_});
1,623,137,049✔
481
}
482

1,623,137,049✔
483
double SurfaceXCylinder::evaluate(Position r) const
484
{
268,256,023✔
485
  return axis_aligned_cylinder_evaluate<1, 2>(r, y0_, z0_, radius_);
486
}
1,354,881,026✔
487

488
double SurfaceXCylinder::distance(
489
  Position r, Direction u, bool coincident) const
490
{
617,459,846✔
491
  return axis_aligned_cylinder_distance<0, 1, 2>(
309,800,354✔
492
    r, u, coincident, y0_, z0_, radius_);
493
}
307,659,492✔
494

495
Direction SurfaceXCylinder::normal(Position r) const
496
{
737,421,180✔
497
  return axis_aligned_cylinder_normal<0, 1, 2>(r, y0_, z0_);
498
}
499

500
void SurfaceXCylinder::to_hdf5_inner(hid_t group_id) const
296,867,592✔
501
{
502
  write_string(group_id, "type", "x-cylinder", false);
503
  array<double, 3> coeffs {{y0_, z0_, radius_}};
504
  write_dataset(group_id, "coefficients", coeffs);
505
}
506

440,553,588✔
507
BoundingBox SurfaceXCylinder::bounding_box(bool pos_side) const
440,553,588✔
508
{
63,410,359✔
509
  if (!pos_side) {
377,143,229✔
510
    return {-INFTY, INFTY, y0_ - radius_, y0_ + radius_, z0_ - radius_,
511
      z0_ + radius_};
512
  } else {
1,622,863,809✔
513
    return {};
514
  }
515
}
1,622,863,809✔
516
//==============================================================================
1,622,863,809✔
517
// SurfaceYCylinder implementation
369,996✔
518
//==============================================================================
519

1,622,493,813✔
520
SurfaceYCylinder::SurfaceYCylinder(pugi::xml_node surf_node)
1,622,493,813✔
521
  : Surface(surf_node)
1,622,493,813✔
522
{
1,622,493,813✔
523
  read_coeffs(surf_node, id_, {&x0_, &z0_, &radius_});
1,622,493,813✔
524
}
525

1,622,493,813✔
526
double SurfaceYCylinder::evaluate(Position r) const
527
{
268,256,023✔
528
  return axis_aligned_cylinder_evaluate<0, 2>(r, x0_, z0_, radius_);
529
}
1,354,237,790✔
530

531
double SurfaceYCylinder::distance(
532
  Position r, Direction u, bool coincident) const
533
{
617,459,846✔
534
  return axis_aligned_cylinder_distance<1, 0, 2>(
309,800,354✔
535
    r, u, coincident, x0_, z0_, radius_);
536
}
307,659,492✔
537

538
Direction SurfaceYCylinder::normal(Position r) const
539
{
736,777,944✔
540
  return axis_aligned_cylinder_normal<1, 0, 2>(r, x0_, z0_);
541
}
542

543
void SurfaceYCylinder::to_hdf5_inner(hid_t group_id) const
296,224,356✔
544
{
545
  write_string(group_id, "type", "y-cylinder", false);
546
  array<double, 3> coeffs {{x0_, z0_, radius_}};
547
  write_dataset(group_id, "coefficients", coeffs);
548
}
549

440,553,588✔
550
BoundingBox SurfaceYCylinder::bounding_box(bool pos_side) const
440,553,588✔
551
{
63,410,359✔
552
  if (!pos_side) {
377,143,229✔
553
    return {x0_ - radius_, x0_ + radius_, -INFTY, INFTY, z0_ - radius_,
554
      z0_ + radius_};
UNCOV
555
  } else {
×
556
    return {};
557
  }
UNCOV
558
}
×
559

×
UNCOV
560
//==============================================================================
×
561
// SurfaceZCylinder implementation
562
//==============================================================================
×
563

×
564
SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node)
×
UNCOV
565
  : Surface(surf_node)
×
566
{
×
567
  read_coeffs(surf_node, id_, {&x0_, &y0_, &radius_});
568
}
×
569

570
double SurfaceZCylinder::evaluate(Position r) const
×
571
{
572
  return axis_aligned_cylinder_evaluate<0, 1>(r, x0_, y0_, radius_);
×
573
}
574

575
double SurfaceZCylinder::distance(
576
  Position r, Direction u, bool coincident) const
×
UNCOV
577
{
×
578
  return axis_aligned_cylinder_distance<2, 0, 1>(
UNCOV
579
    r, u, coincident, x0_, y0_, radius_);
×
580
}
581

UNCOV
582
Direction SurfaceZCylinder::normal(Position r) const
×
583
{
584
  return axis_aligned_cylinder_normal<2, 0, 1>(r, x0_, y0_);
585
}
586

×
587
void SurfaceZCylinder::to_hdf5_inner(hid_t group_id) const
588
{
589
  write_string(group_id, "type", "z-cylinder", false);
590
  array<double, 3> coeffs {{x0_, y0_, radius_}};
591
  write_dataset(group_id, "coefficients", coeffs);
UNCOV
592
}
×
UNCOV
593

×
UNCOV
594
BoundingBox SurfaceZCylinder::bounding_box(bool pos_side) const
×
UNCOV
595
{
×
596
  if (!pos_side) {
597
    return {x0_ - radius_, x0_ + radius_, y0_ - radius_, y0_ + radius_, -INFTY,
598
      INFTY};
1,083,236✔
599
  } else {
600
    return {};
601
  }
1,083,236✔
602
}
1,083,236✔
603

440,000✔
604
//==============================================================================
605
// SurfaceSphere implementation
643,236✔
606
//==============================================================================
643,236✔
607

643,236✔
608
SurfaceSphere::SurfaceSphere(pugi::xml_node surf_node) : Surface(surf_node)
643,236✔
609
{
643,236✔
610
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_});
611
}
643,236✔
612

UNCOV
613
double SurfaceSphere::evaluate(Position r) const
×
614
{
615
  const double x = r.x - x0_;
643,236✔
616
  const double y = r.y - y0_;
617
  const double z = r.z - z0_;
618
  return x * x + y * y + z * z - radius_ * radius_;
UNCOV
619
}
×
UNCOV
620

×
621
double SurfaceSphere::distance(Position r, Direction u, bool coincident) const
UNCOV
622
{
×
623
  const double x = r.x - x0_;
624
  const double y = r.y - y0_;
625
  const double z = r.z - z0_;
643,236✔
626
  const double k = x * u.x + y * u.y + z * u.z;
627
  const double c = x * x + y * y + z * z - radius_ * radius_;
628
  const double quad = k * k - c;
629

643,236✔
630
  if (quad < 0.0) {
631
    // No intersection with sphere.
632
    return INFTY;
633

634
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
UNCOV
635
    // Particle is on the sphere, thus one distance is positive/negative and
×
UNCOV
636
    // the other is zero. The sign of k determines if we are facing in or out.
×
UNCOV
637
    if (k >= 0.0) {
×
UNCOV
638
      return INFTY;
×
639
    } else {
640
      return -k + sqrt(quad);
641
    }
642

643
  } else if (c < 0.0) {
644
    // Particle is inside the sphere, thus one distance must be negative and
645
    // one must be positive. The positive distance will be the one with
646
    // negative sign on sqrt(quad)
1,387,667✔
647
    return -k + sqrt(quad);
648

649
  } else {
1,387,667✔
650
    // Particle is outside the sphere, thus both distances are either positive
1,387,667✔
651
    // or negative. If positive, the smaller distance is the one with positive
1,387,667✔
652
    // sign on sqrt(quad).
1,387,667✔
653
    const double d = -k - sqrt(quad);
1,387,667✔
654
    if (d < 0.0)
655
      return INFTY;
1,387,667✔
656
    return d;
657
  }
658
}
1,387,667✔
659

1,387,667✔
660
Direction SurfaceSphere::normal(Position r) const
1,387,667✔
661
{
1,387,667✔
662
  return {2.0 * (r.x - x0_), 2.0 * (r.y - y0_), 2.0 * (r.z - z0_)};
1,387,667✔
663
}
UNCOV
664

×
665
void SurfaceSphere::to_hdf5_inner(hid_t group_id) const
666
{
UNCOV
667
  write_string(group_id, "type", "sphere", false);
×
668
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_}};
×
UNCOV
669
  write_dataset(group_id, "coefficients", coeffs);
×
UNCOV
670
}
×
671

×
672
BoundingBox SurfaceSphere::bounding_box(bool pos_side) const
673
{
×
674
  if (!pos_side) {
675
    return {x0_ - radius_, x0_ + radius_, y0_ - radius_, y0_ + radius_,
UNCOV
676
      z0_ - radius_, z0_ + radius_};
×
677
  } else {
×
UNCOV
678
    return {};
×
UNCOV
679
  }
×
680
}
×
681

682
//==============================================================================
683
// Generic functions for x-, y-, and z-, cones
684
//==============================================================================
685

686
// The first template parameter indicates which axis the cone is aligned to.
687
// The other two parameters indicate the other two axes.  offset1, offset2,
32✔
688
// and offset3 should correspond with i1, i2, and i3, respectively.
32✔
689
template<int i1, int i2, int i3>
690
double axis_aligned_cone_evaluate(
32✔
691
  Position r, double offset1, double offset2, double offset3, double radius_sq)
32✔
692
{
693
  const double r1 = r.get<i1>() - offset1;
1,360,487✔
694
  const double r2 = r.get<i2>() - offset2;
695
  const double r3 = r.get<i3>() - offset3;
1,360,487✔
696
  return r2 * r2 + r3 * r3 - radius_sq * r1 * r1;
697
}
698

1,083,236✔
699
// The first template parameter indicates which axis the cone is aligned to.
700
// The other two parameters indicate the other two axes.  offset1, offset2,
701
// and offset3 should correspond with i1, i2, and i3, respectively.
1,083,236✔
702
template<int i1, int i2, int i3>
1,083,236✔
703
double axis_aligned_cone_distance(Position r, Direction u, bool coincident,
704
  double offset1, double offset2, double offset3, double radius_sq)
UNCOV
705
{
×
706
  const double r1 = r.get<i1>() - offset1;
UNCOV
707
  const double r2 = r.get<i2>() - offset2;
×
708
  const double r3 = r.get<i3>() - offset3;
709
  const double a = u.get<i2>() * u.get<i2>() + u.get<i3>() * u.get<i3>() -
710
                   radius_sq * u.get<i1>() * u.get<i1>();
22✔
711
  const double k =
712
    r2 * u.get<i2>() + r3 * u.get<i3>() - radius_sq * r1 * u.get<i1>();
22✔
713
  const double c = r2 * r2 + r3 * r3 - radius_sq * r1 * r1;
22✔
714
  double quad = k * k - a * c;
22✔
715

22✔
716
  double d;
UNCOV
717

×
718
  if (quad < 0.0) {
UNCOV
719
    // No intersection with cone.
×
UNCOV
720
    return INFTY;
×
721

×
722
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
723
    // Particle is on the cone, thus one distance is positive/negative
×
724
    // and the other is zero. The sign of k determines if we are facing in or
725
    // out.
726
    if (k >= 0.0) {
727
      d = (-k - sqrt(quad)) / a;
728
    } else {
729
      d = (-k + sqrt(quad)) / a;
UNCOV
730
    }
×
UNCOV
731

×
732
  } else {
UNCOV
733
    // Calculate both solutions to the quadratic.
×
734
    quad = sqrt(quad);
735
    d = (-k - quad) / a;
UNCOV
736
    const double b = (-k + quad) / a;
×
737

UNCOV
738
    // Determine the smallest positive solution.
×
739
    if (d < 0.0) {
740
      if (b > 0.0)
UNCOV
741
        d = b;
×
742
    } else {
743
      if (b > 0.0) {
UNCOV
744
        if (b < d)
×
745
          d = b;
×
746
      }
747
    }
748
  }
×
749

UNCOV
750
  // If the distance was negative, set boundary distance to infinity.
×
751
  if (d <= 0.0)
752
    return INFTY;
UNCOV
753
  return d;
×
754
}
UNCOV
755

×
UNCOV
756
// The first template parameter indicates which axis the cone is aligned to.
×
757
// The other two parameters indicate the other two axes.  offset1, offset2,
×
758
// and offset3 should correspond with i1, i2, and i3, respectively.
759
template<int i1, int i2, int i3>
760
Direction axis_aligned_cone_normal(
×
761
  Position r, double offset1, double offset2, double offset3, double radius_sq)
UNCOV
762
{
×
UNCOV
763
  Direction u;
×
764
  u.get<i1>() = -2.0 * radius_sq * (r.get<i1>() - offset1);
×
765
  u.get<i2>() = 2.0 * (r.get<i2>() - offset2);
766
  u.get<i3>() = 2.0 * (r.get<i3>() - offset3);
×
767
  return u;
768
}
769

770
//==============================================================================
771
// SurfaceXCone implementation
772
//==============================================================================
773

774
SurfaceXCone::SurfaceXCone(pugi::xml_node surf_node) : Surface(surf_node)
4,518✔
775
{
4,518✔
776
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
777
}
4,518✔
778

4,518✔
779
double SurfaceXCone::evaluate(Position r) const
780
{
1,399,254,561✔
781
  return axis_aligned_cone_evaluate<0, 1, 2>(r, x0_, y0_, z0_, radius_sq_);
782
}
1,399,254,561✔
783

784
double SurfaceXCone::distance(Position r, Direction u, bool coincident) const
785
{
1,622,863,809✔
786
  return axis_aligned_cone_distance<0, 1, 2>(
787
    r, u, coincident, x0_, y0_, z0_, radius_sq_);
788
}
1,622,863,809✔
789

1,622,863,809✔
790
Direction SurfaceXCone::normal(Position r) const
791
{
792
  return axis_aligned_cone_normal<0, 1, 2>(r, x0_, y0_, z0_, radius_sq_);
1,387,667✔
793
}
794

1,387,667✔
795
void SurfaceXCone::to_hdf5_inner(hid_t group_id) const
796
{
797
  write_string(group_id, "type", "x-cone", false);
3,270✔
798
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
799
  write_dataset(group_id, "coefficients", coeffs);
3,270✔
800
}
3,270✔
801

3,270✔
802
//==============================================================================
3,270✔
803
// SurfaceYCone implementation
804
//==============================================================================
44✔
805

806
SurfaceYCone::SurfaceYCone(pugi::xml_node surf_node) : Surface(surf_node)
44✔
807
{
22✔
808
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
22✔
809
}
810

22✔
811
double SurfaceYCone::evaluate(Position r) const
812
{
813
  return axis_aligned_cone_evaluate<1, 0, 2>(r, y0_, x0_, z0_, radius_sq_);
814
}
815

816
double SurfaceYCone::distance(Position r, Direction u, bool coincident) const
817
{
818
  return axis_aligned_cone_distance<1, 0, 2>(
7,639✔
819
    r, u, coincident, y0_, x0_, z0_, radius_sq_);
820
}
7,639✔
821

7,639✔
822
Direction SurfaceYCone::normal(Position r) const
823
{
540,504,052✔
824
  return axis_aligned_cone_normal<1, 0, 2>(r, y0_, x0_, z0_, radius_sq_);
825
}
540,504,052✔
826

540,504,052✔
827
void SurfaceYCone::to_hdf5_inner(hid_t group_id) const
540,504,052✔
828
{
540,504,052✔
829
  write_string(group_id, "type", "y-cone", false);
830
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
831
  write_dataset(group_id, "coefficients", coeffs);
594,219,616✔
832
}
833

594,219,616✔
834
//==============================================================================
594,219,616✔
835
// SurfaceZCone implementation
594,219,616✔
836
//==============================================================================
594,219,616✔
837

594,219,616✔
838
SurfaceZCone::SurfaceZCone(pugi::xml_node surf_node) : Surface(surf_node)
594,219,616✔
839
{
840
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
594,219,616✔
841
}
842

116,705,324✔
843
double SurfaceZCone::evaluate(Position r) const
844
{
477,514,292✔
845
  return axis_aligned_cone_evaluate<2, 0, 1>(r, z0_, x0_, y0_, radius_sq_);
846
}
847

65,161,292✔
848
double SurfaceZCone::distance(Position r, Direction u, bool coincident) const
32,341,874✔
849
{
850
  return axis_aligned_cone_distance<2, 0, 1>(
32,819,418✔
851
    r, u, coincident, z0_, x0_, y0_, radius_sq_);
852
}
853

412,353,000✔
854
Direction SurfaceZCone::normal(Position r) const
855
{
856
  return axis_aligned_cone_normal<2, 0, 1>(r, z0_, x0_, y0_, radius_sq_);
857
}
370,005,659✔
858

859
void SurfaceZCone::to_hdf5_inner(hid_t group_id) const
860
{
861
  write_string(group_id, "type", "z-cone", false);
862
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
863
  write_dataset(group_id, "coefficients", coeffs);
42,347,341✔
864
}
42,347,341✔
865

9,335,276✔
866
//==============================================================================
33,012,065✔
867
// SurfaceQuadric implementation
868
//==============================================================================
869

870
SurfaceQuadric::SurfaceQuadric(pugi::xml_node surf_node) : Surface(surf_node)
6,278,063✔
871
{
872
  read_coeffs(
6,278,063✔
873
    surf_node, id_, {&A_, &B_, &C_, &D_, &E_, &F_, &G_, &H_, &J_, &K_});
874
}
875

5,873✔
876
double SurfaceQuadric::evaluate(Position r) const
877
{
5,873✔
878
  const double x = r.x;
5,873✔
879
  const double y = r.y;
5,873✔
880
  const double z = r.z;
5,873✔
881
  return x * (A_ * x + D_ * y + G_) + y * (B_ * y + E_ * z + H_) +
UNCOV
882
         z * (C_ * z + F_ * x + J_) + K_;
×
883
}
UNCOV
884

×
UNCOV
885
double SurfaceQuadric::distance(
×
886
  Position r, Direction ang, bool coincident) const
×
887
{
888
  const double& x = r.x;
×
889
  const double& y = r.y;
890
  const double& z = r.z;
891
  const double& u = ang.x;
892
  const double& v = ang.y;
893
  const double& w = ang.z;
894

895
  const double a =
896
    A_ * u * u + B_ * v * v + C_ * w * w + D_ * u * v + E_ * v * w + F_ * u * w;
897
  const double k = A_ * u * x + B_ * v * y + C_ * w * z +
898
                   0.5 * (D_ * (u * y + v * x) + E_ * (v * z + w * y) +
899
                           F_ * (w * x + u * z) + G_ * u + H_ * v + J_ * w);
900
  const double c = A_ * x * x + B_ * y * y + C_ * z * z + D_ * x * y +
322,586✔
901
                   E_ * y * z + F_ * x * z + G_ * x + H_ * y + J_ * z + K_;
902
  double quad = k * k - a * c;
903

322,586✔
904
  double d;
322,586✔
905

322,586✔
906
  if (quad < 0.0) {
322,586✔
907
    // No intersection with surface.
908
    return INFTY;
322,586✔
909

910
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
911
    // Particle is on the surface, thus one distance is positive/negative and
322,586✔
912
    // the other is zero. The sign of k determines which distance is zero and
322,586✔
913
    // which is not. Additionally, if a is zero, it means the particle is on
322,586✔
914
    // a plane-like surface.
322,586✔
915
    if (a == 0.0) {
UNCOV
916
      d = INFTY; // see the below explanation
×
917
    } else if (k >= 0.0) {
918
      d = (-k - sqrt(quad)) / a;
UNCOV
919
    } else {
×
920
      d = (-k + sqrt(quad)) / a;
×
UNCOV
921
    }
×
UNCOV
922

×
923
  } else if (a == 0.0) {
924
    // Given the orientation of the particle, the quadric looks like a plane in
×
925
    // this case, and thus we have only one solution despite potentially having
926
    // quad > 0.0. While the term under the square root may be real, in one
UNCOV
927
    // case of the +/- of the quadratic formula, 0/0 results, and in another, a
×
928
    // finite value over 0 results. Applying L'Hopital's to the 0/0 case gives
×
UNCOV
929
    // the below. Alternatively this can be found by simply putting a=0 in the
×
UNCOV
930
    // equation ax^2 + bx + c = 0.
×
931
    d = -0.5 * c / k;
932
  } else {
933
    // Calculate both solutions to the quadratic.
934
    quad = sqrt(quad);
935
    d = (-k - quad) / a;
936
    double b = (-k + quad) / a;
937

963,358✔
938
    // Determine the smallest positive solution.
939
    if (d < 0.0) {
940
      if (b > 0.0)
963,358✔
941
        d = b;
963,358✔
942
    } else {
963,358✔
943
      if (b > 0.0) {
963,358✔
944
        if (b < d)
963,358✔
945
          d = b;
963,358✔
946
      }
963,358✔
947
    }
963,358✔
948
  }
963,358✔
949

950
  // If the distance was negative, set boundary distance to infinity.
951
  if (d <= 0.0)
952
    return INFTY;
963,358✔
953
  return d;
UNCOV
954
}
×
955

956
Direction SurfaceQuadric::normal(Position r) const
963,358✔
957
{
958
  const double& x = r.x;
959
  const double& y = r.y;
960
  const double& z = r.z;
60,566✔
UNCOV
961
  return {2.0 * A_ * x + D_ * y + F_ * z + G_,
×
962
    2.0 * B_ * y + D_ * x + E_ * z + H_, 2.0 * C_ * z + E_ * y + F_ * x + J_};
963
}
60,566✔
964

965
void SurfaceQuadric::to_hdf5_inner(hid_t group_id) const
966
{
967
  write_string(group_id, "type", "quadric", false);
968
  array<double, 10> coeffs {{A_, B_, C_, D_, E_, F_, G_, H_, J_, K_}};
902,792✔
969
  write_dataset(group_id, "coefficients", coeffs);
902,792✔
970
}
902,792✔
971

972
//==============================================================================
973
// Torus helper functions
902,792✔
974
//==============================================================================
766,524✔
975

650,507✔
976
double torus_distance(double x1, double x2, double x3, double u1, double u2,
977
  double u3, double A, double B, double C, bool coincident)
136,268✔
978
{
136,268✔
979
  // Coefficients for equation: (c2 t^2 + c1 t + c0)^2 = c2' t^2 + c1' t + c0'
136,268✔
980
  double D = (C * C) / (B * B);
981
  double c2 = u1 * u1 + u2 * u2 + D * u3 * u3;
982
  double c1 = 2 * (u1 * x1 + u2 * x2 + D * u3 * x3);
983
  double c0 = x1 * x1 + x2 * x2 + D * x3 * x3 + A * A - C * C;
984
  double four_A2 = 4 * A * A;
985
  double c2p = four_A2 * (u1 * u1 + u2 * u2);
963,358✔
986
  double c1p = 2 * four_A2 * (u1 * x1 + u2 * x2);
137,467✔
987
  double c0p = four_A2 * (x1 * x1 + x2 * x2);
825,891✔
988

989
  // Coefficient for equation: a t^4 + b t^3 + c t^2 + d t + e = 0. If the point
963,358✔
990
  // is coincident, the 'e' coefficient should be zero. Explicitly setting it to
991
  // zero helps avoid numerical issues below with root finding.
992
  double coeff[5];
963,358✔
993
  coeff[0] = coincident ? 0.0 : c0 * c0 - c0p;
963,358✔
994
  coeff[1] = 2 * c0 * c1 - c1p;
963,358✔
995
  coeff[2] = c1 * c1 + 2 * c0 * c2 - c2p;
963,358✔
996
  coeff[3] = 2 * c1 * c2;
963,358✔
997
  coeff[4] = c2 * c2;
963,358✔
998

963,358✔
999
  std::complex<double> roots[4];
963,358✔
1000
  oqs::quartic_solver(coeff, roots);
963,358✔
1001

1002
  // Find smallest positive, real root. In the case where the particle is
1003
  // coincident with the surface, we are sure to have one root very close to
1004
  // zero but possibly small and positive. A tolerance is set to discard that
963,358✔
1005
  // zero.
UNCOV
1006
  double distance = INFTY;
×
1007
  double cutoff = coincident ? TORUS_TOL : 0.0;
1008
  for (int i = 0; i < 4; ++i) {
963,358✔
1009
    if (roots[i].imag() == 0) {
1010
      double root = roots[i].real();
1011
      if (root > cutoff && root < distance) {
1012
        // Avoid roots corresponding to internal surfaces
60,566✔
UNCOV
1013
        double s1 = x1 + u1 * root;
×
1014
        double s2 = x2 + u2 * root;
1015
        double s3 = x3 + u3 * root;
60,566✔
1016
        double check = D * s3 * s3 + s1 * s1 + s2 * s2 + A * A - C * C;
1017
        if (check >= 0) {
1018
          distance = root;
1019
        }
1020
      }
902,792✔
1021
    }
902,792✔
1022
  }
902,792✔
1023
  return distance;
1024
}
1025

902,792✔
1026
//==============================================================================
766,524✔
1027
// SurfaceXTorus implementation
650,507✔
1028
//==============================================================================
1029

136,268✔
1030
SurfaceXTorus::SurfaceXTorus(pugi::xml_node surf_node) : Surface(surf_node)
136,268✔
1031
{
136,268✔
1032
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1033
}
1034

1035
void SurfaceXTorus::to_hdf5_inner(hid_t group_id) const
1036
{
1037
  write_string(group_id, "type", "x-torus", false);
963,358✔
1038
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
137,467✔
1039
  write_dataset(group_id, "coefficients", coeffs);
825,891✔
1040
}
UNCOV
1041

×
1042
double SurfaceXTorus::evaluate(Position r) const
1043
{
UNCOV
1044
  double x = r.x - x0_;
×
1045
  double y = r.y - y0_;
×
UNCOV
1046
  double z = r.z - z0_;
×
UNCOV
1047
  return (x * x) / (B_ * B_) +
×
1048
         std::pow(std::sqrt(y * y + z * z) - A_, 2) / (C_ * C_) - 1.;
×
1049
}
×
1050

×
1051
double SurfaceXTorus::distance(Position r, Direction u, bool coincident) const
×
1052
{
×
1053
  double x = r.x - x0_;
1054
  double y = r.y - y0_;
1055
  double z = r.z - z0_;
1056
  return torus_distance(y, z, x, u.y, u.z, u.x, A_, B_, C_, coincident);
×
1057
}
UNCOV
1058

×
1059
Direction SurfaceXTorus::normal(Position r) const
1060
{
×
1061
  // reduce the expansion of the full form for torus
1062
  double x = r.x - x0_;
1063
  double y = r.y - y0_;
1064
  double z = r.z - z0_;
×
UNCOV
1065

×
1066
  // f(x,y,z) = x^2/B^2 + (sqrt(y^2 + z^2) - A)^2/C^2 - 1
UNCOV
1067
  // ∂f/∂x = 2x/B^2
×
1068
  // ∂f/∂y = 2y(g - A)/(g*C^2) where g = sqrt(y^2 + z^2)
1069
  // ∂f/∂z = 2z(g - A)/(g*C^2)
1070
  // Multiplying by g*C^2*B^2 / 2 gives:
1071
  double g = std::sqrt(y * y + z * z);
UNCOV
1072
  double nx = C_ * C_ * g * x;
×
UNCOV
1073
  double ny = y * (g - A_) * B_ * B_;
×
UNCOV
1074
  double nz = z * (g - A_) * B_ * B_;
×
1075
  Direction n(nx, ny, nz);
1076
  return n / n.norm();
1077
}
×
1078

×
UNCOV
1079
//==============================================================================
×
1080
// SurfaceYTorus implementation
1081
//==============================================================================
×
1082

×
1083
SurfaceYTorus::SurfaceYTorus(pugi::xml_node surf_node) : Surface(surf_node)
×
1084
{
1085
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1086
}
1087

1088
void SurfaceYTorus::to_hdf5_inner(hid_t group_id) const
UNCOV
1089
{
×
UNCOV
1090
  write_string(group_id, "type", "y-torus", false);
×
UNCOV
1091
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
×
1092
  write_dataset(group_id, "coefficients", coeffs);
1093
}
×
1094

1095
double SurfaceYTorus::evaluate(Position r) const
UNCOV
1096
{
×
1097
  double x = r.x - x0_;
×
UNCOV
1098
  double y = r.y - y0_;
×
UNCOV
1099
  double z = r.z - z0_;
×
1100
  return (y * y) / (B_ * B_) +
×
1101
         std::pow(std::sqrt(x * x + z * z) - A_, 2) / (C_ * C_) - 1.;
×
1102
}
×
1103

×
1104
double SurfaceYTorus::distance(Position r, Direction u, bool coincident) const
×
1105
{
1106
  double x = r.x - x0_;
1107
  double y = r.y - y0_;
1108
  double z = r.z - z0_;
×
1109
  return torus_distance(x, z, y, u.x, u.z, u.y, A_, B_, C_, coincident);
UNCOV
1110
}
×
1111

1112
Direction SurfaceYTorus::normal(Position r) const
×
1113
{
1114
  // reduce the expansion of the full form for torus
1115
  double x = r.x - x0_;
1116
  double y = r.y - y0_;
×
UNCOV
1117
  double z = r.z - z0_;
×
1118

UNCOV
1119
  // f(x,y,z) = y^2/B^2 + (sqrt(x^2 + z^2) - A)^2/C^2 - 1
×
1120
  // ∂f/∂x = 2x(g - A)/(g*C^2) where g = sqrt(x^2 + z^2)
1121
  // ∂f/∂y = 2y/B^2
1122
  // ∂f/∂z = 2z(g - A)/(g*C^2)
1123
  // Multiplying by g*C^2*B^2 / 2 gives:
UNCOV
1124
  double g = std::sqrt(x * x + z * z);
×
UNCOV
1125
  double nx = x * (g - A_) * B_ * B_;
×
UNCOV
1126
  double ny = C_ * C_ * g * y;
×
1127
  double nz = z * (g - A_) * B_ * B_;
1128
  Direction n(nx, ny, nz);
1129
  return n / n.norm();
×
1130
}
×
UNCOV
1131

×
1132
//==============================================================================
1133
// SurfaceZTorus implementation
×
1134
//==============================================================================
×
1135

×
1136
SurfaceZTorus::SurfaceZTorus(pugi::xml_node surf_node) : Surface(surf_node)
1137
{
1138
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1139
}
1140

UNCOV
1141
void SurfaceZTorus::to_hdf5_inner(hid_t group_id) const
×
UNCOV
1142
{
×
UNCOV
1143
  write_string(group_id, "type", "z-torus", false);
×
1144
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
1145
  write_dataset(group_id, "coefficients", coeffs);
1146
}
1147

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

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

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

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

×
1185
//==============================================================================
1186

1187
void read_surfaces(pugi::xml_node node)
1188
{
1189
  // Count the number of surfaces
1190
  int n_surfaces = 0;
UNCOV
1191
  for (pugi::xml_node surf_node : node.children("surface")) {
×
1192
    n_surfaces++;
UNCOV
1193
  }
×
1194

1195
  // Loop over XML surface elements and populate the array.  Keep track of
UNCOV
1196
  // periodic surfaces and their albedos.
×
1197
  model::surfaces.reserve(n_surfaces);
UNCOV
1198
  std::set<std::pair<int, int>> periodic_pairs;
×
1199
  std::unordered_map<int, double> albedo_map;
1200
  {
UNCOV
1201
    pugi::xml_node surf_node;
×
1202
    int i_surf;
UNCOV
1203
    for (surf_node = node.child("surface"), i_surf = 0; surf_node;
×
UNCOV
1204
      surf_node = surf_node.next_sibling("surface"), i_surf++) {
×
1205
      std::string surf_type = get_node_value(surf_node, "type", true, true);
1206

1207
      // Allocate and initialize the new surface
×
1208

UNCOV
1209
      if (surf_type == "x-plane") {
×
1210
        model::surfaces.push_back(make_unique<SurfaceXPlane>(surf_node));
1211

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

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

1218
      } else if (surf_type == "plane") {
1219
        model::surfaces.push_back(make_unique<SurfacePlane>(surf_node));
1220

1221
      } else if (surf_type == "x-cylinder") {
1222
        model::surfaces.push_back(make_unique<SurfaceXCylinder>(surf_node));
UNCOV
1223

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

1227
      } else if (surf_type == "z-cylinder") {
UNCOV
1228
        model::surfaces.push_back(make_unique<SurfaceZCylinder>(surf_node));
×
1229

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

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

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

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

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

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

×
1248
      } else if (surf_type == "y-torus") {
×
1249
        model::surfaces.push_back(std::make_unique<SurfaceYTorus>(surf_node));
1250

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

1254
      } else {
1255
        fatal_error(fmt::format("Invalid surface type, \"{}\"", surf_type));
16✔
1256
      }
1257

16✔
1258
      // Check for a periodic surface
16✔
1259
      if (check_for_node(surf_node, "boundary")) {
1260
        std::string surf_bc = get_node_value(surf_node, "boundary", true, true);
322,586✔
1261
        if (surf_bc == "periodic") {
1262
          // Check for surface albedo. Skip sanity check as it is already done
322,586✔
1263
          // in the Surface class's constructor.
1264
          if (check_for_node(surf_node, "albedo")) {
1265
            albedo_map[model::surfaces.back()->id_] =
963,358✔
1266
              std::stod(get_node_value(surf_node, "albedo"));
1267
          }
963,358✔
1268
          if (check_for_node(surf_node, "periodic_surface_id")) {
963,358✔
1269
            int i_periodic =
1270
              std::stoi(get_node_value(surf_node, "periodic_surface_id"));
1271
            int lo_id = std::min(model::surfaces.back()->id_, i_periodic);
60,566✔
1272
            int hi_id = std::max(model::surfaces.back()->id_, i_periodic);
1273
            periodic_pairs.insert({lo_id, hi_id});
60,566✔
1274
          } else {
1275
            periodic_pairs.insert({model::surfaces.back()->id_, -1});
1276
          }
11✔
1277
        }
1278
      }
11✔
1279
    }
11✔
1280
  }
11✔
1281

11✔
1282
  // Fill the surface map
1283
  for (int i_surf = 0; i_surf < model::surfaces.size(); i_surf++) {
1284
    int id = model::surfaces[i_surf]->id_;
1285
    auto in_map = model::surface_map.find(id);
1286
    if (in_map == model::surface_map.end()) {
1287
      model::surface_map[id] = i_surf;
16✔
1288
    } else {
UNCOV
1289
      fatal_error(
×
1290
        fmt::format("Two or more surfaces use the same unique ID: {}", id));
16✔
1291
    }
16✔
1292
  }
1293

177,572✔
1294
  // Resolve unpaired periodic surfaces.  A lambda function is used with
1295
  // std::find_if to identify the unpaired surfaces.
177,572✔
1296
  auto is_unresolved_pair = [](const std::pair<int, int> p) {
177,572✔
1297
    return p.second == -1;
177,572✔
1298
  };
177,572✔
1299
  auto first_unresolved = std::find_if(
177,572✔
1300
    periodic_pairs.begin(), periodic_pairs.end(), is_unresolved_pair);
1301
  if (first_unresolved != periodic_pairs.end()) {
1302
    // Found one unpaired surface; search for a second one
312,510✔
1303
    auto next_elem = first_unresolved;
1304
    next_elem++;
1305
    auto second_unresolved =
312,510✔
1306
      std::find_if(next_elem, periodic_pairs.end(), is_unresolved_pair);
312,510✔
1307
    if (second_unresolved == periodic_pairs.end()) {
312,510✔
1308
      fatal_error("Found only one periodic surface without a specified partner."
312,510✔
1309
                  " Please specify the partner for each periodic surface.");
312,510✔
1310
    }
312,510✔
1311

1312
    // Make sure there isn't a third unpaired surface
312,510✔
1313
    next_elem = second_unresolved;
312,510✔
1314
    next_elem++;
312,510✔
1315
    auto third_unresolved =
312,510✔
1316
      std::find_if(next_elem, periodic_pairs.end(), is_unresolved_pair);
312,510✔
1317
    if (third_unresolved != periodic_pairs.end()) {
312,510✔
1318
      fatal_error(
312,510✔
1319
        "Found at least three periodic surfaces without a specified "
312,510✔
1320
        "partner. Please specify the partner for each periodic surface.");
1321
    }
1322

1323
    // Add the completed pair and remove the old, unpaired entries
312,510✔
1324
    int lo_id = std::min(first_unresolved->first, second_unresolved->first);
UNCOV
1325
    int hi_id = std::max(first_unresolved->first, second_unresolved->first);
×
1326
    periodic_pairs.insert({lo_id, hi_id});
1327
    periodic_pairs.erase(first_unresolved);
312,510✔
1328
    periodic_pairs.erase(second_unresolved);
1329
  }
1330

1331
  // Assign the periodic boundary conditions with albedos
1332
  for (auto periodic_pair : periodic_pairs) {
34,540✔
UNCOV
1333
    int i_surf = model::surface_map[periodic_pair.first];
×
1334
    int j_surf = model::surface_map[periodic_pair.second];
34,540✔
UNCOV
1335
    Surface& surf1 {*model::surfaces[i_surf]};
×
1336
    Surface& surf2 {*model::surfaces[j_surf]};
1337

34,540✔
1338
    // Compute the dot product of the surface normals
1339
    Direction norm1 = surf1.normal({0, 0, 0});
1340
    Direction norm2 = surf2.normal({0, 0, 0});
277,970✔
1341
    norm1 /= norm1.norm();
1342
    norm2 /= norm2.norm();
1343
    double dot_prod = norm1.dot(norm2);
1344

1345
    // If the dot product is 1 (to within floating point precision) then the
1346
    // planes are parallel which indicates a translational periodic boundary
1347
    // condition.  Otherwise, it is a rotational periodic BC.
UNCOV
1348
    if (std::abs(1.0 - dot_prod) < FP_PRECISION) {
×
1349
      surf1.bc_ = make_unique<TranslationalPeriodicBC>(i_surf, j_surf);
1350
      surf2.bc_ = make_unique<TranslationalPeriodicBC>(i_surf, j_surf);
1351
    } else {
277,970✔
1352
      surf1.bc_ = make_unique<RotationalPeriodicBC>(i_surf, j_surf);
277,970✔
1353
      surf2.bc_ = make_unique<RotationalPeriodicBC>(i_surf, j_surf);
277,970✔
1354
    }
1355

1356
    // If albedo data is present in albedo map, set the boundary albedo.
277,970✔
1357
    if (albedo_map.count(surf1.id_)) {
277,970✔
1358
      surf1.bc_->set_albedo(albedo_map[surf1.id_]);
277,970✔
1359
    }
UNCOV
1360
    if (albedo_map.count(surf2.id_)) {
×
UNCOV
1361
      surf2.bc_->set_albedo(albedo_map[surf2.id_]);
×
UNCOV
1362
    }
×
1363
  }
1364
}
1365

1366
void free_memory_surfaces()
1367
{
1368
  model::surfaces.clear();
312,510✔
UNCOV
1369
  model::surface_map.clear();
×
1370
}
312,510✔
1371

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