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

openmc-dev / openmc / 13423135160

19 Feb 2025 09:58PM UTC coverage: 85.028% (-0.003%) from 85.031%
13423135160

push

github

web-flow
simplify mechanism to detect if geometry entity is DAG (#3269)

19 of 22 new or added lines in 6 files covered. (86.36%)

2 existing lines in 2 files now uncovered.

50630 of 59545 relevant lines covered (85.03%)

35316675.05 hits per line

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

71.77
/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
#include <gsl/gsl-lite.hpp>
11

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

23
namespace openmc {
24

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

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

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

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

49
  // Copy the coefficients
50
  int i = 0;
38,258✔
51
  for (auto c : coeffs) {
116,491✔
52
    *c = coeffs_file[i++];
78,233✔
53
  }
54
}
38,258✔
55

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

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

62
Surface::Surface(pugi::xml_node surf_node)
38,258✔
63
{
64
  if (check_for_node(surf_node, "id")) {
38,258✔
65
    id_ = std::stoi(get_node_value(surf_node, "id"));
38,258✔
66
    if (contains(settings::source_write_surf_id, id_) ||
75,664✔
67
        settings::source_write_surf_id.empty()) {
37,406✔
68
      surf_source_ = true;
37,151✔
69
    }
70
  } else {
71
    fatal_error("Must specify id of surface in geometry XML file.");
×
72
  }
73

74
  if (check_for_node(surf_node, "name")) {
38,258✔
75
    name_ = get_node_value(surf_node, "name", false);
8,372✔
76
  }
77

78
  if (check_for_node(surf_node, "boundary")) {
38,258✔
79
    std::string surf_bc = get_node_value(surf_node, "boundary", true, true);
22,644✔
80

81
    if (surf_bc == "transmission" || surf_bc == "transmit" || surf_bc.empty()) {
22,644✔
82
      // Leave the bc_ a nullptr
83
    } else if (surf_bc == "vacuum") {
22,644✔
84
      bc_ = make_unique<VacuumBC>();
10,954✔
85
    } else if (surf_bc == "reflective" || surf_bc == "reflect" ||
12,040✔
86
               surf_bc == "reflecting") {
350✔
87
      bc_ = make_unique<ReflectiveBC>();
11,340✔
88
    } else if (surf_bc == "white") {
350✔
89
      bc_ = make_unique<WhiteBC>();
102✔
90
    } else if (surf_bc == "periodic") {
248✔
91
      // Periodic BCs are handled separately
92
    } else {
93
      fatal_error(fmt::format("Unknown boundary condition \"{}\" specified "
×
94
                              "on surface {}",
95
        surf_bc, id_));
×
96
    }
97

98
    if (check_for_node(surf_node, "albedo") && bc_) {
22,644✔
99
      double surf_alb = std::stod(get_node_value(surf_node, "albedo"));
102✔
100

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

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

112
      bc_->set_albedo(surf_alb);
102✔
113
    }
114
  }
22,644✔
115
}
38,258✔
116

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

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

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

139
  // Reflect direction according to normal.
140
  return u.reflect(n);
1,321,989,298✔
141
}
142

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

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

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

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

160
  // normalize the direction
161
  return u / u.norm();
2,218,872✔
162
}
163

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

168
  if (geom_type() == GeometryType::DAG) {
31,032✔
169
    write_string(surf_group, "geom_type", "dagmc", false);
594✔
170
  } else if (geom_type() == GeometryType::CSG) {
30,438✔
171
    write_string(surf_group, "geom_type", "csg", false);
30,438✔
172

173
    if (bc_) {
30,438✔
174
      write_string(surf_group, "boundary_type", bc_->type(), false);
18,298✔
175
      bc_->to_hdf5(surf_group);
18,298✔
176
    } else {
177
      write_string(surf_group, "boundary_type", "transmission", false);
12,140✔
178
    }
179
  }
180

181
  if (!name_.empty()) {
31,032✔
182
    write_string(surf_group, "name", name_, false);
6,322✔
183
  }
184

185
  to_hdf5_inner(surf_group);
31,032✔
186

187
  close_group(surf_group);
31,032✔
188
}
31,032✔
189

190
//==============================================================================
191
// Generic functions for x-, y-, and z-, planes.
192
//==============================================================================
193

194
// The template parameter indicates the axis normal to the plane.
195
template<int i>
196
double axis_aligned_plane_distance(
2,147,483,647✔
197
  Position r, Direction u, bool coincident, double offset)
198
{
199
  const double f = offset - r[i];
2,147,483,647✔
200
  if (coincident || std::abs(f) < FP_COINCIDENT || u[i] == 0.0)
2,147,483,647✔
201
    return INFTY;
651,546,791✔
202
  const double d = f / u[i];
2,147,483,647✔
203
  if (d < 0.0)
2,147,483,647✔
204
    return INFTY;
2,147,483,647✔
205
  return d;
2,147,483,647✔
206
}
207

2,039,315,374✔
208
//==============================================================================
209
// SurfaceXPlane implementation
210
//==============================================================================
2,039,315,374✔
211

2,039,315,374✔
212
SurfaceXPlane::SurfaceXPlane(pugi::xml_node surf_node) : Surface(surf_node)
70,668,239✔
213
{
1,968,647,135✔
214
  read_coeffs(surf_node, id_, {&x0_});
1,968,647,135✔
215
}
937,865,718✔
216

1,030,781,417✔
217
double SurfaceXPlane::evaluate(Position r) const
218
{
2,147,483,647✔
219
  return r.x - x0_;
220
}
221

2,147,483,647✔
222
double SurfaceXPlane::distance(Position r, Direction u, bool coincident) const
2,147,483,647✔
223
{
312,109,311✔
224
  return axis_aligned_plane_distance<0>(r, u, coincident, x0_);
2,147,483,647✔
225
}
2,147,483,647✔
226

2,147,483,647✔
227
Direction SurfaceXPlane::normal(Position r) const
2,147,483,647✔
228
{
229
  return {1., 0., 0.};
2,147,483,647✔
230
}
231

232
void SurfaceXPlane::to_hdf5_inner(hid_t group_id) const
2,147,483,647✔
233
{
2,147,483,647✔
234
  write_string(group_id, "type", "x-plane", false);
268,769,241✔
235
  array<double, 1> coeffs {{x0_}};
2,147,483,647✔
236
  write_dataset(group_id, "coefficients", coeffs);
2,147,483,647✔
237
}
2,147,483,647✔
238

2,147,483,647✔
239
BoundingBox SurfaceXPlane::bounding_box(bool pos_side) const
240
{
241
  if (pos_side) {
242
    return {x0_, INFTY, -INFTY, INFTY, -INFTY, INFTY};
243
  } else {
244
    return {-INFTY, x0_, -INFTY, INFTY, -INFTY, INFTY};
245
  }
9,087✔
246
}
247

9,087✔
248
//==============================================================================
9,087✔
249
// SurfaceYPlane implementation
250
//==============================================================================
1,804,356,067✔
251

