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

openmc-dev / openmc / 12735804991

12 Jan 2025 05:53PM UTC coverage: 82.581% (-2.3%) from 84.868%
12735804991

Pull #3087

github

web-flow
Merge e7a8165f6 into d2edf0ce4
Pull Request #3087: wheel building with scikit build core

106989 of 129556 relevant lines covered (82.58%)

12046402.28 hits per line

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

70.24
/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(
30,256✔
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");
30,256✔
43
  if (coeffs_file.size() != coeffs.size()) {
30,256✔
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;
30,256✔
51
  for (auto c : coeffs) {
93,522✔
52
    *c = coeffs_file[i++];
63,266✔
53
  }
54
}
30,256✔
55

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

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

62
Surface::Surface(pugi::xml_node surf_node)
30,256✔
63
{
64
  if (check_for_node(surf_node, "id")) {
30,256✔
65
    id_ = std::stoi(get_node_value(surf_node, "id"));
30,256✔
66
    if (contains(settings::source_write_surf_id, id_) ||
59,660✔
67
        settings::source_write_surf_id.empty()) {
29,404✔
68
      surf_source_ = true;
29,149✔
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")) {
30,256✔
75
    name_ = get_node_value(surf_node, "name", false);
6,685✔
76
  }
77

78
  if (check_for_node(surf_node, "boundary")) {
30,256✔
79
    std::string surf_bc = get_node_value(surf_node, "boundary", true, true);
17,333✔
80

81
    if (surf_bc == "transmission" || surf_bc == "transmit" || surf_bc.empty()) {
17,333✔
82
      // Leave the bc_ a nullptr
83
    } else if (surf_bc == "vacuum") {
17,333✔
84
      bc_ = make_unique<VacuumBC>();
9,112✔
85
    } else if (surf_bc == "reflective" || surf_bc == "reflect" ||
8,571✔
86
               surf_bc == "reflecting") {
350✔
87
      bc_ = make_unique<ReflectiveBC>();
7,871✔
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_) {
17,333✔
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
  }
17,333✔
115
}
30,256✔
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;
966,701✔
129
  }
130
  return f > 0.0;
2,147,483,647✔
131
}
132

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

139
  // Reflect direction according to normal.
140
  return u.reflect(n);
788,147,680✔
141
}
142

143
Direction Surface::diffuse_reflect(
1,109,436✔
144
  Position r, Direction u, uint64_t* seed, GeometryState* p) 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
23,004✔
165
{
166
  hid_t surf_group = create_group(group_id, fmt::format("surface {}", id_));
46,008✔
167

168
  if (geom_type() == GeometryType::DAG) {
23,004✔
169
    write_string(surf_group, "geom_type", "dagmc", false);
321✔
170
  } else if (geom_type() == GeometryType::CSG) {
22,683✔
171
    write_string(surf_group, "geom_type", "csg", false);
22,683✔
172

173
    if (bc_) {
22,683✔
174
      write_string(surf_group, "boundary_type", bc_->type(), false);
13,169✔
175
      bc_->to_hdf5(surf_group);
13,169✔
176
    } else {
177
      write_string(surf_group, "boundary_type", "transmission", false);
9,514✔
178
    }
179
  }
180

181
  if (!name_.empty()) {
23,004✔
182
    write_string(surf_group, "name", name_, false);
4,680✔
183
  }
184

185
  to_hdf5_inner(surf_group);
23,004✔
186

187
  close_group(surf_group);
23,004✔
188
}
23,004✔
189

190
CSGSurface::CSGSurface() : Surface {}
×
191
{
192
  geom_type() = GeometryType::CSG;
×
193
};
194
CSGSurface::CSGSurface(pugi::xml_node surf_node) : Surface {surf_node}
30,256✔
195
{
196
  geom_type() = GeometryType::CSG;
30,256✔
197
};
30,256✔
198

199
//==============================================================================
200
// Generic functions for x-, y-, and z-, planes.
201
//==============================================================================
202

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

1,037,126,402✔
217
//==============================================================================
218
// SurfaceXPlane implementation
219
//==============================================================================
1,037,126,402✔
220

1,037,126,402✔
221
SurfaceXPlane::SurfaceXPlane(pugi::xml_node surf_node) : CSGSurface(surf_node)
19,937,436✔
222
{
1,017,188,966✔
223
  read_coeffs(surf_node, id_, {&x0_});
1,017,188,966✔
224
}
501,507,619✔
225

515,681,347✔
226
double SurfaceXPlane::evaluate(Position r) const
227
{
2,147,483,647✔
228
  return r.x - x0_;
229
}
230

2,147,483,647✔
231
double SurfaceXPlane::distance(Position r, Direction u, bool coincident) const
2,147,483,647✔
232
{
190,237,493✔
233
  return axis_aligned_plane_distance<0>(r, u, coincident, x0_);
2,147,483,647✔
234
}
2,147,483,647✔
235

2,147,483,647✔
236
Direction SurfaceXPlane::normal(Position r) const
2,147,483,647✔
237
{
238
  return {1., 0., 0.};
2,147,483,647✔
239
}
240

241
void SurfaceXPlane::to_hdf5_inner(hid_t group_id) const
2,147,483,647✔
242
{
2,147,483,647✔
243
  write_string(group_id, "type", "x-plane", false);
187,573,693✔
244
  array<double, 1> coeffs {{x0_}};
2,147,483,647✔
245
  write_dataset(group_id, "coefficients", coeffs);
2,147,483,647✔
246
}
2,147,483,647✔
247

2,147,483,647✔
248
BoundingBox SurfaceXPlane::bounding_box(bool pos_side) const
249
{
250
  if (pos_side) {
251
    return {x0_, INFTY, -INFTY, INFTY, -INFTY, INFTY};
252
  } else {
253
    return {-INFTY, x0_, -INFTY, INFTY, -INFTY, INFTY};
254
  }
6,883✔
255
}
256

6,883✔
257
//==============================================================================
6,883✔
258
// SurfaceYPlane implementation
259
//==============================================================================
983,755,107✔
260

261
SurfaceYPlane::SurfaceYPlane(pugi::xml_node surf_node) : CSGSurface(surf_node)
983,755,107✔
262
{
263
  read_coeffs(surf_node, id_, {&y0_});
264
}
2,147,483,647✔
265

