From 1e9f13079047988aeac68006d2c73200fb79cd2f Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Wed, 1 Oct 2025 22:20:42 -0500 Subject: [PATCH 01/13] allow RotationalPeriodicBC around x,y, or z axis --- include/openmc/boundary_condition.h | 9 +++- src/boundary_condition.cpp | 84 ++++++++++++++--------------- 2 files changed, 47 insertions(+), 46 deletions(-) diff --git a/include/openmc/boundary_condition.h b/include/openmc/boundary_condition.h index af40131f1c8..0e7cc717679 100644 --- a/include/openmc/boundary_condition.h +++ b/include/openmc/boundary_condition.h @@ -138,18 +138,23 @@ class TranslationalPeriodicBC : public PeriodicBC { //============================================================================== //! A BC that rotates particles about a global axis. // -//! Currently only rotations about the z-axis are supported. +//! Only rotations about the x, y, and z axes are supported. //============================================================================== class RotationalPeriodicBC : public PeriodicBC { public: RotationalPeriodicBC(int i_surf, int j_surf); - + float compute_periodic_rotation(float rise_1, float run_1, float rise_2, float run_2) const; void handle_particle(Particle& p, const Surface& surf) const override; protected: //! Angle about the axis by which particle coordinates will be rotated double angle_; + enum PeriodicAxis {x, y, z}; + //! Ensure that choice of axes is right handed. axis_1_idx_ corresponds to the independent axis and axis_2_idx_ corresponds to the dependent axis in the 2D plane perpendicular to the planes' axis of rotation + int zero_axis_idx; + int axis_1_idx_; + int axis_2_idx_; }; } // namespace openmc diff --git a/src/boundary_condition.cpp b/src/boundary_condition.cpp index 7216ac89649..b3cee5673e8 100644 --- a/src/boundary_condition.cpp +++ b/src/boundary_condition.cpp @@ -158,56 +158,46 @@ void TranslationalPeriodicBC::handle_particle( // RotationalPeriodicBC implementation //============================================================================== -RotationalPeriodicBC::RotationalPeriodicBC(int i_surf, int j_surf) +RotationalPeriodicBC::RotationalPeriodicBC(int i_surf, int j_surf, PeriodicAxis axis) : PeriodicBC(i_surf, j_surf) { Surface& surf1 {*model::surfaces[i_surf_]}; Surface& surf2 {*model::surfaces[j_surf_]}; - // Check the type of the first surface - bool surf1_is_xyplane; - if (const auto* ptr = dynamic_cast(&surf1)) { - surf1_is_xyplane = true; - } else if (const auto* ptr = dynamic_cast(&surf1)) { - surf1_is_xyplane = true; - } else if (const auto* ptr = dynamic_cast(&surf1)) { - surf1_is_xyplane = false; - } else { - throw std::invalid_argument(fmt::format( - "Surface {} is an invalid type for " - "rotational periodic BCs. Only x-planes, y-planes, or general planes " - "(that are perpendicular to z) are supported for these BCs.", - surf1.id_)); + // below convention for right handed coordinate system + switch(axis) { + case x: + zero_axis_idx = 0; // x component of plane must be zero + axis_1_idx_ = 1; // y component independent + axis_2_idx_ = 2; // z component dependent + break; + case y: + zero_axis_idx = 1; // y component of plane must be zero + axis_1_idx_ = 2; // z component independent + axis_2_idx_ = 0; // x component dependent + break; + case z: + zero_axis_idx = 2; // z component of plane must be zero + axis_1_idx_ = 0; // x component independent + axis_2_idx_ = 1; // y component dependent + break; + default: + throw std::invalid_argument(fmt::format("You've specified an axis {} that is not x, y, or z."),axis) } - // Check the type of the second surface - bool surf2_is_xyplane; - if (const auto* ptr = dynamic_cast(&surf2)) { - surf2_is_xyplane = true; - } else if (const auto* ptr = dynamic_cast(&surf2)) { - surf2_is_xyplane = true; - } else if (const auto* ptr = dynamic_cast(&surf2)) { - surf2_is_xyplane = false; - } else { - throw std::invalid_argument(fmt::format( - "Surface {} is an invalid type for " - "rotational periodic BCs. Only x-planes, y-planes, or general planes " - "(that are perpendicular to z) are supported for these BCs.", - surf2.id_)); - } // Compute the surface normal vectors and make sure they are perpendicular - // to the z-axis + // to the correct axis Direction norm1 = surf1.normal({0, 0, 0}); Direction norm2 = surf2.normal({0, 0, 0}); - if (std::abs(norm1.z) > FP_PRECISION) { + if (std::abs(norm1[zero_axis_idx]) > FP_PRECISION) { throw std::invalid_argument(fmt::format( "Rotational periodic BCs are only " "supported for rotations about the z-axis, but surface {} is not " "perpendicular to the z-axis.", surf1.id_)); } - if (std::abs(norm2.z) > FP_PRECISION) { + if (std::abs(norm2[zero_axis_idx]) > FP_PRECISION) { throw std::invalid_argument(fmt::format( "Rotational periodic BCs are only " "supported for rotations about the z-axis, but surface {} is not " @@ -231,15 +221,7 @@ RotationalPeriodicBC::RotationalPeriodicBC(int i_surf, int j_surf) surf2.id_)); } - // Compute the BC rotation angle. Here it is assumed that both surface - // normal vectors point inwards---towards the valid geometry region. - // Consequently, the rotation angle is not the difference between the two - // normals, but is instead the difference between one normal and one - // anti-normal. (An incident ray on one surface must be an outgoing ray on - // the other surface after rotation hence the anti-normal.) - double theta1 = std::atan2(norm1.y, norm1.x); - double theta2 = std::atan2(norm2.y, norm2.x) + PI; - angle_ = theta2 - theta1; + angle_ = compute_periodic_rotation(norm1[axis_2_idx_], norm1[axis_1_idx_], norm2[axis_2_idx_], norm2[axis_1_idx_]) // Warn the user if the angle does not evenly divide a circle double rem = std::abs(std::remainder((2 * PI / angle_), 1.0)); @@ -251,6 +233,20 @@ RotationalPeriodicBC::RotationalPeriodicBC(int i_surf, int j_surf) } } +float RotationalPeriodicBC::compute_periodic_rotation( + float rise_1, float run_1, float rise_2, float run_2) const +{ + // Compute the BC rotation angle. Here it is assumed that both surface + // normal vectors point inwards---towards the valid geometry region. + // Consequently, the rotation angle is not the difference between the two + // normals, but is instead the difference between one normal and one + // anti-normal. (An incident ray on one surface must be an outgoing ray on + // the other surface after rotation hence the anti-normal.) + double theta1 = std::atan2(rise_1, run_1); + double theta2 = std::atan2(rise_2, run_2) + PI; + return theta2 - theta1; +} + void RotationalPeriodicBC::handle_particle( Particle& p, const Surface& surf) const { @@ -279,9 +275,9 @@ void RotationalPeriodicBC::handle_particle( double cos_theta = std::cos(theta); double sin_theta = std::sin(theta); Position new_r = { - cos_theta * r.x - sin_theta * r.y, sin_theta * r.x + cos_theta * r.y, r.z}; + cos_theta * r[axis_1_idx_] - sin_theta * r[axis_2_idx_], sin_theta * r[axis_1_idx_] + cos_theta * r[axis_2_idx_], r[zero_axis_idx]}; Direction new_u = { - cos_theta * u.x - sin_theta * u.y, sin_theta * u.x + cos_theta * u.y, u.z}; + cos_theta * u[axis_1_idx_] - sin_theta * u[axis_2_idx_], sin_theta * u[axis_1_idx_] + cos_theta * u[axis_2_idx_], u[zero_axis_idx]}; // Handle the effects of the surface albedo on the particle's weight. BoundaryCondition::handle_albedo(p, surf); From 1575c563061377d833f677efb11184214eaaed25 Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Sat, 1 Nov 2025 17:36:22 -0500 Subject: [PATCH 02/13] added two periodic cylinders with 1/6th symmetry to regression tests. they should generate the same answers, need to run transport to get the results_true.dat --- .../periodic_cyls/__init__.py | 0 tests/regression_tests/periodic_cyls/test.py | 85 +++++++++++++++++++ .../periodic_cyls/xcyl_model/inputs_true.dat | 29 +++++++ .../periodic_cyls/xcyl_model/results_true.dat | 0 .../periodic_cyls/ycyl_model/inputs_true.dat | 29 +++++++ .../periodic_cyls/ycyl_model/results_true.dat | 0 6 files changed, 143 insertions(+) create mode 100644 tests/regression_tests/periodic_cyls/__init__.py create mode 100644 tests/regression_tests/periodic_cyls/test.py create mode 100644 tests/regression_tests/periodic_cyls/xcyl_model/inputs_true.dat create mode 100644 tests/regression_tests/periodic_cyls/xcyl_model/results_true.dat create mode 100644 tests/regression_tests/periodic_cyls/ycyl_model/inputs_true.dat create mode 100644 tests/regression_tests/periodic_cyls/ycyl_model/results_true.dat diff --git a/tests/regression_tests/periodic_cyls/__init__.py b/tests/regression_tests/periodic_cyls/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/periodic_cyls/test.py b/tests/regression_tests/periodic_cyls/test.py new file mode 100644 index 00000000000..e3bc293a320 --- /dev/null +++ b/tests/regression_tests/periodic_cyls/test.py @@ -0,0 +1,85 @@ +import openmc +import numpy +import pytest + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture +def xcyl_model(): + model = openmc.Model() + # Define materials + fuel = openmc.Material() + fuel.add_nuclide('U235', 0.2) + fuel.add_nuclide('U238', 0.8) + fuel.set_density('g/cc', 19.1) + model.materials = openmc.Materials([fuel]) + + # Define geometry + # finite cylinder + x_min = openmc.XPlane(x0=0.0, boundary_type='reflective') + x_max = openmc.XPlane(x0=20.0, boundary_type='reflective') + x_cyl = openmc.XCylinder(r=20.0,boundary_type='vacuum') + # slice cylinder for periodic BC + periodic_bounding_yplane = openmc.YPlane(y0=0, boundary_type='periodic') + periodic_bounding_plane = openmc.Plane( + a=0.0, b=-np.sqrt(3) / 3, c=1, boundary_type='periodic', + ) + sixth_cyl_cell = openmc.Cell(1, fill=fuel, region = + +x_min &- x_max & -x_cyl & +periodic_bounding_yplane & +periodic_bounding_plane) + periodic_bounding_yplane.periodic_surface = periodic_bounding_plane + periodic_bounding_plane.periodic_surface = periodic_bounding_yplane + + model.geometry = openmc.Geometry([sixth_cyl_cell]) + + + # Define settings + model.settings.particles = 1000 + model.settings.batches = 4 + model.settings.inactive = 0 + model.settings.source = openmc.IndependentSource(space=openmc.stats.Box( + (0, 0, 0), (20, 20, 20)) + ) + +@pytest.fixture +def ycyl_model(): + model = openmc.Model() + # Define materials + fuel = openmc.Material() + fuel.add_nuclide('U235', 0.2) + fuel.add_nuclide('U238', 0.8) + fuel.set_density('g/cc', 19.1) + model.materials = openmc.Materials([fuel]) + + # Define geometry + # finite cylinder + y_min = openmc.YPlane(y0=0.0, boundary_type='reflective') + y_max = openmc.YPlane(y0=20.0, boundary_type='reflective') + y_cyl = openmc.YCylinder(r=20.0,boundary_type='vacuum') + # slice cylinder for periodic BC + periodic_bounding_xplane = openmc.XPlane(x0=0, boundary_type='periodic') + periodic_bounding_plane = openmc.Plane( + a=-np.sqrt(3) / 3, b=0.0, c=1, boundary_type='periodic', + ) + sixth_cyl_cell = openmc.Cell(1, fill=fuel, region = + +y_min &- y_max & -y_cyl & +periodic_bounding_xplane & +periodic_bounding_plane) + periodic_bounding_xplane.periodic_surface = periodic_bounding_plane + periodic_bounding_plane.periodic_surface = periodic_bounding_xplane + model.geometry = openmc.Geometry([sixth_cyl_cell]) + + + # Define settings + model.settings.particles = 1000 + model.settings.batches = 4 + model.settings.inactive = 0 + model.settings.source = openmc.IndependentSource(space=openmc.stats.Box( + (0, 0, 0), (20, 20, 20)) + ) + + +@pytest.mark.parametrize('cyl_model', ['xcyl_model','ycyl_model']) +def test_periodic(cyl_model): + with change_directory(cyl_model): + harness = PyAPITestHarness('statepoint.4.h5', cyl_model) + harness.main() + assert \ No newline at end of file diff --git a/tests/regression_tests/periodic_cyls/xcyl_model/inputs_true.dat b/tests/regression_tests/periodic_cyls/xcyl_model/inputs_true.dat new file mode 100644 index 00000000000..7d1ecf426a8 --- /dev/null +++ b/tests/regression_tests/periodic_cyls/xcyl_model/inputs_true.dat @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + eigenvalue + 1000 + 4 + 0 + + + 0 0 0 20 20 20 + + + + diff --git a/tests/regression_tests/periodic_cyls/xcyl_model/results_true.dat b/tests/regression_tests/periodic_cyls/xcyl_model/results_true.dat new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/periodic_cyls/ycyl_model/inputs_true.dat b/tests/regression_tests/periodic_cyls/ycyl_model/inputs_true.dat new file mode 100644 index 00000000000..8ce785ed749 --- /dev/null +++ b/tests/regression_tests/periodic_cyls/ycyl_model/inputs_true.dat @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + eigenvalue + 1000 + 4 + 0 + + + 0 0 0 20 20 20 + + + + diff --git a/tests/regression_tests/periodic_cyls/ycyl_model/results_true.dat b/tests/regression_tests/periodic_cyls/ycyl_model/results_true.dat new file mode 100644 index 00000000000..e69de29bb2d From 5dc513801ec05908633d2c301c5729174acf8c4c Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Tue, 4 Nov 2025 23:06:57 +0000 Subject: [PATCH 03/13] syntax corrections --- include/openmc/boundary_condition.h | 4 ++-- openmc/surface.py | 5 ++--- src/boundary_condition.cpp | 6 ++++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/include/openmc/boundary_condition.h b/include/openmc/boundary_condition.h index 0e7cc717679..9410c126288 100644 --- a/include/openmc/boundary_condition.h +++ b/include/openmc/boundary_condition.h @@ -143,14 +143,14 @@ class TranslationalPeriodicBC : public PeriodicBC { class RotationalPeriodicBC : public PeriodicBC { public: - RotationalPeriodicBC(int i_surf, int j_surf); + enum PeriodicAxis { x, y, z }; + RotationalPeriodicBC(int i_surf, int j_surf, PeriodicAxis axis = z); float compute_periodic_rotation(float rise_1, float run_1, float rise_2, float run_2) const; void handle_particle(Particle& p, const Surface& surf) const override; protected: //! Angle about the axis by which particle coordinates will be rotated double angle_; - enum PeriodicAxis {x, y, z}; //! Ensure that choice of axes is right handed. axis_1_idx_ corresponds to the independent axis and axis_2_idx_ corresponds to the dependent axis in the 2D plane perpendicular to the planes' axis of rotation int zero_axis_idx; int axis_1_idx_; diff --git a/openmc/surface.py b/openmc/surface.py index 4839783ffa8..7af06253c1c 100644 --- a/openmc/surface.py +++ b/openmc/surface.py @@ -123,9 +123,8 @@ class Surface(IDManagerMixin, ABC): boundary_type : {'transmission', 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles - freely pass through the surface. Note that periodic boundary conditions - can only be applied to x-, y-, and z-planes, and only axis-aligned - periodicity is supported. + freely pass through the surface. Note that only axis-aligned + periodicity is supported periodic around the o x-, y-, and z-axes. albedo : float, optional Albedo of the surfaces as a ratio of particle weight after interaction with the surface to the initial weight. Values must be positive. Only diff --git a/src/boundary_condition.cpp b/src/boundary_condition.cpp index b3cee5673e8..4f9223162dc 100644 --- a/src/boundary_condition.cpp +++ b/src/boundary_condition.cpp @@ -182,7 +182,8 @@ RotationalPeriodicBC::RotationalPeriodicBC(int i_surf, int j_surf, PeriodicAxis axis_2_idx_ = 1; // y component dependent break; default: - throw std::invalid_argument(fmt::format("You've specified an axis {} that is not x, y, or z."),axis) + throw std::invalid_argument( + fmt::format("You've specified an axis that is not x, y, or z.")); } @@ -221,7 +222,8 @@ RotationalPeriodicBC::RotationalPeriodicBC(int i_surf, int j_surf, PeriodicAxis surf2.id_)); } - angle_ = compute_periodic_rotation(norm1[axis_2_idx_], norm1[axis_1_idx_], norm2[axis_2_idx_], norm2[axis_1_idx_]) + angle_ = compute_periodic_rotation(norm1[axis_2_idx_], norm1[axis_1_idx_], + norm2[axis_2_idx_], norm2[axis_1_idx_]); // Warn the user if the angle does not evenly divide a circle double rem = std::abs(std::remainder((2 * PI / angle_), 1.0)); From 3bec147ee7af38a3b8f665689d9a6626162c893c Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Wed, 5 Nov 2025 12:08:33 -0600 Subject: [PATCH 04/13] apply style locally --- include/openmc/boundary_condition.h | 9 ++++-- openmc/surface.py | 2 +- src/boundary_condition.cpp | 50 ++++++++++++++--------------- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/include/openmc/boundary_condition.h b/include/openmc/boundary_condition.h index 9410c126288..b5139536ea3 100644 --- a/include/openmc/boundary_condition.h +++ b/include/openmc/boundary_condition.h @@ -145,15 +145,18 @@ class RotationalPeriodicBC : public PeriodicBC { public: enum PeriodicAxis { x, y, z }; RotationalPeriodicBC(int i_surf, int j_surf, PeriodicAxis axis = z); - float compute_periodic_rotation(float rise_1, float run_1, float rise_2, float run_2) const; + float compute_periodic_rotation( + float rise_1, float run_1, float rise_2, float run_2) const; void handle_particle(Particle& p, const Surface& surf) const override; protected: //! Angle about the axis by which particle coordinates will be rotated double angle_; - //! Ensure that choice of axes is right handed. axis_1_idx_ corresponds to the independent axis and axis_2_idx_ corresponds to the dependent axis in the 2D plane perpendicular to the planes' axis of rotation + //! Ensure that choice of axes is right handed. axis_1_idx_ corresponds to the + //! independent axis and axis_2_idx_ corresponds to the dependent axis in the + //! 2D plane perpendicular to the planes' axis of rotation int zero_axis_idx; - int axis_1_idx_; + int axis_1_idx_; int axis_2_idx_; }; diff --git a/openmc/surface.py b/openmc/surface.py index 7af06253c1c..53b30841443 100644 --- a/openmc/surface.py +++ b/openmc/surface.py @@ -124,7 +124,7 @@ class Surface(IDManagerMixin, ABC): Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. Note that only axis-aligned - periodicity is supported periodic around the o x-, y-, and z-axes. + periodicity is supported periodic around the x-, y-, and z-axes. albedo : float, optional Albedo of the surfaces as a ratio of particle weight after interaction with the surface to the initial weight. Values must be positive. Only diff --git a/src/boundary_condition.cpp b/src/boundary_condition.cpp index 4f9223162dc..4b2136a2e50 100644 --- a/src/boundary_condition.cpp +++ b/src/boundary_condition.cpp @@ -158,35 +158,35 @@ void TranslationalPeriodicBC::handle_particle( // RotationalPeriodicBC implementation //============================================================================== -RotationalPeriodicBC::RotationalPeriodicBC(int i_surf, int j_surf, PeriodicAxis axis) +RotationalPeriodicBC::RotationalPeriodicBC( + int i_surf, int j_surf, PeriodicAxis axis) : PeriodicBC(i_surf, j_surf) { Surface& surf1 {*model::surfaces[i_surf_]}; Surface& surf2 {*model::surfaces[j_surf_]}; // below convention for right handed coordinate system - switch(axis) { - case x: - zero_axis_idx = 0; // x component of plane must be zero - axis_1_idx_ = 1; // y component independent - axis_2_idx_ = 2; // z component dependent - break; - case y: - zero_axis_idx = 1; // y component of plane must be zero - axis_1_idx_ = 2; // z component independent - axis_2_idx_ = 0; // x component dependent - break; - case z: - zero_axis_idx = 2; // z component of plane must be zero - axis_1_idx_ = 0; // x component independent - axis_2_idx_ = 1; // y component dependent - break; - default: - throw std::invalid_argument( - fmt::format("You've specified an axis that is not x, y, or z.")); + switch (axis) { + case x: + zero_axis_idx = 0; // x component of plane must be zero + axis_1_idx_ = 1; // y component independent + axis_2_idx_ = 2; // z component dependent + break; + case y: + zero_axis_idx = 1; // y component of plane must be zero + axis_1_idx_ = 2; // z component independent + axis_2_idx_ = 0; // x component dependent + break; + case z: + zero_axis_idx = 2; // z component of plane must be zero + axis_1_idx_ = 0; // x component independent + axis_2_idx_ = 1; // y component dependent + break; + default: + throw std::invalid_argument( + fmt::format("You've specified an axis that is not x, y, or z.")); } - // Compute the surface normal vectors and make sure they are perpendicular // to the correct axis Direction norm1 = surf1.normal({0, 0, 0}); @@ -276,10 +276,10 @@ void RotationalPeriodicBC::handle_particle( Direction u = p.u(); double cos_theta = std::cos(theta); double sin_theta = std::sin(theta); - Position new_r = { - cos_theta * r[axis_1_idx_] - sin_theta * r[axis_2_idx_], sin_theta * r[axis_1_idx_] + cos_theta * r[axis_2_idx_], r[zero_axis_idx]}; - Direction new_u = { - cos_theta * u[axis_1_idx_] - sin_theta * u[axis_2_idx_], sin_theta * u[axis_1_idx_] + cos_theta * u[axis_2_idx_], u[zero_axis_idx]}; + Position new_r = {cos_theta * r[axis_1_idx_] - sin_theta * r[axis_2_idx_], + sin_theta * r[axis_1_idx_] + cos_theta * r[axis_2_idx_], r[zero_axis_idx]}; + Direction new_u = {cos_theta * u[axis_1_idx_] - sin_theta * u[axis_2_idx_], + sin_theta * u[axis_1_idx_] + cos_theta * u[axis_2_idx_], u[zero_axis_idx]}; // Handle the effects of the surface albedo on the particle's weight. BoundaryCondition::handle_albedo(p, surf); From 5544b3e67d28c231e717cef588e1cde4b18792cc Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Thu, 6 Nov 2025 10:25:23 -0600 Subject: [PATCH 05/13] remove lingering check for z periodicity. get tests actually setup correctly --- src/boundary_condition.cpp | 15 ------------- tests/regression_tests/periodic_cyls/test.py | 22 +++++++++++++------- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/src/boundary_condition.cpp b/src/boundary_condition.cpp index 4b2136a2e50..9d5a6098808 100644 --- a/src/boundary_condition.cpp +++ b/src/boundary_condition.cpp @@ -191,21 +191,6 @@ RotationalPeriodicBC::RotationalPeriodicBC( // to the correct axis Direction norm1 = surf1.normal({0, 0, 0}); Direction norm2 = surf2.normal({0, 0, 0}); - if (std::abs(norm1[zero_axis_idx]) > FP_PRECISION) { - throw std::invalid_argument(fmt::format( - "Rotational periodic BCs are only " - "supported for rotations about the z-axis, but surface {} is not " - "perpendicular to the z-axis.", - surf1.id_)); - } - if (std::abs(norm2[zero_axis_idx]) > FP_PRECISION) { - throw std::invalid_argument(fmt::format( - "Rotational periodic BCs are only " - "supported for rotations about the z-axis, but surface {} is not " - "perpendicular to the z-axis.", - surf2.id_)); - } - // Make sure both surfaces intersect the origin if (std::abs(surf1.evaluate({0, 0, 0})) > FP_COINCIDENT) { throw std::invalid_argument(fmt::format( diff --git a/tests/regression_tests/periodic_cyls/test.py b/tests/regression_tests/periodic_cyls/test.py index e3bc293a320..a341e379944 100644 --- a/tests/regression_tests/periodic_cyls/test.py +++ b/tests/regression_tests/periodic_cyls/test.py @@ -1,7 +1,7 @@ import openmc -import numpy +import numpy as np import pytest - +from openmc.utility_funcs import change_directory from tests.testing_harness import PyAPITestHarness @@ -40,6 +40,7 @@ def xcyl_model(): model.settings.source = openmc.IndependentSource(space=openmc.stats.Box( (0, 0, 0), (20, 20, 20)) ) + return model @pytest.fixture def ycyl_model(): @@ -75,11 +76,16 @@ def ycyl_model(): model.settings.source = openmc.IndependentSource(space=openmc.stats.Box( (0, 0, 0), (20, 20, 20)) ) + return model - -@pytest.mark.parametrize('cyl_model', ['xcyl_model','ycyl_model']) -def test_periodic(cyl_model): - with change_directory(cyl_model): - harness = PyAPITestHarness('statepoint.4.h5', cyl_model) +def test_xcyl(xcyl_model): + with change_directory("xcyl_model"): + openmc.reset_auto_ids() + harness = PyAPITestHarness('statepoint.4.h5', xcyl_model) harness.main() - assert \ No newline at end of file + +def test_ycyl(ycyl_model): + with change_directory("ycyl_model"): + openmc.reset_auto_ids() + harness = PyAPITestHarness('statepoint.4.h5', ycyl_model) + harness.main() \ No newline at end of file From 022dfc2ec5fe84f4dd64d48ae6b122436d349e0b Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Thu, 6 Nov 2025 13:49:55 -0600 Subject: [PATCH 06/13] acccount for flip in sign for y rotation. assign components of new_r and new_u for correctness --- include/openmc/boundary_condition.h | 2 +- src/boundary_condition.cpp | 44 ++++++++++++++++++++--------- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/include/openmc/boundary_condition.h b/include/openmc/boundary_condition.h index b5139536ea3..9cfd6cce0be 100644 --- a/include/openmc/boundary_condition.h +++ b/include/openmc/boundary_condition.h @@ -155,7 +155,7 @@ class RotationalPeriodicBC : public PeriodicBC { //! Ensure that choice of axes is right handed. axis_1_idx_ corresponds to the //! independent axis and axis_2_idx_ corresponds to the dependent axis in the //! 2D plane perpendicular to the planes' axis of rotation - int zero_axis_idx; + int zero_axis_idx_; int axis_1_idx_; int axis_2_idx_; }; diff --git a/src/boundary_condition.cpp b/src/boundary_condition.cpp index 9d5a6098808..264592e8711 100644 --- a/src/boundary_condition.cpp +++ b/src/boundary_condition.cpp @@ -168,19 +168,19 @@ RotationalPeriodicBC::RotationalPeriodicBC( // below convention for right handed coordinate system switch (axis) { case x: - zero_axis_idx = 0; // x component of plane must be zero - axis_1_idx_ = 1; // y component independent - axis_2_idx_ = 2; // z component dependent + zero_axis_idx_ = 0; // x component of plane must be zero + axis_1_idx_ = 1; // y component independent + axis_2_idx_ = 2; // z component dependent break; case y: - zero_axis_idx = 1; // y component of plane must be zero - axis_1_idx_ = 2; // z component independent - axis_2_idx_ = 0; // x component dependent + zero_axis_idx_ = 1; // y component of plane must be zero + axis_1_idx_ = 2; // z component independent + axis_2_idx_ = 0; // x component dependent break; case z: - zero_axis_idx = 2; // z component of plane must be zero - axis_1_idx_ = 0; // x component independent - axis_2_idx_ = 1; // y component dependent + zero_axis_idx_ = 2; // z component of plane must be zero + axis_1_idx_ = 0; // x component independent + axis_2_idx_ = 1; // y component dependent break; default: throw std::invalid_argument( @@ -261,10 +261,28 @@ void RotationalPeriodicBC::handle_particle( Direction u = p.u(); double cos_theta = std::cos(theta); double sin_theta = std::sin(theta); - Position new_r = {cos_theta * r[axis_1_idx_] - sin_theta * r[axis_2_idx_], - sin_theta * r[axis_1_idx_] + cos_theta * r[axis_2_idx_], r[zero_axis_idx]}; - Direction new_u = {cos_theta * u[axis_1_idx_] - sin_theta * u[axis_2_idx_], - sin_theta * u[axis_1_idx_] + cos_theta * u[axis_2_idx_], u[zero_axis_idx]}; + + // rotations around the y-axis have sign flipped for the sin_theta terms + int flip; + if (zero_axis_idx_ == 1) { + flip = -1; + } else { + flip = 1; + } + + Position new_r; + new_r[zero_axis_idx_] = r[zero_axis_idx_]; + new_r[axis_1_idx_] = + cos_theta * r[axis_1_idx_] - flip * sin_theta * r[axis_2_idx_]; + new_r[axis_2_idx_] = + flip * sin_theta * r[axis_1_idx_] + cos_theta * r[axis_2_idx_]; + + Direction new_u; + new_u[zero_axis_idx_] = u[zero_axis_idx_]; + new_u[axis_1_idx_] = + cos_theta * u[axis_1_idx_] - flip * sin_theta * u[axis_2_idx_]; + new_u[axis_2_idx_] = + flip * sin_theta * u[axis_1_idx_] + cos_theta * u[axis_2_idx_]; // Handle the effects of the surface albedo on the particle's weight. BoundaryCondition::handle_albedo(p, surf); From 31096651dc42b790002e16683530aee55cfa772a Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Tue, 18 Nov 2025 16:04:55 -0600 Subject: [PATCH 07/13] put types back to double --- include/openmc/boundary_condition.h | 4 ++-- src/boundary_condition.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/openmc/boundary_condition.h b/include/openmc/boundary_condition.h index 9cfd6cce0be..1325434a3d3 100644 --- a/include/openmc/boundary_condition.h +++ b/include/openmc/boundary_condition.h @@ -145,8 +145,8 @@ class RotationalPeriodicBC : public PeriodicBC { public: enum PeriodicAxis { x, y, z }; RotationalPeriodicBC(int i_surf, int j_surf, PeriodicAxis axis = z); - float compute_periodic_rotation( - float rise_1, float run_1, float rise_2, float run_2) const; + double compute_periodic_rotation( + double rise_1, double run_1, double rise_2, double run_2) const; void handle_particle(Particle& p, const Surface& surf) const override; protected: diff --git a/src/boundary_condition.cpp b/src/boundary_condition.cpp index 264592e8711..32fffff7384 100644 --- a/src/boundary_condition.cpp +++ b/src/boundary_condition.cpp @@ -220,8 +220,8 @@ RotationalPeriodicBC::RotationalPeriodicBC( } } -float RotationalPeriodicBC::compute_periodic_rotation( - float rise_1, float run_1, float rise_2, float run_2) const +double RotationalPeriodicBC::compute_periodic_rotation( + double rise_1, double run_1, double rise_2, double run_2) const { // Compute the BC rotation angle. Here it is assumed that both surface // normal vectors point inwards---towards the valid geometry region. From c84881259225058dc828ec63c419ae95d07c0255 Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Wed, 19 Nov 2025 12:58:26 -0600 Subject: [PATCH 08/13] add error checking to determine which periodic axis is specified by the user --- include/openmc/boundary_condition.h | 2 +- src/surface.cpp | 33 +++++++++++++++++-- .../periodic_cyls/xcyl_model/results_true.dat | 2 ++ .../periodic_cyls/ycyl_model/inputs_true.dat | 14 ++++---- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/include/openmc/boundary_condition.h b/include/openmc/boundary_condition.h index 1325434a3d3..5a14239e8ba 100644 --- a/include/openmc/boundary_condition.h +++ b/include/openmc/boundary_condition.h @@ -144,7 +144,7 @@ class TranslationalPeriodicBC : public PeriodicBC { class RotationalPeriodicBC : public PeriodicBC { public: enum PeriodicAxis { x, y, z }; - RotationalPeriodicBC(int i_surf, int j_surf, PeriodicAxis axis = z); + RotationalPeriodicBC(int i_surf, int j_surf, PeriodicAxis axis); double compute_periodic_rotation( double rise_1, double run_1, double rise_2, double run_2) const; void handle_particle(Particle& p, const Surface& surf) const override; diff --git a/src/surface.cpp b/src/surface.cpp index ea19514a311..ee79a0685e6 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -1334,8 +1334,37 @@ void read_surfaces(pugi::xml_node node) surf1.bc_ = make_unique(i_surf, j_surf); surf2.bc_ = make_unique(i_surf, j_surf); } else { - surf1.bc_ = make_unique(i_surf, j_surf); - surf2.bc_ = make_unique(i_surf, j_surf); + // check that both normals have at least one 0 component + if (norm1.x != 0.0 && norm1.y != 0.0 && norm1.z != 0.0) { + fatal_error(fmt::format( + "The normal ({}) of the periodic surface ({}) does not contain any " + "component with a zero value. A RotationalPeriodicBC requires one " + "component which is zero for both plane normals.", + norm1, i_surf)); + } + if (norm2.x != 0.0 && norm2.y != 0.0 && norm2.z != 0.0) { + fatal_error(fmt::format( + "The normal ({}) of the periodic surface ({}) does not contain any " + "component with a zero value. A RotationalPeriodicBC requires one " + "component which is zero for both plane normals.", + norm2, j_surf)); + } + // find common zero component, which indicates the periodic axis + RotationalPeriodicBC::PeriodicAxis axis; + if (norm1.x == 0.0 && norm2.x == 0.0) { + axis = RotationalPeriodicBC::PeriodicAxis::x; + } else if (norm1.y == 0.0 && norm2.y == 0.0) { + axis = RotationalPeriodicBC::PeriodicAxis::y; + } else if (norm1.z == 0.0 && norm2.z == 0.0) { + axis = RotationalPeriodicBC::PeriodicAxis::z; + } else { + fatal_error(fmt::format( + "There is no component which is 0.0 in both normal vectors. This " + "indicates that the two planes are not periodic about the X, Y, or Z " + "axis, which is not supported.")); + } + surf1.bc_ = make_unique(i_surf, j_surf, axis); + surf2.bc_ = make_unique(i_surf, j_surf, axis); } // If albedo data is present in albedo map, set the boundary albedo. diff --git a/tests/regression_tests/periodic_cyls/xcyl_model/results_true.dat b/tests/regression_tests/periodic_cyls/xcyl_model/results_true.dat index e69de29bb2d..c7e3eb67031 100644 --- a/tests/regression_tests/periodic_cyls/xcyl_model/results_true.dat +++ b/tests/regression_tests/periodic_cyls/xcyl_model/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +1.082283E+00 6.676373E-02 diff --git a/tests/regression_tests/periodic_cyls/ycyl_model/inputs_true.dat b/tests/regression_tests/periodic_cyls/ycyl_model/inputs_true.dat index 8ce785ed749..f3ca7e0f4a4 100644 --- a/tests/regression_tests/periodic_cyls/ycyl_model/inputs_true.dat +++ b/tests/regression_tests/periodic_cyls/ycyl_model/inputs_true.dat @@ -1,19 +1,19 @@ - + - - - - - - + + + + + + eigenvalue From 6cf5bfc86a4592a2f446ee29c9b2cabbd9d65aca Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Wed, 19 Nov 2025 16:24:47 -0600 Subject: [PATCH 09/13] use ternary operator to multiply the computed angle by -1 when we have a PeriodicAxis y --- src/boundary_condition.cpp | 10 ++++++++-- .../periodic_cyls/ycyl_model/results_true.dat | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/boundary_condition.cpp b/src/boundary_condition.cpp index 32fffff7384..eae61cbe1d4 100644 --- a/src/boundary_condition.cpp +++ b/src/boundary_condition.cpp @@ -207,8 +207,14 @@ RotationalPeriodicBC::RotationalPeriodicBC( surf2.id_)); } - angle_ = compute_periodic_rotation(norm1[axis_2_idx_], norm1[axis_1_idx_], - norm2[axis_2_idx_], norm2[axis_1_idx_]); + // reverse the angle computed if there is y-periodicity to account for + // reversal of axes cross product direction + angle_ = + (zero_axis_idx_ != 1) + ? compute_periodic_rotation(norm1[axis_2_idx_], norm1[axis_1_idx_], + norm2[axis_2_idx_], norm2[axis_1_idx_]) + : -1 * compute_periodic_rotation(norm1[axis_2_idx_], norm1[axis_1_idx_], + norm2[axis_2_idx_], norm2[axis_1_idx_]); // Warn the user if the angle does not evenly divide a circle double rem = std::abs(std::remainder((2 * PI / angle_), 1.0)); diff --git a/tests/regression_tests/periodic_cyls/ycyl_model/results_true.dat b/tests/regression_tests/periodic_cyls/ycyl_model/results_true.dat index e69de29bb2d..562467f2cd6 100644 --- a/tests/regression_tests/periodic_cyls/ycyl_model/results_true.dat +++ b/tests/regression_tests/periodic_cyls/ycyl_model/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +1.082652E+00 3.316031E-02 From bd9e67ec4ce20757d8194e0b2200bd6748fd581c Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Mon, 8 Dec 2025 14:02:48 -0600 Subject: [PATCH 10/13] switch axis_1 and axis_2 for y case to make it conform with x and z cases. remove y specific logic --- src/boundary_condition.cpp | 37 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/src/boundary_condition.cpp b/src/boundary_condition.cpp index eae61cbe1d4..c139e995e09 100644 --- a/src/boundary_condition.cpp +++ b/src/boundary_condition.cpp @@ -173,9 +173,14 @@ RotationalPeriodicBC::RotationalPeriodicBC( axis_2_idx_ = 2; // z component dependent break; case y: + // for a right handed coordinate system, z should be the independent axis + // but this would cause the y-rotation case to be different than the other + // two. using a left handed coordinate system and a negative rotation the + // compute angle and rotation matrix behavior mimics that of the x and z + // cases zero_axis_idx_ = 1; // y component of plane must be zero - axis_1_idx_ = 2; // z component independent - axis_2_idx_ = 0; // x component dependent + axis_1_idx_ = 0; // x component independent + axis_2_idx_ = 2; // z component dependent break; case z: zero_axis_idx_ = 2; // z component of plane must be zero @@ -209,12 +214,8 @@ RotationalPeriodicBC::RotationalPeriodicBC( // reverse the angle computed if there is y-periodicity to account for // reversal of axes cross product direction - angle_ = - (zero_axis_idx_ != 1) - ? compute_periodic_rotation(norm1[axis_2_idx_], norm1[axis_1_idx_], - norm2[axis_2_idx_], norm2[axis_1_idx_]) - : -1 * compute_periodic_rotation(norm1[axis_2_idx_], norm1[axis_1_idx_], - norm2[axis_2_idx_], norm2[axis_1_idx_]); + angle_ = compute_periodic_rotation(norm1[axis_2_idx_], norm1[axis_1_idx_], + norm2[axis_2_idx_], norm2[axis_1_idx_]); // Warn the user if the angle does not evenly divide a circle double rem = std::abs(std::remainder((2 * PI / angle_), 1.0)); @@ -268,27 +269,15 @@ void RotationalPeriodicBC::handle_particle( double cos_theta = std::cos(theta); double sin_theta = std::sin(theta); - // rotations around the y-axis have sign flipped for the sin_theta terms - int flip; - if (zero_axis_idx_ == 1) { - flip = -1; - } else { - flip = 1; - } - Position new_r; new_r[zero_axis_idx_] = r[zero_axis_idx_]; - new_r[axis_1_idx_] = - cos_theta * r[axis_1_idx_] - flip * sin_theta * r[axis_2_idx_]; - new_r[axis_2_idx_] = - flip * sin_theta * r[axis_1_idx_] + cos_theta * r[axis_2_idx_]; + new_r[axis_1_idx_] = cos_theta * r[axis_1_idx_] - sin_theta * r[axis_2_idx_]; + new_r[axis_2_idx_] = sin_theta * r[axis_1_idx_] + cos_theta * r[axis_2_idx_]; Direction new_u; new_u[zero_axis_idx_] = u[zero_axis_idx_]; - new_u[axis_1_idx_] = - cos_theta * u[axis_1_idx_] - flip * sin_theta * u[axis_2_idx_]; - new_u[axis_2_idx_] = - flip * sin_theta * u[axis_1_idx_] + cos_theta * u[axis_2_idx_]; + new_u[axis_1_idx_] = cos_theta * u[axis_1_idx_] - sin_theta * u[axis_2_idx_]; + new_u[axis_2_idx_] = sin_theta * u[axis_1_idx_] + cos_theta * u[axis_2_idx_]; // Handle the effects of the surface albedo on the particle's weight. BoundaryCondition::handle_albedo(p, surf); From 43ca2a315108e843c97a47a453148e4eb4052e3f Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Mon, 8 Dec 2025 19:30:15 -0600 Subject: [PATCH 11/13] update docs references to periodic boundary conditions --- docs/source/io_formats/geometry.rst | 8 +++----- docs/source/usersguide/geometry.rst | 2 +- openmc/surface.py | 9 +++------ 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/docs/source/io_formats/geometry.rst b/docs/source/io_formats/geometry.rst index 6d0a37a24fa..dda1efa9aee 100644 --- a/docs/source/io_formats/geometry.rst +++ b/docs/source/io_formats/geometry.rst @@ -38,11 +38,9 @@ Each ```` element can have the following attributes or sub-elements: :boundary: The boundary condition for the surface. This can be "transmission", - "vacuum", "reflective", or "periodic". Periodic boundary conditions can - only be applied to x-, y-, and z-planes. Only axis-aligned periodicity is - supported, i.e., x-planes can only be paired with x-planes. Specify which - planes are periodic and the code will automatically identify which planes - are paired together. + "vacuum", "reflective", or "periodic". Specify which planes are + periodic and the code will automatically identify which planes are + paired together. *Default*: "transmission" diff --git a/docs/source/usersguide/geometry.rst b/docs/source/usersguide/geometry.rst index 6f14ebfa51c..8c68e485163 100644 --- a/docs/source/usersguide/geometry.rst +++ b/docs/source/usersguide/geometry.rst @@ -192,7 +192,7 @@ Otherwise it is necessary to specify pairs explicitly using the Both rotational and translational periodic boundary conditions are specified in the same fashion. If both planes have the same normal vector, a translational periodicity is assumed; rotational periodicity is assumed otherwise. Currently, -only rotations about the :math:`z`-axis are supported. +rotations must be about the :math:`x`-, :math:`y`-, or :math:`z`-axis. For a rotational periodic BC, the normal vectors of each surface must point inwards---towards the valid geometry. For example, a :class:`XPlane` and diff --git a/openmc/surface.py b/openmc/surface.py index 53b30841443..87abdbb5ede 100644 --- a/openmc/surface.py +++ b/openmc/surface.py @@ -821,8 +821,7 @@ class XPlane(PlaneMixin, Surface): boundary_type : {'transmission', 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles - freely pass through the surface. Only axis-aligned periodicity is - supported, i.e., x-planes can only be paired with x-planes. + freely pass through the surface. albedo : float, optional Albedo of the surfaces as a ratio of particle weight after interaction with the surface to the initial weight. Values must be positive. Only @@ -886,8 +885,7 @@ class YPlane(PlaneMixin, Surface): boundary_type : {'transmission', 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles - freely pass through the surface. Only axis-aligned periodicity is - supported, i.e., y-planes can only be paired with y-planes. + freely pass through the surface. albedo : float, optional Albedo of the surfaces as a ratio of particle weight after interaction with the surface to the initial weight. Values must be positive. Only @@ -951,8 +949,7 @@ class ZPlane(PlaneMixin, Surface): boundary_type : {'transmission', 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles - freely pass through the surface. Only axis-aligned periodicity is - supported, i.e., z-planes can only be paired with z-planes. + freely pass through the surface. albedo : float, optional Albedo of the surfaces as a ratio of particle weight after interaction with the surface to the initial weight. Values must be positive. Only From 6ecffe6333871bcbbf5e8c024f38558e61d09908 Mon Sep 17 00:00:00 2001 From: Lewis Gross <43077972+lewisgross1296@users.noreply.github.com> Date: Tue, 9 Dec 2025 09:22:12 -0600 Subject: [PATCH 12/13] Apply suggestions from GuySten to not compare to literal 0 Co-authored-by: GuySten <62616591+GuySten@users.noreply.github.com> --- src/boundary_condition.cpp | 2 -- src/surface.cpp | 17 ++++++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/boundary_condition.cpp b/src/boundary_condition.cpp index c139e995e09..2840b3c7d52 100644 --- a/src/boundary_condition.cpp +++ b/src/boundary_condition.cpp @@ -212,8 +212,6 @@ RotationalPeriodicBC::RotationalPeriodicBC( surf2.id_)); } - // reverse the angle computed if there is y-periodicity to account for - // reversal of axes cross product direction angle_ = compute_periodic_rotation(norm1[axis_2_idx_], norm1[axis_1_idx_], norm2[axis_2_idx_], norm2[axis_1_idx_]); diff --git a/src/surface.cpp b/src/surface.cpp index ee79a0685e6..09fc0f24f09 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -1335,14 +1335,18 @@ void read_surfaces(pugi::xml_node node) surf2.bc_ = make_unique(i_surf, j_surf); } else { // check that both normals have at least one 0 component - if (norm1.x != 0.0 && norm1.y != 0.0 && norm1.z != 0.0) { + if (std::abs(norm1.x) > FP_PRECISION && + std::abs(norm1.y) > FP_PRECISION && + std::abs(norm1.z) > FP_PRECISION) { fatal_error(fmt::format( "The normal ({}) of the periodic surface ({}) does not contain any " "component with a zero value. A RotationalPeriodicBC requires one " "component which is zero for both plane normals.", norm1, i_surf)); } - if (norm2.x != 0.0 && norm2.y != 0.0 && norm2.z != 0.0) { + if (std::abs(norm2.x) > FP_PRECISION && + std::abs(norm2.y) > FP_PRECISION && + std::abs(norm2.z) > FP_PRECISION) { fatal_error(fmt::format( "The normal ({}) of the periodic surface ({}) does not contain any " "component with a zero value. A RotationalPeriodicBC requires one " @@ -1351,11 +1355,14 @@ void read_surfaces(pugi::xml_node node) } // find common zero component, which indicates the periodic axis RotationalPeriodicBC::PeriodicAxis axis; - if (norm1.x == 0.0 && norm2.x == 0.0) { + if (std::abs(norm1.x) <= FP_PRECISION && + std::abs(norm2.x) <= FP_PRECISION) { axis = RotationalPeriodicBC::PeriodicAxis::x; - } else if (norm1.y == 0.0 && norm2.y == 0.0) { + } else if (std::abs(norm1.y) <= FP_PRECISION && + std::abs(norm2.y) <= FP_PRECISION) { axis = RotationalPeriodicBC::PeriodicAxis::y; - } else if (norm1.z == 0.0 && norm2.z == 0.0) { + } else if (std::abs(norm1.z) <= FP_PRECISION && + std::abs(norm2.z) <= FP_PRECISION) { axis = RotationalPeriodicBC::PeriodicAxis::z; } else { fatal_error(fmt::format( From e373536803808f6b649c8642880ff5ecedb36ba0 Mon Sep 17 00:00:00 2001 From: GuySten <62616591+GuySten@users.noreply.github.com> Date: Tue, 9 Dec 2025 17:41:07 +0200 Subject: [PATCH 13/13] Update openmc/surface.py --- openmc/surface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/surface.py b/openmc/surface.py index 87abdbb5ede..1fe5fabdf7b 100644 --- a/openmc/surface.py +++ b/openmc/surface.py @@ -124,7 +124,7 @@ class Surface(IDManagerMixin, ABC): Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. Note that only axis-aligned - periodicity is supported periodic around the x-, y-, and z-axes. + periodicity is supported around the x-, y-, and z-axes. albedo : float, optional Albedo of the surfaces as a ratio of particle weight after interaction with the surface to the initial weight. Values must be positive. Only