252
SurfaceYPlane::SurfaceYPlane(pugi::xml_node surf_node) : Surface(surf_node)
1,804,356,067✔
253
{
254
  read_coeffs(surf_node, id_, {&y0_});
255
}
2,147,483,647✔
256

257
double SurfaceYPlane::evaluate(Position r) const
2,147,483,647✔
258
{
259
  return r.y - y0_;
260
}
270,681,061✔
261

262
double SurfaceYPlane::distance(Position r, Direction u, bool coincident) const
270,681,061✔
263
{
264
  return axis_aligned_plane_distance<1>(r, u, coincident, y0_);
265
}
7,154✔
266

267
Direction SurfaceYPlane::normal(Position r) const
7,154✔
268
{
7,154✔
269
  return {0., 1., 0.};
7,154✔
270
}
7,154✔
271

272
void SurfaceYPlane::to_hdf5_inner(hid_t group_id) const
264✔
273
{
274
  write_string(group_id, "type", "y-plane", false);
264✔
275
  array<double, 1> coeffs {{y0_}};
132✔
276
  write_dataset(group_id, "coefficients", coeffs);
277
}
132✔
278

279
BoundingBox SurfaceYPlane::bounding_box(bool pos_side) const
280
{
281
  if (pos_side) {
282
    return {-INFTY, INFTY, y0_, INFTY, -INFTY, INFTY};
283
  } else {
284
    return {-INFTY, INFTY, -INFTY, y0_, -INFTY, INFTY};
285
  }
8,132✔
286
}
287

8,132✔
288
//==============================================================================
8,132✔
289
// SurfaceZPlane implementation
290
//==============================================================================
1,617,093,936✔
291

292
SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) : Surface(surf_node)
1,617,093,936✔
293
{
294
  read_coeffs(surf_node, id_, {&z0_});
295
}
2,147,483,647✔
296

297
double SurfaceZPlane::evaluate(Position r) const
2,147,483,647✔
298
{
299
  return r.z - z0_;
300
}
314,341,955✔
301

302
double SurfaceZPlane::distance(Position r, Direction u, bool coincident) const
314,341,955✔
303
{
304
  return axis_aligned_plane_distance<2>(r, u, coincident, z0_);
305
}
6,568✔
306

307
Direction SurfaceZPlane::normal(Position r) const
6,568✔
308
{
6,568✔
309
  return {0., 0., 1.};
6,568✔
310
}
6,568✔
311

312
void SurfaceZPlane::to_hdf5_inner(hid_t group_id) const
264✔
313
{
314
  write_string(group_id, "type", "z-plane", false);
264✔
315
  array<double, 1> coeffs {{z0_}};
132✔
316
  write_dataset(group_id, "coefficients", coeffs);
317
}
132✔
318

319
BoundingBox SurfaceZPlane::bounding_box(bool pos_side) const
320
{
321
  if (pos_side) {
322
    return {-INFTY, INFTY, -INFTY, INFTY, z0_, INFTY};
323
  } else {
324
    return {-INFTY, INFTY, -INFTY, INFTY, -INFTY, z0_};
325
  }
6,322✔
326
}
327

6,322✔
328
//==============================================================================
6,322✔
329
// SurfacePlane implementation
330
//==============================================================================
534,981,911✔
331

332
SurfacePlane::SurfacePlane(pugi::xml_node surf_node) : Surface(surf_node)
534,981,911✔
333
{
334
  read_coeffs(surf_node, id_, {&A_, &B_, &C_, &D_});
335
}
2,039,315,374✔
336

337
double SurfacePlane::evaluate(Position r) const
2,039,315,374✔
338
{
339
  return A_ * r.x + B_ * r.y + C_ * r.z - D_;
340
}
71,473,573✔
341

342
double SurfacePlane::distance(Position r, Direction u, bool coincident) const
71,473,573✔
343
{
344
  const double f = A_ * r.x + B_ * r.y + C_ * r.z - D_;
345
  const double projection = A_ * u.x + B_ * u.y + C_ * u.z;
5,436✔
346
  if (coincident || std::abs(f) < FP_COINCIDENT || projection == 0.0) {
347
    return INFTY;
5,436✔
348
  } else {
5,436✔
349
    const double d = -f / projection;
5,436✔
350
    if (d < 0.0)
5,436✔
351
      return INFTY;
352
    return d;
×
353
  }
354
}
×
355

×
356
Direction SurfacePlane::normal(Position r) const
357
{
×
358
  return {A_, B_, C_};
359
}
360

361
void SurfacePlane::to_hdf5_inner(hid_t group_id) const
362
{
363
  write_string(group_id, "type", "plane", false);
364
  array<double, 4> coeffs {{A_, B_, C_, D_}};
365
  write_dataset(group_id, "coefficients", coeffs);
1,522✔
366
}
367

1,522✔
368
//==============================================================================
1,522✔
369
// Generic functions for x-, y-, and z-, cylinders
370
//==============================================================================
212,951,229✔
371

372
// The template parameters indicate the axes perpendicular to the axis of the
212,951,229✔
373
// cylinder.  offset1 and offset2 should correspond with i1 and i2,
374
// respectively.
375
template<int i1, int i2>
619,562,704✔
376
double axis_aligned_cylinder_evaluate(
377
  Position r, double offset1, double offset2, double radius)
619,562,704✔
378
{
619,562,704✔
379
  const double r1 = r.get<i1>() - offset1;
619,562,704✔
380
  const double r2 = r.get<i2>() - offset2;
37,368,562✔
381
  return r1 * r1 + r2 * r2 - radius * radius;
382
}
582,194,142✔
383

582,194,142✔
384
// The first template parameter indicates which axis the cylinder is aligned to.
272,334,758✔
385
// The other two parameters indicate the other two axes.  offset1 and offset2
309,859,384✔
386
// should correspond with i2 and i3, respectively.
387
template<int i1, int i2, int i3>
388
double axis_aligned_cylinder_distance(Position r, Direction u, bool coincident,
389
  double offset1, double offset2, double radius)
6,390,737✔
390
{
391
  const double a = 1.0 - u.get<i1>() * u.get<i1>(); // u^2 + v^2
6,390,737✔
392
  if (a == 0.0)
393
    return INFTY;
394

1,084✔
395
  const double r2 = r.get<i2>() - offset1;
396
  const double r3 = r.get<i3>() - offset2;
1,084✔
397
  const double k = r2 * u.get<i2>() + r3 * u.get<i3>();
1,084✔
398
  const double c = r2 * r2 + r3 * r3 - radius * radius;
1,084✔
399
  const double quad = k * k - a * c;
1,084✔
400

401
  if (quad < 0.0) {
402
    // No intersection with cylinder.
403
    return INFTY;
404

405
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
406
    // Particle is on the cylinder, thus one distance is positive/negative
407
    // and the other is zero. The sign of k determines if we are facing in or
408
    // out.
409
    if (k >= 0.0) {
1,604,316,615✔
410
      return INFTY;
411
    } else {
412
      return (-k + sqrt(quad)) / a;
1,604,316,615✔
413
    }
1,604,316,615✔
414

1,604,316,615✔
415
  } else if (c < 0.0) {
416
    // Particle is inside the cylinder, thus one distance must be negative
1,602,820,937✔
417
    // and one must be positive. The positive distance will be the one with
418
    // negative sign on sqrt(quad).
419
    return (-k + sqrt(quad)) / a;
1,602,820,937✔
420

1,602,820,937✔
421
  } else {
1,602,820,937✔
422
    // Particle is outside the cylinder, thus both distances are either
423
    // positive or negative. If positive, the smaller distance is the one
×
424
    // with positive sign on sqrt(quad).
425
    const double d = (-k - sqrt(quad)) / a;
426
    if (d < 0.0)
×
427
      return INFTY;
×
428
    return d;
×
429
  }
430
}
1,495,678✔
431