266
double SurfaceYPlane::evaluate(Position r) const
2,147,483,647✔
267
{
268
  return r.y - y0_;
269
}
189,488,981✔
270

271
double SurfaceYPlane::distance(Position r, Direction u, bool coincident) const
189,488,981✔
272
{
273
  return axis_aligned_plane_distance<1>(r, u, coincident, y0_);
274
}
4,990✔
275

276
Direction SurfaceYPlane::normal(Position r) const
4,990✔
277
{
4,990✔
278
  return {0., 1., 0.};
4,990✔
279
}
4,990✔
280

281
void SurfaceYPlane::to_hdf5_inner(hid_t group_id) const
×
282
{
283
  write_string(group_id, "type", "y-plane", false);
×
284
  array<double, 1> coeffs {{y0_}};
×
285
  write_dataset(group_id, "coefficients", coeffs);
286
}
×
287

288
BoundingBox SurfaceYPlane::bounding_box(bool pos_side) const
289
{
290
  if (pos_side) {
291
    return {-INFTY, INFTY, y0_, INFTY, -INFTY, INFTY};
292
  } else {
293
    return {-INFTY, INFTY, -INFTY, y0_, -INFTY, INFTY};
294
  }
6,108✔
295
}
296

6,108✔
297
//==============================================================================
6,108✔
298
// SurfaceZPlane implementation
299
//==============================================================================
1,002,063,478✔
300

301
SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) : CSGSurface(surf_node)
1,002,063,478✔
302
{
303
  read_coeffs(surf_node, id_, {&z0_});
304
}
2,147,483,647✔
305

306
double SurfaceZPlane::evaluate(Position r) const
2,147,483,647✔
307
{
308
  return r.z - z0_;
309
}
192,470,317✔
310

311
double SurfaceZPlane::distance(Position r, Direction u, bool coincident) const
192,470,317✔
312
{
313
  return axis_aligned_plane_distance<2>(r, u, coincident, z0_);
314
}
4,584✔
315

316
Direction SurfaceZPlane::normal(Position r) const
4,584✔
317
{
4,584✔
318
  return {0., 0., 1.};
4,584✔
319
}
4,584✔
320

321
void SurfaceZPlane::to_hdf5_inner(hid_t group_id) const
×
322
{
323
  write_string(group_id, "type", "z-plane", false);
×
324
  array<double, 1> coeffs {{z0_}};
×
325
  write_dataset(group_id, "coefficients", coeffs);
326
}
×
327

328
BoundingBox SurfaceZPlane::bounding_box(bool pos_side) const
329
{
330
  if (pos_side) {
331
    return {-INFTY, INFTY, -INFTY, INFTY, z0_, INFTY};
332
  } else {
333
    return {-INFTY, INFTY, -INFTY, INFTY, -INFTY, z0_};
334
  }
5,126✔
335
}
336

5,126✔
337
//==============================================================================
5,126✔
338
// SurfacePlane implementation
339
//==============================================================================
299,124,027✔
340

341
SurfacePlane::SurfacePlane(pugi::xml_node surf_node) : CSGSurface(surf_node)
299,124,027✔
342
{
343
  read_coeffs(surf_node, id_, {&A_, &B_, &C_, &D_});
344
}
1,037,126,402✔
345

346
double SurfacePlane::evaluate(Position r) const
1,037,126,402✔
347
{
348
  return A_ * r.x + B_ * r.y + C_ * r.z - D_;
349
}
19,420,118✔
350

351
double SurfacePlane::distance(Position r, Direction u, bool coincident) const
19,420,118✔
352
{
353
  const double f = A_ * r.x + B_ * r.y + C_ * r.z - D_;
354
  const double projection = A_ * u.x + B_ * u.y + C_ * u.z;
4,270✔
355
  if (coincident || std::abs(f) < FP_COINCIDENT || projection == 0.0) {
356
    return INFTY;
4,270✔
357
  } else {
4,270✔
358
    const double d = -f / projection;
4,270✔
359
    if (d < 0.0)
4,270✔
360
      return INFTY;
361
    return d;
×
362
  }
363
}
×
364

×
365
Direction SurfacePlane::normal(Position r) const
366
{
×
367
  return {A_, B_, C_};
368
}
369

370
void SurfacePlane::to_hdf5_inner(hid_t group_id) const
371
{
372
  write_string(group_id, "type", "plane", false);
373
  array<double, 4> coeffs {{A_, B_, C_, D_}};
374
  write_dataset(group_id, "coefficients", coeffs);
1,190✔
375
}
376

1,190✔
377
//==============================================================================
1,190✔
378
// Generic functions for x-, y-, and z-, cylinders
379
//==============================================================================
182,518,670✔
380

381
// The template parameters indicate the axes perpendicular to the axis of the
182,518,670✔
382
// cylinder.  offset1 and offset2 should correspond with i1 and i2,
383
// respectively.
384
template<int i1, int i2>
583,241,186✔
385
double axis_aligned_cylinder_evaluate(
386
  Position r, double offset1, double offset2, double radius)
583,241,186✔
387
{
583,241,186✔
388
  const double r1 = r.get<i1>() - offset1;
583,241,186✔
389
  const double r2 = r.get<i2>() - offset2;
32,473,680✔
390
  return r1 * r1 + r2 * r2 - radius * radius;
391
}
550,767,506✔
392

550,767,506✔
393
// The first template parameter indicates which axis the cylinder is aligned to.
259,069,080✔
394
// The other two parameters indicate the other two axes.  offset1 and offset2
291,698,426✔
395
// should correspond with i2 and i3, respectively.
396
template<int i1, int i2, int i3>
397
double axis_aligned_cylinder_distance(Position r, Direction u, bool coincident,
398
  double offset1, double offset2, double radius)