432
// The first template parameter indicates which axis the cylinder is aligned to.
433
// The other two parameters indicate the other two axes.  offset1 and offset2
1,495,678✔
434
// should correspond with i2 and i3, respectively.
1,495,678✔
435
template<int i1, int i2, int i3>
1,495,678✔
436
Direction axis_aligned_cylinder_normal(
437
  Position r, double offset1, double offset2)
438
{
439
  Direction u;
440
  u.get<i2>() = 2.0 * (r.get<i2>() - offset1);
441
  u.get<i3>() = 2.0 * (r.get<i3>() - offset2);
442
  u.get<i1>() = 0.0;
1,845,176,139✔
443
  return u;
444
}
445

1,845,176,139✔
446
//==============================================================================
1,845,176,139✔
447
// SurfaceXCylinder implementation
659,892✔
448
//==============================================================================
449

1,844,516,247✔
450
SurfaceXCylinder::SurfaceXCylinder(pugi::xml_node surf_node)
1,844,516,247✔
451
  : Surface(surf_node)
1,844,516,247✔
452
{
1,844,516,247✔
453
  read_coeffs(surf_node, id_, {&y0_, &z0_, &radius_});
1,844,516,247✔
454
}
455

1,844,516,247✔
456
double SurfaceXCylinder::evaluate(Position r) const
457
{
298,974,099✔
458
  return axis_aligned_cylinder_evaluate<1, 2>(r, y0_, z0_, radius_);
459
}
1,545,542,148✔
460

461
double SurfaceXCylinder::distance(
462
  Position r, Direction u, bool coincident) const
463
{
689,755,765✔
464
  return axis_aligned_cylinder_distance<0, 1, 2>(
346,167,112✔
465
    r, u, coincident, y0_, z0_, radius_);
466
}
343,588,653✔
467

468
Direction SurfaceXCylinder::normal(Position r) const
469
{
855,786,383✔
470
  return axis_aligned_cylinder_normal<0, 1, 2>(r, y0_, z0_);
471
}
472

473
void SurfaceXCylinder::to_hdf5_inner(hid_t group_id) const
360,749,045✔
474
{
475
  write_string(group_id, "type", "x-cylinder", false);
476
  array<double, 3> coeffs {{y0_, z0_, radius_}};
477
  write_dataset(group_id, "coefficients", coeffs);
478
}
479

495,037,338✔
480
BoundingBox SurfaceXCylinder::bounding_box(bool pos_side) const
495,037,338✔
481
{
72,673,622✔
482
  if (!pos_side) {
422,363,716✔
483
    return {-INFTY, INFTY, y0_ - radius_, y0_ + radius_, z0_ - radius_,
484
      z0_ + radius_};
485
  } else {
1,843,988,907✔
486
    return {};
487
  }
488
}
1,843,988,907✔
489
//==============================================================================
1,843,988,907✔
490
// SurfaceYCylinder implementation
179,892✔
491
//==============================================================================
492

1,843,809,015✔
493
SurfaceYCylinder::SurfaceYCylinder(pugi::xml_node surf_node)
1,843,809,015✔
494
  : Surface(surf_node)
1,843,809,015✔
495
{
1,843,809,015✔
496
  read_coeffs(surf_node, id_, {&x0_, &z0_, &radius_});
1,843,809,015✔
497
}
498

1,843,809,015✔
499
double SurfaceYCylinder::evaluate(Position r) const
500
{
298,974,099✔
501
  return axis_aligned_cylinder_evaluate<0, 2>(r, x0_, z0_, radius_);
502
}
1,544,834,916✔
503

504
double SurfaceYCylinder::distance(
505
  Position r, Direction u, bool coincident) const
506
{
689,755,765✔
507
  return axis_aligned_cylinder_distance<1, 0, 2>(
346,167,112✔
508
    r, u, coincident, x0_, z0_, radius_);
509
}
343,588,653✔
510

511
Direction SurfaceYCylinder::normal(Position r) const
512
{
855,079,151✔
513
  return axis_aligned_cylinder_normal<1, 0, 2>(r, x0_, z0_);
514
}
515

516
void SurfaceYCylinder::to_hdf5_inner(hid_t group_id) const
360,041,813✔
517
{
518
  write_string(group_id, "type", "y-cylinder", false);
519
  array<double, 3> coeffs {{x0_, z0_, radius_}};
520
  write_dataset(group_id, "coefficients", coeffs);
521
}
522

495,037,338✔
523
BoundingBox SurfaceYCylinder::bounding_box(bool pos_side) const
495,037,338✔
524
{
72,673,622✔
525
  if (!pos_side) {
422,363,716✔
526
    return {x0_ - radius_, x0_ + radius_, -INFTY, INFTY, z0_ - radius_,
527
      z0_ + radius_};
528
  } else {
×
529
    return {};
530
  }
531
}
×
532

×
533
//==============================================================================
×
534
// SurfaceZCylinder implementation
535
//==============================================================================
×
536

×
537
SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node)
×
NEW
538
  : Surface(surf_node)
×
539
{
×
540
  read_coeffs(surf_node, id_, {&x0_, &y0_, &radius_});
541
}
×
542

543
double SurfaceZCylinder::evaluate(Position r) const
×
544
{
545
  return axis_aligned_cylinder_evaluate<0, 1>(r, x0_, y0_, radius_);
×
546
}
547

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

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

×
560
void SurfaceZCylinder::to_hdf5_inner(hid_t group_id) const
561
{
562
  write_string(group_id, "type", "z-cylinder", false);
563
  array<double, 3> coeffs {{x0_, y0_, radius_}};
564
  write_dataset(group_id, "coefficients", coeffs);
565
}
×
566

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

480,000✔
577
//==============================================================================
578
// SurfaceSphere implementation
707,232✔
579
//==============================================================================
707,232✔
580

707,232✔
581
SurfaceSphere::SurfaceSphere(pugi::xml_node surf_node) : Surface(surf_node)
707,232✔
582
{
707,232✔
583
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_});
584
}
707,232✔
585

586
double SurfaceSphere::evaluate(Position r) const
×
587
{
588
  const double x = r.x - x0_;
707,232✔
589
  const double y = r.y - y0_;
590
  const double z = r.z - z0_;
591
  return x * x + y * y + z * z - radius_ * radius_;
592
}
×
593

×
594
double SurfaceSphere::distance(Position r, Direction u, bool coincident) const
595
{
×
596
  const double x = r.x - x0_;
597
  const double y = r.y - y0_;
598
  const double z = r.z - z0_;
707,232✔
599
  const double k = x * u.x + y * u.y + z * u.z;
600
  const double c = x * x + y * y + z * z - radius_ * radius_;
601
  const double quad = k * k - c;
602

707,232✔
603
  if (quad < 0.0) {
604
    // No intersection with sphere.
605
    return INFTY;
606

607
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
608
    // Particle is on the sphere, thus one distance is positive/negative and
×
609
    // the other is zero. The sign of k determines if we are facing in or out.
×
610
    if (k >= 0.0) {
×
611
      return INFTY;
×
612
    } else {
613
      return -k + sqrt(quad);
614
    }
615

616
  } else if (c < 0.0) {
617
    // Particle is inside the sphere, thus one distance must be negative and
618
    // one must be positive. The positive distance will be the one with
619
    // negative sign on sqrt(quad)
1,519,490✔
620
    return -k + sqrt(quad);
621

622
  } else {
1,519,490✔
623
    // Particle is outside the sphere, thus both distances are either positive
1,519,490✔
624
    // or negative. If positive, the smaller distance is the one with positive
1,519,490✔
625
    // sign on sqrt(quad).
1,519,490✔
626
    const double d = -k - sqrt(quad);
1,519,490✔
627
    if (d < 0.0)
628
      return INFTY;
1,519,490✔
629
    return d;
630
  }
631
}
1,519,490✔
632

1,519,490✔
633
Direction SurfaceSphere::normal(Position r) const
1,519,490✔
634
{
1,519,490✔
635
  return {2.0 * (r.x - x0_), 2.0 * (r.y - y0_), 2.0 * (r.z - z0_)};
1,519,490✔
636
}
637

×
638
void SurfaceSphere::to_hdf5_inner(hid_t group_id) const
639
{
640
  write_string(group_id, "type", "sphere", false);
×
641
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_}};
×
642
  write_dataset(group_id, "coefficients", coeffs);
×
643
}
×
644

×
645
BoundingBox SurfaceSphere::bounding_box(bool pos_side) const
646
{
×
647
  if (!pos_side) {
648
    return {x0_ - radius_, x0_ + radius_, y0_ - radius_, y0_ + radius_,
649
      z0_ - radius_, z0_ + radius_};
×
650
  } else {
×
651
    return {};
×
652
  }
×
653
}
×
654

655
//==============================================================================
656
// Generic functions for x-, y-, and z-, cones
657
//==============================================================================
658

659
// The first template parameter indicates which axis the cone is aligned to.
660
// The other two parameters indicate the other two axes.  offset1, offset2,
34✔
661
// and offset3 should correspond with i1, i2, and i3, respectively.
34✔
662
template<int i1, int i2, int i3>
663
double axis_aligned_cone_evaluate(
34✔
664
  Position r, double offset1, double offset2, double offset3, double radius_sq)
34✔
665
{
666
  const double r1 = r.get<i1>() - offset1;
1,495,678✔
667
  const double r2 = r.get<i2>() - offset2;
668
  const double r3 = r.get<i3>() - offset3;
1,495,678✔
669
  return r2 * r2 + r3 * r3 - radius_sq * r1 * r1;
670
}
671

1,187,232✔
672
// The first template parameter indicates which axis the cone is aligned to.
673
// The other two parameters indicate the other two axes.  offset1, offset2,
674
// and offset3 should correspond with i1, i2, and i3, respectively.
1,187,232✔
675
template<int i1, int i2, int i3>
1,187,232✔
676
double axis_aligned_cone_distance(Position r, Direction u, bool coincident,
677
  double offset1, double offset2, double offset3, double radius_sq)
678
{
×
679
  const double r1 = r.get<i1>() - offset1;
680
  const double r2 = r.get<i2>() - offset2;
×
681
  const double r3 = r.get<i3>() - offset3;
682
  const double a = u.get<i2>() * u.get<i2>() + u.get<i3>() * u.get<i3>() -
683
                   radius_sq * u.get<i1>() * u.get<i1>();
24✔
684
  const double k =
685
    r2 * u.get<i2>() + r3 * u.get<i3>() - radius_sq * r1 * u.get<i1>();
24✔
686
  const double c = r2 * r2 + r3 * r3 - radius_sq * r1 * r1;
24✔
687
  double quad = k * k - a * c;
24✔
688

24✔
689
  double d;
690

×
691
  if (quad < 0.0) {
692
    // No intersection with cone.
×
693
    return INFTY;
×
694

×
695
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
696
    // Particle is on the cone, thus one distance is positive/negative
×
697
    // and the other is zero. The sign of k determines if we are facing in or
698
    // out.
699
    if (k >= 0.0) {
700
      d = (-k - sqrt(quad)) / a;
701
    } else {
702
      d = (-k + sqrt(quad)) / a;
703
    }
×
704

×
705
  } else {
706
    // Calculate both solutions to the quadratic.
×
707
    quad = sqrt(quad);
708
    d = (-k - quad) / a;
709
    const double b = (-k + quad) / a;
×
710

711
    // Determine the smallest positive solution.
×
712
    if (d < 0.0) {
713
      if (b > 0.0)
714
        d = b;
×
715
    } else {
716
      if (b > 0.0) {
717
        if (b < d)
×
718
          d = b;
×
719
      }
720
    }
721
  }
×
722

723
  // If the distance was negative, set boundary distance to infinity.
×
724
  if (d <= 0.0)
725
    return INFTY;
726
  return d;
×
727
}
728

×
729
// The first template parameter indicates which axis the cone is aligned to.
×
730
// The other two parameters indicate the other two axes.  offset1, offset2,
×
731
// and offset3 should correspond with i1, i2, and i3, respectively.
732
template<int i1, int i2, int i3>
733
Direction axis_aligned_cone_normal(
×
734
  Position r, double offset1, double offset2, double offset3, double radius_sq)
735
{
×
736
  Direction u;
×
737
  u.get<i1>() = -2.0 * radius_sq * (r.get<i1>() - offset1);
×
738
  u.get<i2>() = 2.0 * (r.get<i2>() - offset2);
739
  u.get<i3>() = 2.0 * (r.get<i3>() - offset3);
×
740
  return u;
741
}
742

743
//==============================================================================
744
// SurfaceXCone implementation
745
//==============================================================================
746

747
SurfaceXCone::SurfaceXCone(pugi::xml_node surf_node) : Surface(surf_node)
4,736✔
748
{
4,736✔
749
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
750
}
4,736✔
751

4,736✔
752
double SurfaceXCone::evaluate(Position r) const
753
{
1,602,820,937✔
754
  return axis_aligned_cone_evaluate<0, 1, 2>(r, x0_, y0_, z0_, radius_sq_);
755
}
1,602,820,937✔
756

757
double SurfaceXCone::distance(Position r, Direction u, bool coincident) const
758
{
1,843,988,907✔
759
  return axis_aligned_cone_distance<0, 1, 2>(
760
    r, u, coincident, x0_, y0_, z0_, radius_sq_);
761
}
1,843,988,907✔
762