5,964,826✔
399
{
400
  const double a = 1.0 - u.get<i1>() * u.get<i1>(); // u^2 + v^2
5,964,826✔
401
  if (a == 0.0)
402
    return INFTY;
403

792✔
404
  const double r2 = r.get<i2>() - offset1;
405
  const double r3 = r.get<i3>() - offset2;
792✔
406
  const double k = r2 * u.get<i2>() + r3 * u.get<i3>();
792✔
407
  const double c = r2 * r2 + r3 * r3 - radius * radius;
792✔
408
  const double quad = k * k - a * c;
792✔
409

410
  if (quad < 0.0) {
411
    // No intersection with cylinder.
412
    return INFTY;
413

414
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
415
    // Particle is on the cylinder, thus one distance is positive/negative
416
    // and the other is zero. The sign of k determines if we are facing in or
417
    // out.
418
    if (k >= 0.0) {
843,049,624✔
419
      return INFTY;
420
    } else {
421
      return (-k + sqrt(quad)) / a;
843,049,624✔
422
    }
843,049,624✔
423

843,049,624✔
424
  } else if (c < 0.0) {
425
    // Particle is inside the cylinder, thus one distance must be negative
841,553,945✔
426
    // and one must be positive. The positive distance will be the one with
427
    // negative sign on sqrt(quad).
428
    return (-k + sqrt(quad)) / a;
841,553,945✔
429

841,553,945✔
430
  } else {
841,553,945✔
431
    // Particle is outside the cylinder, thus both distances are either
432
    // positive or negative. If positive, the smaller distance is the one
×
433
    // with positive sign on sqrt(quad).
434
    const double d = (-k - sqrt(quad)) / a;
435
    if (d < 0.0)
×
436
      return INFTY;
×
437
    return d;
×
438
  }
439
}
1,495,679✔
440

441
// The first template parameter indicates which axis the cylinder is aligned to.
442
// The other two parameters indicate the other two axes.  offset1 and offset2
1,495,679✔
443
// should correspond with i2 and i3, respectively.
1,495,679✔
444
template<int i1, int i2, int i3>
1,495,679✔
445
Direction axis_aligned_cylinder_normal(
446
  Position r, double offset1, double offset2)
447
{
448
  Direction u;
449
  u.get<i2>() = 2.0 * (r.get<i2>() - offset1);
450
  u.get<i3>() = 2.0 * (r.get<i3>() - offset2);
451
  u.get<i1>() = 0.0;
780,674,265✔
452
  return u;
453
}
454

780,674,265✔
455
//==============================================================================
780,674,265✔
456
// SurfaceXCylinder implementation
659,892✔
457
//==============================================================================
458

780,014,373✔
459
SurfaceXCylinder::SurfaceXCylinder(pugi::xml_node surf_node)
780,014,373✔
460
  : CSGSurface(surf_node)
780,014,373✔
461
{
780,014,373✔
462
  read_coeffs(surf_node, id_, {&y0_, &z0_, &radius_});
780,014,373✔
463
}
464

780,014,373✔
465
double SurfaceXCylinder::evaluate(Position r) const
466
{
141,465,954✔
467
  return axis_aligned_cylinder_evaluate<1, 2>(r, y0_, z0_, radius_);
468
}
638,548,419✔
469

470
double SurfaceXCylinder::distance(
471
  Position r, Direction u, bool coincident) const
472
{
277,802,039✔
473
  return axis_aligned_cylinder_distance<0, 1, 2>(
139,559,771✔
474
    r, u, coincident, y0_, z0_, radius_);
475
}
138,242,268✔
476

477
Direction SurfaceXCylinder::normal(Position r) const
478
{
360,746,380✔
479
  return axis_aligned_cylinder_normal<0, 1, 2>(r, y0_, z0_);
480
}
481

482
void SurfaceXCylinder::to_hdf5_inner(hid_t group_id) const
151,844,264✔
483
{
484
  write_string(group_id, "type", "x-cylinder", false);
485
  array<double, 3> coeffs {{y0_, z0_, radius_}};
486
  write_dataset(group_id, "coefficients", coeffs);
487
}
488

208,902,116✔
489
BoundingBox SurfaceXCylinder::bounding_box(bool pos_side) const
208,902,116✔
490
{
35,759,654✔
491
  if (!pos_side) {
173,142,462✔
492
    return {-INFTY, INFTY, y0_ - radius_, y0_ + radius_, z0_ - radius_,
493
      z0_ + radius_};
494
  } else {
779,487,033✔
495
    return {};
496
  }
497
}
779,487,033✔
498
//==============================================================================
779,487,033✔
499
// SurfaceYCylinder implementation
179,892✔
500
//==============================================================================
501

779,307,141✔
502
SurfaceYCylinder::SurfaceYCylinder(pugi::xml_node surf_node)
779,307,141✔
503
  : CSGSurface(surf_node)
779,307,141✔
504
{
779,307,141✔
505
  read_coeffs(surf_node, id_, {&x0_, &z0_, &radius_});
779,307,141✔
506
}
507

779,307,141✔
508
double SurfaceYCylinder::evaluate(Position r) const
509
{
141,465,954✔
510
  return axis_aligned_cylinder_evaluate<0, 2>(r, x0_, z0_, radius_);
511
}
637,841,187✔
512

513
double SurfaceYCylinder::distance(
514
  Position r, Direction u, bool coincident) const
515
{
277,802,039✔
516
  return axis_aligned_cylinder_distance<1, 0, 2>(
139,559,771✔
517
    r, u, coincident, x0_, z0_, radius_);
518
}
138,242,268✔
519

520
Direction SurfaceYCylinder::normal(Position r) const
521
{
360,039,148✔
522
  return axis_aligned_cylinder_normal<1, 0, 2>(r, x0_, z0_);
523
}
524

525
void SurfaceYCylinder::to_hdf5_inner(hid_t group_id) const
151,137,032✔
526
{
527
  write_string(group_id, "type", "y-cylinder", false);
528
  array<double, 3> coeffs {{x0_, z0_, radius_}};
529
  write_dataset(group_id, "coefficients", coeffs);
530
}
531

208,902,116✔
532
BoundingBox SurfaceYCylinder::bounding_box(bool pos_side) const
208,902,116✔
533
{
35,759,654✔
534
  if (!pos_side) {
173,142,462✔
535
    return {x0_ - radius_, x0_ + radius_, -INFTY, INFTY, z0_ - radius_,
536
      z0_ + radius_};
537
  } else {
×
538
    return {};
539
  }
540
}
×
541

×
542
//==============================================================================
×
543
// SurfaceZCylinder implementation
544
//==============================================================================
×
545

×
546
SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node)
×
547
  : CSGSurface(surf_node)