1,843,988,907✔
763
Direction SurfaceXCone::normal(Position r) const
764
{
765
  return axis_aligned_cone_normal<0, 1, 2>(r, x0_, y0_, z0_, radius_sq_);
1,519,490✔
766
}
767

1,519,490✔
768
void SurfaceXCone::to_hdf5_inner(hid_t group_id) const
769
{
770
  write_string(group_id, "type", "x-cone", false);
3,521✔
771
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
772
  write_dataset(group_id, "coefficients", coeffs);
3,521✔
773
}
3,521✔
774

3,521✔
775
//==============================================================================
3,521✔
776
// SurfaceYCone implementation
777
//==============================================================================
48✔
778

779
SurfaceYCone::SurfaceYCone(pugi::xml_node surf_node) : Surface(surf_node)
48✔
780
{
24✔
781
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
24✔
782
}
783

24✔
784
double SurfaceYCone::evaluate(Position r) const
785
{
786
  return axis_aligned_cone_evaluate<1, 0, 2>(r, y0_, x0_, z0_, radius_sq_);
787
}
788

789
double SurfaceYCone::distance(Position r, Direction u, bool coincident) const
790
{
791
  return axis_aligned_cone_distance<1, 0, 2>(
8,145✔
792
    r, u, coincident, y0_, x0_, z0_, radius_sq_);
793
}
8,145✔
794

8,145✔
795
Direction SurfaceYCone::normal(Position r) const
796
{
520,718,752✔
797
  return axis_aligned_cone_normal<1, 0, 2>(r, y0_, x0_, z0_, radius_sq_);
798
}
520,718,752✔
799

520,718,752✔
800
void SurfaceYCone::to_hdf5_inner(hid_t group_id) const
520,718,752✔
801
{
520,718,752✔
802
  write_string(group_id, "type", "y-cone", false);
803
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
804
  write_dataset(group_id, "coefficients", coeffs);
625,086,923✔
805
}
806

625,086,923✔
807
//==============================================================================
625,086,923✔
808
// SurfaceZCone implementation
625,086,923✔
809
//==============================================================================
625,086,923✔
810

625,086,923✔
811
SurfaceZCone::SurfaceZCone(pugi::xml_node surf_node) : Surface(surf_node)
625,086,923✔
812
{
813
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
625,086,923✔
814
}
815

128,123,009✔
816
double SurfaceZCone::evaluate(Position r) const
817
{
496,963,914✔
818
  return axis_aligned_cone_evaluate<2, 0, 1>(r, z0_, x0_, y0_, radius_sq_);
819
}
820

67,054,602✔
821
double SurfaceZCone::distance(Position r, Direction u, bool coincident) const
30,087,558✔
822
{
823
  return axis_aligned_cone_distance<2, 0, 1>(
36,967,044✔
824
    r, u, coincident, z0_, x0_, y0_, radius_sq_);
825
}
826

429,909,312✔
827
Direction SurfaceZCone::normal(Position r) const
828
{
829
  return axis_aligned_cone_normal<2, 0, 1>(r, z0_, x0_, y0_, radius_sq_);
830
}
388,444,388✔
831

832
void SurfaceZCone::to_hdf5_inner(hid_t group_id) const
833
{
834
  write_string(group_id, "type", "z-cone", false);
835
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
836
  write_dataset(group_id, "coefficients", coeffs);
41,464,924✔
837
}
41,464,924✔
838

10,437,601✔
839
//==============================================================================
31,027,323✔
840
// SurfaceQuadric implementation
841
//==============================================================================
842

843
SurfaceQuadric::SurfaceQuadric(pugi::xml_node surf_node) : Surface(surf_node)
13,253,056✔
844
{
845
  read_coeffs(
13,253,056✔
846
    surf_node, id_, {&A_, &B_, &C_, &D_, &E_, &F_, &G_, &H_, &J_, &K_});
847
}
848

6,411✔
849
double SurfaceQuadric::evaluate(Position r) const
850
{
6,411✔
851
  const double x = r.x;
6,411✔
852
  const double y = r.y;
6,411✔
853
  const double z = r.z;
6,411✔
854
  return x * (A_ * x + D_ * y + G_) + y * (B_ * y + E_ * z + H_) +
855
         z * (C_ * z + F_ * x + J_) + K_;
×
856
}
857

×
858
double SurfaceQuadric::distance(
×
859
  Position r, Direction ang, bool coincident) const
×
860
{
861
  const double& x = r.x;
×
862
  const double& y = r.y;
863
  const double& z = r.z;
864
  const double& u = ang.x;
865
  const double& v = ang.y;
866
  const double& w = ang.z;
867

868
  const double a =
869
    A_ * u * u + B_ * v * v + C_ * w * w + D_ * u * v + E_ * v * w + F_ * u * w;
870
  const double k = A_ * u * x + B_ * v * y + C_ * w * z +
871
                   0.5 * (D_ * (u * y + v * x) + E_ * (v * z + w * y) +
872
                           F_ * (w * x + u * z) + G_ * u + H_ * v + J_ * w);
873
  const double c = A_ * x * x + B_ * y * y + C_ * z * z + D_ * x * y +
351,912✔
874
                   E_ * y * z + F_ * x * z + G_ * x + H_ * y + J_ * z + K_;
875
  double quad = k * k - a * c;
876

351,912✔
877
  double d;
351,912✔
878

351,912✔
879
  if (quad < 0.0) {
351,912✔
880
    // No intersection with surface.
881
    return INFTY;
351,912✔
882

883
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
884
    // Particle is on the surface, thus one distance is positive/negative and
351,912✔
885
    // the other is zero. The sign of k determines which distance is zero and
351,912✔
886
    // which is not. Additionally, if a is zero, it means the particle is on
351,912✔
887
    // a plane-like surface.
351,912✔
888
    if (a == 0.0) {
889
      d = INFTY; // see the below explanation
×
890
    } else if (k >= 0.0) {
891
      d = (-k - sqrt(quad)) / a;
892
    } else {
×
893
      d = (-k + sqrt(quad)) / a;
×
894
    }
×
895

×
896
  } else if (a == 0.0) {
897
    // Given the orientation of the particle, the quadric looks like a plane in
×
898
    // this case, and thus we have only one solution despite potentially having
899
    // quad > 0.0. While the term under the square root may be real, in one
900
    // case of the +/- of the quadratic formula, 0/0 results, and in another, a
×
901
    // finite value over 0 results. Applying L'Hopital's to the 0/0 case gives
×
902
    // the below. Alternatively this can be found by simply putting a=0 in the
×
903
    // equation ax^2 + bx + c = 0.
×
904
    d = -0.5 * c / k;
905
  } else {
906
    // Calculate both solutions to the quadratic.
907
    quad = sqrt(quad);
908
    d = (-k - quad) / a;
909
    double b = (-k + quad) / a;
910

1,050,936✔
911
    // Determine the smallest positive solution.
912
    if (d < 0.0) {
913
      if (b > 0.0)
1,050,936✔
914
        d = b;
1,050,936✔
915
    } else {
1,050,936✔
916
      if (b > 0.0) {
1,050,936✔
917
        if (b < d)
1,050,936✔
918
          d = b;
1,050,936✔
919
      }
1,050,936✔
920
    }
1,050,936✔
921
  }
1,050,936✔
922

923
  // If the distance was negative, set boundary distance to infinity.
924
  if (d <= 0.0)
925
    return INFTY;
1,050,936✔
926
  return d;
927
}
×
928

929
Direction SurfaceQuadric::normal(Position r) const
1,050,936✔
930
{
931
  const double& x = r.x;
932
  const double& y = r.y;
933
  const double& z = r.z;
66,072✔
934
  return {2.0 * A_ * x + D_ * y + F_ * z + G_,
×
935
    2.0 * B_ * y + D_ * x + E_ * z + H_, 2.0 * C_ * z + E_ * y + F_ * x + J_};
936
}
66,072✔
937

938
void SurfaceQuadric::to_hdf5_inner(hid_t group_id) const
939
{
940
  write_string(group_id, "type", "quadric", false);
941
  array<double, 10> coeffs {{A_, B_, C_, D_, E_, F_, G_, H_, J_, K_}};
984,864✔
942
  write_dataset(group_id, "coefficients", coeffs);
984,864✔
943
}
984,864✔
944

945
//==============================================================================
946
// Torus helper functions
984,864✔
947
//==============================================================================
836,208✔
948

709,644✔
949
double torus_distance(double x1, double x2, double x3, double u1, double u2,
950
  double u3, double A, double B, double C, bool coincident)
148,656✔
951
{
148,656✔
952
  // Coefficients for equation: (c2 t^2 + c1 t + c0)^2 = c2' t^2 + c1' t + c0'
148,656✔
953
  double D = (C * C) / (B * B);
954
  double c2 = u1 * u1 + u2 * u2 + D * u3 * u3;
955
  double c1 = 2 * (u1 * x1 + u2 * x2 + D * u3 * x3);
956
  double c0 = x1 * x1 + x2 * x2 + D * x3 * x3 + A * A - C * C;
957
  double four_A2 = 4 * A * A;
958
  double c2p = four_A2 * (u1 * u1 + u2 * u2);
1,050,936✔
959
  double c1p = 2 * four_A2 * (u1 * x1 + u2 * x2);
149,964✔
960
  double c0p = four_A2 * (x1 * x1 + x2 * x2);
900,972✔
961

962
  // Coefficient for equation: a t^4 + b t^3 + c t^2 + d t + e = 0. If the point
1,050,936✔
963
  // is coincident, the 'e' coefficient should be zero. Explicitly setting it to
964
  // zero helps avoid numerical issues below with root finding.
965
  double coeff[5];
1,050,936✔
966
  coeff[0] = coincident ? 0.0 : c0 * c0 - c0p;
1,050,936✔
967
  coeff[1] = 2 * c0 * c1 - c1p;
1,050,936✔
968
  coeff[2] = c1 * c1 + 2 * c0 * c2 - c2p;
1,050,936✔
969
  coeff[3] = 2 * c1 * c2;
1,050,936✔
970
  coeff[4] = c2 * c2;
1,050,936✔
971

1,050,936✔
972
  std::complex<double> roots[4];
1,050,936✔
973
  oqs::quartic_solver(coeff, roots);
1,050,936✔
974

975
  // Find smallest positive, real root. In the case where the particle is
976
  // coincident with the surface, we are sure to have one root very close to
977
  // zero but possibly small and positive. A tolerance is set to discard that
1,050,936✔
978
  // zero.
979
  double distance = INFTY;
×
980
  double cutoff = coincident ? TORUS_TOL : 0.0;
981
  for (int i = 0; i < 4; ++i) {
1,050,936✔
982
    if (roots[i].imag() == 0) {
983
      double root = roots[i].real();
984
      if (root > cutoff && root < distance) {
985
        // Avoid roots corresponding to internal surfaces
66,072✔
986
        double s1 = x1 + u1 * root;
×
987
        double s2 = x2 + u2 * root;
988
        double s3 = x3 + u3 * root;
66,072✔
989
        double check = D * s3 * s3 + s1 * s1 + s2 * s2 + A * A - C * C;
990
        if (check >= 0) {
991
          distance = root;
992
        }
993
      }
984,864✔
994
    }
984,864✔
995
  }
984,864✔
996
  return distance;
997
}
998

984,864✔
999
//==============================================================================
836,208✔
1000
// SurfaceXTorus implementation
709,644✔
1001
//==============================================================================
1002

148,656✔
1003
SurfaceXTorus::SurfaceXTorus(pugi::xml_node surf_node) : Surface(surf_node)
148,656✔
1004
{
148,656✔
1005
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1006
}
1007

1008
void SurfaceXTorus::to_hdf5_inner(hid_t group_id) const
1009
{
1010
  write_string(group_id, "type", "x-torus", false);
1,050,936✔
1011
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
149,964✔
1012
  write_dataset(group_id, "coefficients", coeffs);
900,972✔
1013
}
1014

×
1015
double SurfaceXTorus::evaluate(Position r) const
1016
{
1017
  double x = r.x - x0_;
×
1018
  double y = r.y - y0_;
×
1019
  double z = r.z - z0_;
×
1020
  return (x * x) / (B_ * B_) +
×
1021
         std::pow(std::sqrt(y * y + z * z) - A_, 2) / (C_ * C_) - 1.;
×
1022
}
×
1023

×
1024
double SurfaceXTorus::distance(Position r, Direction u, bool coincident) const
×
1025
{
×
1026
  double x = r.x - x0_;
1027
  double y = r.y - y0_;
1028
  double z = r.z - z0_;
1029
  return torus_distance(y, z, x, u.y, u.z, u.x, A_, B_, C_, coincident);
×
1030
}
1031

×
1032
Direction SurfaceXTorus::normal(Position r) const
1033
{
×
1034
  // reduce the expansion of the full form for torus
1035
  double x = r.x - x0_;
1036
  double y = r.y - y0_;
1037
  double z = r.z - z0_;
×
1038

×
1039
  // f(x,y,z) = x^2/B^2 + (sqrt(y^2 + z^2) - A)^2/C^2 - 1
1040
  // ∂f/∂x = 2x/B^2
×
1041
  // ∂f/∂y = 2y(g - A)/(g*C^2) where g = sqrt(y^2 + z^2)
1042
  // ∂f/∂z = 2z(g - A)/(g*C^2)
1043
  // Multiplying by g*C^2*B^2 / 2 gives:
1044
  double g = std::sqrt(y * y + z * z);
1045
  double nx = C_ * C_ * g * x;
×
1046
  double ny = y * (g - A_) * B_ * B_;
×
1047
  double nz = z * (g - A_) * B_ * B_;
×
1048
  Direction n(nx, ny, nz);
1049
  return n / n.norm();
1050
}
×
1051

×
1052
//==============================================================================
×
1053
// SurfaceYTorus implementation
1054
//==============================================================================
×
1055