×
548
{
×
549
  read_coeffs(surf_node, id_, {&x0_, &y0_, &radius_});
550
}
×
551

552
double SurfaceZCylinder::evaluate(Position r) const
×
553
{
554
  return axis_aligned_cylinder_evaluate<0, 1>(r, x0_, y0_, radius_);
×
555
}
556

557
double SurfaceZCylinder::distance(
558
  Position r, Direction u, bool coincident) const
×
559
{
×
560
  return axis_aligned_cylinder_distance<2, 0, 1>(
561
    r, u, coincident, x0_, y0_, radius_);
×
562
}
563

564
Direction SurfaceZCylinder::normal(Position r) const
×
565
{
566
  return axis_aligned_cylinder_normal<2, 0, 1>(r, x0_, y0_);
567
}
568

×
569
void SurfaceZCylinder::to_hdf5_inner(hid_t group_id) const
570
{
571
  write_string(group_id, "type", "z-cylinder", false);
572
  array<double, 3> coeffs {{x0_, y0_, radius_}};
573
  write_dataset(group_id, "coefficients", coeffs);
574
}
×
575

×
576
BoundingBox SurfaceZCylinder::bounding_box(bool pos_side) const
×
577
{
×
578
  if (!pos_side) {
579
    return {x0_ - radius_, x0_ + radius_, y0_ - radius_, y0_ + radius_, -INFTY,
580
      INFTY};
1,187,232✔
581
  } else {
582
    return {};
583
  }
1,187,232✔
584
}
1,187,232✔
585

480,000✔
586
//==============================================================================
587
// SurfaceSphere implementation
707,232✔
588
//==============================================================================
707,232✔
589

707,232✔
590
SurfaceSphere::SurfaceSphere(pugi::xml_node surf_node) : CSGSurface(surf_node)
707,232✔
591
{
707,232✔
592
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_});
593
}
707,232✔
594

595
double SurfaceSphere::evaluate(Position r) const
×
596
{
597
  const double x = r.x - x0_;
707,232✔
598
  const double y = r.y - y0_;
599
  const double z = r.z - z0_;
600
  return x * x + y * y + z * z - radius_ * radius_;
601
}
×
602

×
603
double SurfaceSphere::distance(Position r, Direction u, bool coincident) const
604
{
×
605
  const double x = r.x - x0_;
606
  const double y = r.y - y0_;
607
  const double z = r.z - z0_;
707,232✔
608
  const double k = x * u.x + y * u.y + z * u.z;
609
  const double c = x * x + y * y + z * z - radius_ * radius_;
610
  const double quad = k * k - c;
611

707,232✔
612
  if (quad < 0.0) {
613
    // No intersection with sphere.
614
    return INFTY;
615

616
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
617
    // Particle is on the sphere, thus one distance is positive/negative and
×
618
    // the other is zero. The sign of k determines if we are facing in or out.
×
619
    if (k >= 0.0) {
×
620
      return INFTY;
×
621
    } else {
622
      return -k + sqrt(quad);
623
    }
624

625
  } else if (c < 0.0) {
626
    // Particle is inside the sphere, thus one distance must be negative and
627
    // one must be positive. The positive distance will be the one with
628
    // negative sign on sqrt(quad)
1,330,910✔
629
    return -k + sqrt(quad);
630

631
  } else {
1,330,910✔
632
    // Particle is outside the sphere, thus both distances are either positive
1,330,910✔
633
    // or negative. If positive, the smaller distance is the one with positive
1,330,910✔
634
    // sign on sqrt(quad).
1,330,910✔
635
    const double d = -k - sqrt(quad);
1,330,910✔
636
    if (d < 0.0)
637
      return INFTY;
1,330,910✔
638
    return d;
639
  }
640
}
1,330,910✔
641

1,330,910✔
642
Direction SurfaceSphere::normal(Position r) const
1,330,910✔
643
{
1,330,910✔
644
  return {2.0 * (r.x - x0_), 2.0 * (r.y - y0_), 2.0 * (r.z - z0_)};
1,330,910✔
645
}
646

×
647
void SurfaceSphere::to_hdf5_inner(hid_t group_id) const
648
{
649
  write_string(group_id, "type", "sphere", false);
×
650
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_}};
×
651
  write_dataset(group_id, "coefficients", coeffs);
×
652
}
×
653

×
654
BoundingBox SurfaceSphere::bounding_box(bool pos_side) const
655
{
×
656
  if (!pos_side) {
657
    return {x0_ - radius_, x0_ + radius_, y0_ - radius_, y0_ + radius_,
658
      z0_ - radius_, z0_ + radius_};
×
659
  } else {
×
660
    return {};
×
661
  }
×
662
}
×
663

664
//==============================================================================
665
// Generic functions for x-, y-, and z-, cones
666
//==============================================================================
667

668
// The first template parameter indicates which axis the cone is aligned to.
669
// The other two parameters indicate the other two axes.  offset1, offset2,
34✔
670
// and offset3 should correspond with i1, i2, and i3, respectively.
34✔
671
template<int i1, int i2, int i3>
672
double axis_aligned_cone_evaluate(
34✔
673
  Position r, double offset1, double offset2, double offset3, double radius_sq)
34✔
674
{
675
  const double r1 = r.get<i1>() - offset1;
1,495,679✔
676
  const double r2 = r.get<i2>() - offset2;
677
  const double r3 = r.get<i3>() - offset3;
1,495,679✔
678
  return r2 * r2 + r3 * r3 - radius_sq * r1 * r1;
679
}
680

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

24✔
698
  double d;
699

×
700
  if (quad < 0.0) {
701
    // No intersection with cone.
×
702
    return INFTY;
×
703

×
704
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
705
    // Particle is on the cone, thus one distance is positive/negative
×
706
    // and the other is zero. The sign of k determines if we are facing in or
707
    // out.
708
    if (k >= 0.0) {
709
      d = (-k - sqrt(quad)) / a;
710
    } else {
711
      d = (-k + sqrt(quad)) / a;
712
    }
×
713

×
714
  } else {
715
    // Calculate both solutions to the quadratic.
×
716
    quad = sqrt(quad);
717
    d = (-k - quad) / a;
718
    const double b = (-k + quad) / a;
×
719

720
    // Determine the smallest positive solution.
×
721
    if (d < 0.0) {
722
      if (b > 0.0)
723
        d = b;
×
724
    } else {
725
      if (b > 0.0) {
726
        if (b < d)
×
727
          d = b;
×
728
      }
729
    }
730
  }