×
NEW
1056
SurfaceYTorus::SurfaceYTorus(pugi::xml_node surf_node) : Surface(surf_node)
×
1057
{
1058
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1059
}
1060

1061
void SurfaceYTorus::to_hdf5_inner(hid_t group_id) const
1062
{
×
1063
  write_string(group_id, "type", "y-torus", false);
×
1064
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
×
1065
  write_dataset(group_id, "coefficients", coeffs);
1066
}
×
1067

1068
double SurfaceYTorus::evaluate(Position r) const
1069
{
×
1070
  double x = r.x - x0_;
×
1071
  double y = r.y - y0_;
×
1072
  double z = r.z - z0_;
×
1073
  return (y * y) / (B_ * B_) +
×
1074
         std::pow(std::sqrt(x * x + z * z) - A_, 2) / (C_ * C_) - 1.;
×
1075
}
×
1076

×
1077
double SurfaceYTorus::distance(Position r, Direction u, bool coincident) const
×
1078
{
1079
  double x = r.x - x0_;
1080
  double y = r.y - y0_;
1081
  double z = r.z - z0_;
×
1082
  return torus_distance(x, z, y, u.x, u.z, u.y, A_, B_, C_, coincident);
1083
}
×
1084

1085
Direction SurfaceYTorus::normal(Position r) const
×
1086
{
1087
  // reduce the expansion of the full form for torus
1088
  double x = r.x - x0_;
1089
  double y = r.y - y0_;
×
1090
  double z = r.z - z0_;
×
1091

1092
  // f(x,y,z) = y^2/B^2 + (sqrt(x^2 + z^2) - A)^2/C^2 - 1
×
1093
  // ∂f/∂x = 2x(g - A)/(g*C^2) where g = sqrt(x^2 + z^2)
1094
  // ∂f/∂y = 2y/B^2
1095
  // ∂f/∂z = 2z(g - A)/(g*C^2)
1096
  // Multiplying by g*C^2*B^2 / 2 gives:
1097
  double g = std::sqrt(x * x + z * z);
×
1098
  double nx = x * (g - A_) * B_ * B_;
×
1099
  double ny = C_ * C_ * g * y;
×
1100
  double nz = z * (g - A_) * B_ * B_;
1101
  Direction n(nx, ny, nz);
1102
  return n / n.norm();
×
1103
}
×
1104

×
1105
//==============================================================================
1106
// SurfaceZTorus implementation
×
1107
//==============================================================================
×
1108

×
1109
SurfaceZTorus::SurfaceZTorus(pugi::xml_node surf_node) : Surface(surf_node)
1110
{
1111
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1112
}
1113

1114
void SurfaceZTorus::to_hdf5_inner(hid_t group_id) const
×
1115
{
×
1116
  write_string(group_id, "type", "z-torus", false);
×
1117
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
1118
  write_dataset(group_id, "coefficients", coeffs);
1119
}
1120

1121
double SurfaceZTorus::evaluate(Position r) const
1122
{
1123
  double x = r.x - x0_;
66,072✔
1124
  double y = r.y - y0_;
1125
  double z = r.z - z0_;
1126
  return (z * z) / (B_ * B_) +
66,072✔
1127
         std::pow(std::sqrt(x * x + y * y) - A_, 2) / (C_ * C_) - 1.;
66,072✔
1128
}
66,072✔
1129

66,072✔
1130
double SurfaceZTorus::distance(Position r, Direction u, bool coincident) const
66,072✔
1131
{
1132
  double x = r.x - x0_;
66,072✔
1133
  double y = r.y - y0_;
1134
  double z = r.z - z0_;
1135
  return torus_distance(x, y, z, u.x, u.y, u.z, A_, B_, C_, coincident);
66,072✔
1136
}
66,072✔
1137

66,072✔
1138
Direction SurfaceZTorus::normal(Position r) const
66,072✔
1139
{
66,072✔
1140
  // reduce the expansion of the full form for torus
1141
  double x = r.x - x0_;
×
1142
  double y = r.y - y0_;
1143
  double z = r.z - z0_;
1144

×
1145
  // f(x,y,z) = z^2/B^2 + (sqrt(x^2 + y^2) - A)^2/C^2 - 1
×
1146
  // ∂f/∂x = 2x(g - A)/(g*C^2) where g = sqrt(x^2 + y^2)
×
1147
  // ∂f/∂y = 2y(g - A)/(g*C^2)
×
1148
  // ∂f/∂z = 2z/B^2
×
1149
  // Multiplying by g*C^2*B^2 / 2 gives:
1150
  double g = std::sqrt(x * x + y * y);
×
1151
  double nx = x * (g - A_) * B_ * B_;
1152
  double ny = y * (g - A_) * B_ * B_;
1153
  double nz = C_ * C_ * g * z;
×
1154
  Position n(nx, ny, nz);
×
1155
  return n / n.norm();
×
1156
}
×
1157

×
1158
//==============================================================================
1159