×
731

732
  // If the distance was negative, set boundary distance to infinity.
×
733
  if (d <= 0.0)
734
    return INFTY;
735
  return d;
×
736
}
737

×
738
// The first template parameter indicates which axis the cone is aligned to.
×
739
// The other two parameters indicate the other two axes.  offset1, offset2,
×
740
// and offset3 should correspond with i1, i2, and i3, respectively.
741
template<int i1, int i2, int i3>
742
Direction axis_aligned_cone_normal(
×
743
  Position r, double offset1, double offset2, double offset3, double radius_sq)
744
{
×
745
  Direction u;
×
746
  u.get<i1>() = -2.0 * radius_sq * (r.get<i1>() - offset1);
×
747
  u.get<i2>() = 2.0 * (r.get<i2>() - offset2);
748
  u.get<i3>() = 2.0 * (r.get<i3>() - offset3);
×
749
  return u;
750
}
751

752
//==============================================================================
753
// SurfaceXCone implementation
754
//==============================================================================
755

756
SurfaceXCone::SurfaceXCone(pugi::xml_node surf_node) : CSGSurface(surf_node)
3,967✔
757
{
3,967✔
758
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
759
}
3,967✔
760

3,967✔
761
double SurfaceXCone::evaluate(Position r) const
762
{
841,553,945✔
763
  return axis_aligned_cone_evaluate<0, 1, 2>(r, x0_, y0_, z0_, radius_sq_);
764
}
841,553,945✔
765

766
double SurfaceXCone::distance(Position r, Direction u, bool coincident) const
767
{
779,487,033✔
768
  return axis_aligned_cone_distance<0, 1, 2>(
769
    r, u, coincident, x0_, y0_, z0_, radius_sq_);
770
}
779,487,033✔
771

779,487,033✔
772
Direction SurfaceXCone::normal(Position r) const
773
{
774
  return axis_aligned_cone_normal<0, 1, 2>(r, x0_, y0_, z0_, radius_sq_);
1,330,910✔
775
}
776

1,330,910✔
777
void SurfaceXCone::to_hdf5_inner(hid_t group_id) const
778
{
779
  write_string(group_id, "type", "x-cone", false);
2,777✔
780
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
781
  write_dataset(group_id, "coefficients", coeffs);
2,777✔
782
}
2,777✔
783

2,777✔
784
//==============================================================================
2,777✔
785
// SurfaceYCone implementation
786
//==============================================================================
×
787

788
SurfaceYCone::SurfaceYCone(pugi::xml_node surf_node) : CSGSurface(surf_node)
×
789
{
×
790
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
×
791
}
792

×
793
double SurfaceYCone::evaluate(Position r) const
794
{
795
  return axis_aligned_cone_evaluate<1, 0, 2>(r, y0_, x0_, z0_, radius_sq_);
796
}
797

798
double SurfaceYCone::distance(Position r, Direction u, bool coincident) const
799
{
800
  return axis_aligned_cone_distance<1, 0, 2>(
6,668✔
801
    r, u, coincident, y0_, x0_, z0_, radius_sq_);
802
}
6,668✔
803

6,668✔
804
Direction SurfaceYCone::normal(Position r) const
805
{
477,040,719✔
806
  return axis_aligned_cone_normal<1, 0, 2>(r, y0_, x0_, z0_, radius_sq_);
807
}
477,040,719✔
808

477,040,719✔
809
void SurfaceYCone::to_hdf5_inner(hid_t group_id) const
477,040,719✔
810
{
477,040,719✔
811
  write_string(group_id, "type", "y-cone", false);
812
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
813
  write_dataset(group_id, "coefficients", coeffs);
487,925,435✔
814
}
815

487,925,435✔
816
//==============================================================================
487,925,435✔
817
// SurfaceZCone implementation
487,925,435✔
818
//==============================================================================
487,925,435✔
819

487,925,435✔
820
SurfaceZCone::SurfaceZCone(pugi::xml_node surf_node) : CSGSurface(surf_node)
487,925,435✔
821
{
822
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
487,925,435✔
823
}
824

104,015,772✔
825
double SurfaceZCone::evaluate(Position r) const
826
{
383,909,663✔
827
  return axis_aligned_cone_evaluate<2, 0, 1>(r, z0_, x0_, y0_, radius_sq_);
828
}
829

30,610,420✔
830
double SurfaceZCone::distance(Position r, Direction u, bool coincident) const
17,726,400✔
831
{
832
  return axis_aligned_cone_distance<2, 0, 1>(
12,884,020✔
833
    r, u, coincident, z0_, x0_, y0_, radius_sq_);
834
}
835

353,299,243✔
836
Direction SurfaceZCone::normal(Position r) const
837
{
838
  return axis_aligned_cone_normal<2, 0, 1>(r, z0_, x0_, y0_, radius_sq_);
839
}
334,423,755✔
840

841
void SurfaceZCone::to_hdf5_inner(hid_t group_id) const
842
{
843
  write_string(group_id, "type", "z-cone", false);
844
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
845
  write_dataset(group_id, "coefficients", coeffs);
18,875,488✔
846
}
18,875,488✔
847

4,187,304✔
848
//==============================================================================
14,688,184✔
849
// SurfaceQuadric implementation
850
//==============================================================================
851

852
SurfaceQuadric::SurfaceQuadric(pugi::xml_node surf_node) : CSGSurface(surf_node)
937,656✔
853
{
854
  read_coeffs(
937,656✔
855
    surf_node, id_, {&A_, &B_, &C_, &D_, &E_, &F_, &G_, &H_, &J_, &K_});
856
}
857

5,006✔
858
double SurfaceQuadric::evaluate(Position r) const
859
{
5,006✔
860
  const double x = r.x;
5,006✔
861
  const double y = r.y;
5,006✔
862
  const double z = r.z;
5,006✔
863
  return x * (A_ * x + D_ * y + G_) + y * (B_ * y + E_ * z + H_) +
864
         z * (C_ * z + F_ * x + J_) + K_;
×
865
}
866