1160
void read_surfaces(pugi::xml_node node)
1161
{
1162
  // Count the number of surfaces
1163
  int n_surfaces = 0;
1164
  for (pugi::xml_node surf_node : node.children("surface")) {
×
1165
    n_surfaces++;
1166
  }
×
1167

1168
  // Loop over XML surface elements and populate the array.  Keep track of
1169
  // periodic surfaces and their albedos.
×
1170
  model::surfaces.reserve(n_surfaces);
1171
  std::set<std::pair<int, int>> periodic_pairs;
×
1172
  std::unordered_map<int, double> albedo_map;
1173
  {
1174
    pugi::xml_node surf_node;
×
1175
    int i_surf;
1176
    for (surf_node = node.child("surface"), i_surf = 0; surf_node;
×
1177
         surf_node = surf_node.next_sibling("surface"), i_surf++) {
×
1178
      std::string surf_type = get_node_value(surf_node, "type", true, true);
1179

1180
      // Allocate and initialize the new surface
×
1181

1182
      if (surf_type == "x-plane") {
×
1183
        model::surfaces.push_back(make_unique<SurfaceXPlane>(surf_node));
1184

1185
      } else if (surf_type == "y-plane") {
×
1186
        model::surfaces.push_back(make_unique<SurfaceYPlane>(surf_node));
1187

×
1188
      } else if (surf_type == "z-plane") {
×
1189
        model::surfaces.push_back(make_unique<SurfaceZPlane>(surf_node));
×
1190

1191
      } else if (surf_type == "plane") {
1192
        model::surfaces.push_back(make_unique<SurfacePlane>(surf_node));
1193

1194
      } else if (surf_type == "x-cylinder") {
1195
        model::surfaces.push_back(make_unique<SurfaceXCylinder>(surf_node));
1196

×
1197
      } else if (surf_type == "y-cylinder") {
1198
        model::surfaces.push_back(make_unique<SurfaceYCylinder>(surf_node));
×
1199

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

1203
      } else if (surf_type == "sphere") {
×
1204
        model::surfaces.push_back(make_unique<SurfaceSphere>(surf_node));
1205

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

×
1209
      } else if (surf_type == "y-cone") {
×
1210
        model::surfaces.push_back(make_unique<SurfaceYCone>(surf_node));
1211

1212
      } else if (surf_type == "z-cone") {
×
1213
        model::surfaces.push_back(make_unique<SurfaceZCone>(surf_node));
1214

×
1215
      } else if (surf_type == "quadric") {
1216
        model::surfaces.push_back(make_unique<SurfaceQuadric>(surf_node));
1217

×
1218
      } else if (surf_type == "x-torus") {
1219
        model::surfaces.push_back(std::make_unique<SurfaceXTorus>(surf_node));
×
1220

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

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

1227
      } else {
1228
        fatal_error(fmt::format("Invalid surface type, \"{}\"", surf_type));
17✔
1229
      }
1230

17✔
1231
      // Check for a periodic surface
17✔
1232
      if (check_for_node(surf_node, "boundary")) {
1233
        std::string surf_bc = get_node_value(surf_node, "boundary", true, true);
351,912✔
1234
        if (surf_bc == "periodic") {
1235
          // Check for surface albedo. Skip sanity check as it is already done
351,912✔
1236
          // in the Surface class's constructor.
1237
          if (check_for_node(surf_node, "albedo")) {
1238
            albedo_map[model::surfaces.back()->id_] =
1,050,936✔
1239
              std::stod(get_node_value(surf_node, "albedo"));
1240
          }
1,050,936✔
1241
          if (check_for_node(surf_node, "periodic_surface_id")) {
1,050,936✔
1242
            int i_periodic =
1243
              std::stoi(get_node_value(surf_node, "periodic_surface_id"));
1244
            int lo_id = std::min(model::surfaces.back()->id_, i_periodic);
66,072✔
1245
            int hi_id = std::max(model::surfaces.back()->id_, i_periodic);
1246
            periodic_pairs.insert({lo_id, hi_id});
66,072✔
1247
          } else {
1248
            periodic_pairs.insert({model::surfaces.back()->id_, -1});
1249
          }
12✔
1250
        }
1251
      }
12✔
1252
    }
12✔
1253
  }
12✔
1254

12✔
1255
  // Fill the surface map
1256
  for (int i_surf = 0; i_surf < model::surfaces.size(); i_surf++) {
1257
    int id = model::surfaces[i_surf]->id_;
1258
    auto in_map = model::surface_map.find(id);
1259
    if (in_map == model::surface_map.end()) {
1260
      model::surface_map[id] = i_surf;
17✔
1261
    } else {
1262
      fatal_error(
×
1263
        fmt::format("Two or more surfaces use the same unique ID: {}", id));
17✔
1264
    }
17✔
1265
  }
1266

191,344✔
1267
  // Resolve unpaired periodic surfaces.  A lambda function is used with
1268
  // std::find_if to identify the unpaired surfaces.
191,344✔
1269
  auto is_unresolved_pair = [](const std::pair<int, int> p) {
191,344✔
1270
    return p.second == -1;
191,344✔
1271
  };
191,344✔
1272
  auto first_unresolved = std::find_if(
191,344✔
1273
    periodic_pairs.begin(), periodic_pairs.end(), is_unresolved_pair);
1274
  if (first_unresolved != periodic_pairs.end()) {
1275
    // Found one unpaired surface; search for a second one
340,920✔
1276
    auto next_elem = first_unresolved;
1277
    next_elem++;
1278
    auto second_unresolved =
340,920✔
1279
      std::find_if(next_elem, periodic_pairs.end(), is_unresolved_pair);
340,920✔
1280
    if (second_unresolved == periodic_pairs.end()) {
340,920✔
1281
      fatal_error("Found only one periodic surface without a specified partner."
340,920✔
1282
                  " Please specify the partner for each periodic surface.");
340,920✔
1283
    }
340,920✔
1284

1285
    // Make sure there isn't a third unpaired surface
340,920✔
1286
    next_elem = second_unresolved;
340,920✔
1287
    next_elem++;
340,920✔
1288
    auto third_unresolved =
340,920✔
1289
      std::find_if(next_elem, periodic_pairs.end(), is_unresolved_pair);
340,920✔
1290
    if (third_unresolved != periodic_pairs.end()) {
340,920✔
1291
      fatal_error(
340,920✔
1292
        "Found at least three periodic surfaces without a specified "
340,920✔
1293
        "partner. Please specify the partner for each periodic surface.");
1294
    }
1295

1296
    // Add the completed pair and remove the old, unpaired entries
340,920✔
1297
    int lo_id = std::min(first_unresolved->first, second_unresolved->first);
1298
    int hi_id = std::max(first_unresolved->first, second_unresolved->first);
×
1299
    periodic_pairs.insert({lo_id, hi_id});
1300
    periodic_pairs.erase(first_unresolved);
340,920✔
1301
    periodic_pairs.erase(second_unresolved);
1302
  }
1303

1304
  // Assign the periodic boundary conditions with albedos
1305
  for (auto periodic_pair : periodic_pairs) {
37,680✔
1306
    int i_surf = model::surface_map[periodic_pair.first];
×
1307
    int j_surf = model::surface_map[periodic_pair.second];
37,680✔
1308
    Surface& surf1 {*model::surfaces[i_surf]};
×
1309
    Surface& surf2 {*model::surfaces[j_surf]};
1310

37,680✔
1311
    // Compute the dot product of the surface normals
1312
    Direction norm1 = surf1.normal({0, 0, 0});
1313
    Direction norm2 = surf2.normal({0, 0, 0});
303,240✔
1314
    norm1 /= norm1.norm();
1315
    norm2 /= norm2.norm();
1316
    double dot_prod = norm1.dot(norm2);
1317

1318
    // If the dot product is 1 (to within floating point precision) then the
1319
    // planes are parallel which indicates a translational periodic boundary
1320
    // condition.  Otherwise, it is a rotational periodic BC.
1321
    if (std::abs(1.0 - dot_prod) < FP_PRECISION) {
×
1322
      surf1.bc_ = make_unique<TranslationalPeriodicBC>(i_surf, j_surf);
1323
      surf2.bc_ = make_unique<TranslationalPeriodicBC>(i_surf, j_surf);
1324
    } else {
303,240✔
1325
      surf1.bc_ = make_unique<RotationalPeriodicBC>(i_surf, j_surf);
303,240✔
1326
      surf2.bc_ = make_unique<RotationalPeriodicBC>(i_surf, j_surf);
303,240✔
1327
    }
1328

1329
    // If albedo data is present in albedo map, set the boundary albedo.
303,240✔
1330
    if (albedo_map.count(surf1.id_)) {
303,240✔
1331
      surf1.bc_->set_albedo(albedo_map[surf1.id_]);
303,240✔
1332
    }
1333
    if (albedo_map.count(surf2.id_)) {
×
1334
      surf2.bc_->set_albedo(albedo_map[surf2.id_]);
×
1335
    }
×
1336
  }
1337
}
1338

1339
void free_memory_surfaces()
1340
{
1341
  model::surfaces.clear();
340,920✔
1342
  model::surface_map.clear();
×
1343
}
340,920✔
1344

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

© 2025 Coveralls, Inc