×
867
double SurfaceQuadric::distance(
×
868
  Position r, Direction ang, bool coincident) const
×
869
{
870
  const double& x = r.x;
×
871
  const double& y = r.y;
872
  const double& z = r.z;
873
  const double& u = ang.x;
874
  const double& v = ang.y;
875
  const double& w = ang.z;
876

877
  const double a =
878
    A_ * u * u + B_ * v * v + C_ * w * w + D_ * u * v + E_ * v * w + F_ * u * w;
879
  const double k = A_ * u * x + B_ * v * y + C_ * w * z +
880
                   0.5 * (D_ * (u * y + v * x) + E_ * (v * z + w * y) +
881
                           F_ * (w * x + u * z) + G_ * u + H_ * v + J_ * w);
882
  const double c = A_ * x * x + B_ * y * y + C_ * z * z + D_ * x * y +
351,912✔
883
                   E_ * y * z + F_ * x * z + G_ * x + H_ * y + J_ * z + K_;
884
  double quad = k * k - a * c;
885

351,912✔
886
  double d;
351,912✔
887

351,912✔
888
  if (quad < 0.0) {
351,912✔
889
    // No intersection with surface.
890
    return INFTY;
351,912✔
891

892
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
893
    // Particle is on the surface, thus one distance is positive/negative and
351,912✔
894
    // the other is zero. The sign of k determines which distance is zero and
351,912✔
895
    // which is not. Additionally, if a is zero, it means the particle is on
351,912✔
896
    // a plane-like surface.
351,912✔
897
    if (a == 0.0) {
898
      d = INFTY; // see the below explanation
×
899
    } else if (k >= 0.0) {
900
      d = (-k - sqrt(quad)) / a;
901
    } else {
×
902
      d = (-k + sqrt(quad)) / a;
×
903
    }
×
904

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

1,050,936✔
920
    // Determine the smallest positive solution.
921
    if (d < 0.0) {
922
      if (b > 0.0)
1,050,936✔
923
        d = b;
1,050,936✔
924
    } else {
1,050,936✔
925
      if (b > 0.0) {
1,050,936✔
926
        if (b < d)
1,050,936✔
927
          d = b;
1,050,936✔
928
      }
1,050,936✔
929
    }
1,050,936✔
930
  }
1,050,936✔
931

932
  // If the distance was negative, set boundary distance to infinity.
933
  if (d <= 0.0)
934
    return INFTY;
1,050,936✔
935
  return d;
936
}
×
937

938
Direction SurfaceQuadric::normal(Position r) const
1,050,936✔
939
{
940
  const double& x = r.x;
941
  const double& y = r.y;
942
  const double& z = r.z;
66,072✔
943
  return {2.0 * A_ * x + D_ * y + F_ * z + G_,
×
944
    2.0 * B_ * y + D_ * x + E_ * z + H_, 2.0 * C_ * z + E_ * y + F_ * x + J_};
945
}
66,072✔
946

947
void SurfaceQuadric::to_hdf5_inner(hid_t group_id) const
948
{
949
  write_string(group_id, "type", "quadric", false);
950
  array<double, 10> coeffs {{A_, B_, C_, D_, E_, F_, G_, H_, J_, K_}};
984,864✔
951
  write_dataset(group_id, "coefficients", coeffs);
984,864✔
952
}
984,864✔
953

954
//==============================================================================
955
// Torus helper functions
984,864✔
956
//==============================================================================
836,208✔
957

709,644✔
958
double torus_distance(double x1, double x2, double x3, double u1, double u2,
959
  double u3, double A, double B, double C, bool coincident)
148,656✔
960
{
148,656✔
961
  // Coefficients for equation: (c2 t^2 + c1 t + c0)^2 = c2' t^2 + c1' t + c0'
148,656✔
962
  double D = (C * C) / (B * B);
963
  double c2 = u1 * u1 + u2 * u2 + D * u3 * u3;
964
  double c1 = 2 * (u1 * x1 + u2 * x2 + D * u3 * x3);
965
  double c0 = x1 * x1 + x2 * x2 + D * x3 * x3 + A * A - C * C;
966
  double four_A2 = 4 * A * A;
967
  double c2p = four_A2 * (u1 * u1 + u2 * u2);
1,050,936✔
968
  double c1p = 2 * four_A2 * (u1 * x1 + u2 * x2);
149,964✔
969
  double c0p = four_A2 * (x1 * x1 + x2 * x2);
900,972✔
970

971
  // Coefficient for equation: a t^4 + b t^3 + c t^2 + d t + e = 0. If the point
1,050,936✔
972
  // is coincident, the 'e' coefficient should be zero. Explicitly setting it to
973
  // zero helps avoid numerical issues below with root finding.
974
  double coeff[5];
1,050,936✔
975
  coeff[0] = coincident ? 0.0 : c0 * c0 - c0p;
1,050,936✔
976
  coeff[1] = 2 * c0 * c1 - c1p;
1,050,936✔
977
  coeff[2] = c1 * c1 + 2 * c0 * c2 - c2p;
1,050,936✔
978
  coeff[3] = 2 * c1 * c2;
1,050,936✔
979
  coeff[4] = c2 * c2;
1,050,936✔
980

1,050,936✔
981
  std::complex<double> roots[4];
1,050,936✔
982
  oqs::quartic_solver(coeff, roots);
1,050,936✔
983

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

984,864✔
1008
//==============================================================================
836,208✔
1009
// SurfaceXTorus implementation
709,644✔
1010
//==============================================================================
1011

148,656✔
1012
SurfaceXTorus::SurfaceXTorus(pugi::xml_node surf_node) : CSGSurface(surf_node)
148,656✔
1013
{
148,656✔
1014
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1015
}
1016

1017
void SurfaceXTorus::to_hdf5_inner(hid_t group_id) const
1018
{
1019
  write_string(group_id, "type", "x-torus", false);
1,050,936✔
1020
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
149,964✔
1021
  write_dataset(group_id, "coefficients", coeffs);
900,972✔
1022
}
1023

×
1024
double SurfaceXTorus::evaluate(Position r) const
1025
{
1026
  double x = r.x - x0_;
×
1027
  double y = r.y - y0_;
×
1028
  double z = r.z - z0_;
×
1029
  return (x * x) / (B_ * B_) +
×
1030
         std::pow(std::sqrt(y * y + z * z) - A_, 2) / (C_ * C_) - 1.;
×
1031
}
×
1032

×
1033
double SurfaceXTorus::distance(Position r, Direction u, bool coincident) const
×
1034
{
×
1035
  double x = r.x - x0_;
1036
  double y = r.y - y0_;
1037
  double z = r.z - z0_;
1038
  return torus_distance(y, z, x, u.y, u.z, u.x, A_, B_, C_, coincident);
×
1039
}
1040

×
1041
Direction SurfaceXTorus::normal(Position r) const
1042
{
×
1043
  // reduce the expansion of the full form for torus
1044
  double x = r.x - x0_;
1045
  double y = r.y - y0_;
1046
  double z = r.z - z0_;
×
1047

×
1048
  // f(x,y,z) = x^2/B^2 + (sqrt(y^2 + z^2) - A)^2/C^2 - 1
1049
  // ∂f/∂x = 2x/B^2
×
1050
  // ∂f/∂y = 2y(g - A)/(g*C^2) where g = sqrt(y^2 + z^2)
1051
  // ∂f/∂z = 2z(g - A)/(g*C^2)
1052
  // Multiplying by g*C^2*B^2 / 2 gives:
1053
  double g = std::sqrt(y * y + z * z);
1054
  double nx = C_ * C_ * g * x;
×
1055
  double ny = y * (g - A_) * B_ * B_;
×
1056
  double nz = z * (g - A_) * B_ * B_;
×
1057
  Direction n(nx, ny, nz);
1058
  return n / n.norm();
1059
}
×
1060

×
1061
//==============================================================================
×
1062
// SurfaceYTorus implementation
1063
//==============================================================================
×
1064

×
1065
SurfaceYTorus::SurfaceYTorus(pugi::xml_node surf_node) : CSGSurface(surf_node)
×
1066
{
1067
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1068
}
1069

1070
void SurfaceYTorus::to_hdf5_inner(hid_t group_id) const
1071
{
×
1072
  write_string(group_id, "type", "y-torus", false);
×
1073
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
×
1074
  write_dataset(group_id, "coefficients", coeffs);
1075
}
×
1076

1077
double SurfaceYTorus::evaluate(Position r) const
1078
{
×
1079
  double x = r.x - x0_;
×
1080
  double y = r.y - y0_;
×
1081
  double z = r.z - z0_;
×
1082
  return (y * y) / (B_ * B_) +
×
1083
         std::pow(std::sqrt(x * x + z * z) - A_, 2) / (C_ * C_) - 1.;
×
1084
}
×
1085

×
1086
double SurfaceYTorus::distance(Position r, Direction u, bool coincident) const
×
1087
{
1088
  double x = r.x - x0_;
1089
  double y = r.y - y0_;
1090
  double z = r.z - z0_;
×
1091
  return torus_distance(x, z, y, u.x, u.z, u.y, A_, B_, C_, coincident);
1092
}
×
1093

1094
Direction SurfaceYTorus::normal(Position r) const
×
1095
{
1096
  // reduce the expansion of the full form for torus
1097
  double x = r.x - x0_;
1098
  double y = r.y - y0_;
×
1099
  double z = r.z - z0_;
×
1100

1101
  // f(x,y,z) = y^2/B^2 + (sqrt(x^2 + z^2) - A)^2/C^2 - 1
×
1102
  // ∂f/∂x = 2x(g - A)/(g*C^2) where g = sqrt(x^2 + z^2)
1103
  // ∂f/∂y = 2y/B^2
1104
  // ∂f/∂z = 2z(g - A)/(g*C^2)
1105
  // Multiplying by g*C^2*B^2 / 2 gives:
1106
  double g = std::sqrt(x * x + z * z);
×
1107
  double nx = x * (g - A_) * B_ * B_;
×
1108
  double ny = C_ * C_ * g * y;
×
1109
  double nz = z * (g - A_) * B_ * B_;
1110
  Direction n(nx, ny, nz);
1111
  return n / n.norm();
×
1112
}
×
1113

×
1114
//==============================================================================
1115
// SurfaceZTorus implementation
×
1116
//==============================================================================
×
1117

×
1118
SurfaceZTorus::SurfaceZTorus(pugi::xml_node surf_node) : CSGSurface(surf_node)
1119
{
1120
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1121
}
1122

1123
void SurfaceZTorus::to_hdf5_inner(hid_t group_id) const
×
1124
{
×
1125
  write_string(group_id, "type", "z-torus", false);
×
1126
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
1127
  write_dataset(group_id, "coefficients", coeffs);
1128
}
1129

1130
double SurfaceZTorus::evaluate(Position r) const
1131
{
1132
  double x = r.x - x0_;
66,072✔
1133
  double y = r.y - y0_;
1134
  double z = r.z - z0_;
1135
  return (z * z) / (B_ * B_) +
66,072✔
1136
         std::pow(std::sqrt(x * x + y * y) - A_, 2) / (C_ * C_) - 1.;
66,072✔
1137
}
66,072✔
1138

66,072✔
1139
double SurfaceZTorus::distance(Position r, Direction u, bool coincident) const
66,072✔
1140
{
1141
  double x = r.x - x0_;
66,072✔
1142
  double y = r.y - y0_;
1143
  double z = r.z - z0_;
1144
  return torus_distance(x, y, z, u.x, u.y, u.z, A_, B_, C_, coincident);
66,072✔
1145
}
66,072✔
1146

66,072✔
1147
Direction SurfaceZTorus::normal(Position r) const
66,072✔
1148
{
66,072✔
1149
  // reduce the expansion of the full form for torus
1150
  double x = r.x - x0_;
×
1151
  double y = r.y - y0_;
1152
  double z = r.z - z0_;
1153

×
1154
  // f(x,y,z) = z^2/B^2 + (sqrt(x^2 + y^2) - A)^2/C^2 - 1
×
1155
  // ∂f/∂x = 2x(g - A)/(g*C^2) where g = sqrt(x^2 + y^2)
×
1156
  // ∂f/∂y = 2y(g - A)/(g*C^2)
×
1157
  // ∂f/∂z = 2z/B^2
×
1158
  // Multiplying by g*C^2*B^2 / 2 gives:
1159
  double g = std::sqrt(x * x + y * y);
×
1160
  double nx = x * (g - A_) * B_ * B_;
1161
  double ny = y * (g - A_) * B_ * B_;
1162
  double nz = C_ * C_ * g * z;
×
1163
  Position n(nx, ny, nz);
×
1164
  return n / n.norm();
×
1165
}
×
1166

×
1167
//==============================================================================
1168

1169
void read_surfaces(pugi::xml_node node)
1170
{
1171
  // Count the number of surfaces
1172
  int n_surfaces = 0;
1173
  for (pugi::xml_node surf_node : node.children("surface")) {
×
1174
    n_surfaces++;
1175
  }
×
1176

1177
  // Loop over XML surface elements and populate the array.  Keep track of
1178
  // periodic surfaces and their albedos.
×
1179
  model::surfaces.reserve(n_surfaces);
1180
  std::set<std::pair<int, int>> periodic_pairs;
×
1181
  std::unordered_map<int, double> albedo_map;
1182
  {
1183
    pugi::xml_node surf_node;
×
1184
    int i_surf;
1185
    for (surf_node = node.child("surface"), i_surf = 0; surf_node;
×
1186
         surf_node = surf_node.next_sibling("surface"), i_surf++) {
×
1187
      std::string surf_type = get_node_value(surf_node, "type", true, true);
1188

1189
      // Allocate and initialize the new surface
×
1190

1191
      if (surf_type == "x-plane") {
×
1192
        model::surfaces.push_back(make_unique<SurfaceXPlane>(surf_node));
1193

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

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

1200
      } else if (surf_type == "plane") {
1201
        model::surfaces.push_back(make_unique<SurfacePlane>(surf_node));
1202

1203
      } else if (surf_type == "x-cylinder") {
1204
        model::surfaces.push_back(make_unique<SurfaceXCylinder>(surf_node));
1205

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

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

1212
      } else if (surf_type == "sphere") {
×
1213
        model::surfaces.push_back(make_unique<SurfaceSphere>(surf_node));
1214

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

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

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

×
1224
      } else if (surf_type == "quadric") {
1225
        model::surfaces.push_back(make_unique<SurfaceQuadric>(surf_node));
1226

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

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

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

1236
      } else {
1237
        fatal_error(fmt::format("Invalid surface type, \"{}\"", surf_type));
17✔
1238
      }
1239

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

12✔
1264
  // Fill the surface map
1265
  for (int i_surf = 0; i_surf < model::surfaces.size(); i_surf++) {
1266
    int id = model::surfaces[i_surf]->id_;
1267
    auto in_map = model::surface_map.find(id);
1268
    if (in_map == model::surface_map.end()) {
1269
      model::surface_map[id] = i_surf;
17✔
1270
    } else {
1271
      fatal_error(
×
1272
        fmt::format("Two or more surfaces use the same unique ID: {}", id));
17✔
1273
    }
17✔
1274
  }
1275

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

1294
    // Make sure there isn't a third unpaired surface
340,920✔
1295
    next_elem = second_unresolved;
340,920✔
1296
    next_elem++;
340,920✔
1297
    auto third_unresolved =
340,920✔
1298
      std::find_if(next_elem, periodic_pairs.end(), is_unresolved_pair);
340,920✔
1299
    if (third_unresolved != periodic_pairs.end()) {
340,920✔
1300
      fatal_error(
340,920✔
1301
        "Found at least three periodic surfaces without a specified "
340,920✔
1302
        "partner. Please specify the partner for each periodic surface.");
1303
    }
1304

1305
    // Add the completed pair and remove the old, unpaired entries
340,920✔
1306
    int lo_id = std::min(first_unresolved->first, second_unresolved->first);
1307
    int hi_id = std::max(first_unresolved->first, second_unresolved->first);
×
1308
    periodic_pairs.insert({lo_id, hi_id});
1309
    periodic_pairs.erase(first_unresolved);
340,920✔
1310
    periodic_pairs.erase(second_unresolved);
1311
  }
1312

1313
  // Assign the periodic boundary conditions with albedos
1314
  for (auto periodic_pair : periodic_pairs) {
37,680✔
1315
    int i_surf = model::surface_map[periodic_pair.first];
×
1316
    int j_surf = model::surface_map[periodic_pair.second];
37,680✔
1317
    Surface& surf1 {*model::surfaces[i_surf]};
×
1318
    Surface& surf2 {*model::surfaces[j_surf]};
1319

37,680✔
1320
    // Compute the dot product of the surface normals
1321
    Direction norm1 = surf1.normal({0, 0, 0});
1322
    Direction norm2 = surf2.normal({0, 0, 0});
303,240✔
1323
    norm1 /= norm1.norm();
1324
    norm2 /= norm2.norm();
1325
    double dot_prod = norm1.dot(norm2);
1326

1327
    // If the dot product is 1 (to within floating point precision) then the
1328
    // planes are parallel which indicates a translational periodic boundary
1329
    // condition.  Otherwise, it is a rotational periodic BC.
1330
    if (std::abs(1.0 - dot_prod) < FP_PRECISION) {
×
1331
      surf1.bc_ = make_unique<TranslationalPeriodicBC>(i_surf, j_surf);
1332
      surf2.bc_ = make_unique<TranslationalPeriodicBC>(i_surf, j_surf);
1333
    } else {
303,240✔
1334
      surf1.bc_ = make_unique<RotationalPeriodicBC>(i_surf, j_surf);
303,240✔
1335
      surf2.bc_ = make_unique<RotationalPeriodicBC>(i_surf, j_surf);
303,240✔
1336
    }
1337

1338
    // If albedo data is present in albedo map, set the boundary albedo.
303,240✔
1339
    if (albedo_map.count(surf1.id_)) {
303,240✔
1340
      surf1.bc_->set_albedo(albedo_map[surf1.id_]);
303,240✔
1341
    }
1342
    if (albedo_map.count(surf2.id_)) {
×
1343
      surf2.bc_->set_albedo(albedo_map[surf2.id_]);
×
1344
    }
×
1345
  }
1346
}
1347

1348
void free_memory_surfaces()
1349
{
1350
  model::surfaces.clear();
340,920✔
1351
  model::surface_map.clear();
×
1352
}
340,920✔
1353

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