Skip to content

Commit f77aec1

Browse files
authored
Merge pull request #2443 from pshriwise/sph-cyl-mesh-fix
Improvements to spherical/cylindrical mesh radial boundary coincidence with geometry
2 parents 3068d0c + 6cb4401 commit f77aec1

File tree

5 files changed

+213
-7
lines changed

5 files changed

+213
-7
lines changed

include/openmc/constants.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ constexpr double FP_PRECISION {1e-14};
5151
constexpr double FP_REL_PRECISION {1e-5};
5252
constexpr double FP_COINCIDENT {1e-12};
5353

54+
// Coincidence tolerances
55+
constexpr double TORUS_TOL {1e-10};
56+
constexpr double RADIAL_MESH_TOL {1e-10};
57+
5458
// Maximum number of random samples per history
5559
constexpr int MAX_SAMPLE {100000};
5660

src/mesh.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,6 +1066,9 @@ double CylindricalMesh::find_r_crossing(
10661066
// s^2 * (u^2 + v^2) + 2*s*(u*x+v*y) + x^2+y^2-r0^2 = 0
10671067

10681068
const double r0 = grid_[0][shell];
1069+
if (r0 == 0.0)
1070+
return INFTY;
1071+
10691072
const double denominator = u.x * u.x + u.y * u.y;
10701073

10711074
// Direction of flight is in z-direction. Will never intersect r.
@@ -1085,7 +1088,10 @@ double CylindricalMesh::find_r_crossing(
10851088
D = std::sqrt(D);
10861089

10871090
// the solution -p - D is always smaller as -p + D : Check this one first
1088-
if (-p - D > l && std::abs(c) > 1e-10)
1091+
if (std::abs(c) <= RADIAL_MESH_TOL)
1092+
return INFTY;
1093+
1094+
if (-p - D > l)
10891095
return -p - D;
10901096
if (-p + D > l)
10911097
return -p + D;
@@ -1304,14 +1310,19 @@ double SphericalMesh::find_r_crossing(
13041310
// solve |r+s*u| = r0
13051311
// |r+s*u| = |r| + 2*s*r*u + s^2 (|u|==1 !)
13061312
const double r0 = grid_[0][shell];
1313+
if (r0 == 0.0)
1314+
return INFTY;
13071315
const double p = r.dot(u);
13081316
double c = r.dot(r) - r0 * r0;
13091317
double D = p * p - c;
13101318

1319+
if (std::abs(c) <= RADIAL_MESH_TOL)
1320+
return INFTY;
1321+
13111322
if (D >= 0.0) {
13121323
D = std::sqrt(D);
13131324
// the solution -p - D is always smaller as -p + D : Check this one first
1314-
if (-p - D > l && std::abs(c) > 1e-10)
1325+
if (-p - D > l)
13151326
return -p - D;
13161327
if (-p + D > l)
13171328
return -p + D;

src/surface.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1044,7 +1044,7 @@ double torus_distance(double x1, double x2, double x3, double u1, double u2,
10441044
// zero but possibly small and positive. A tolerance is set to discard that
10451045
// zero.
10461046
double distance = INFTY;
1047-
double cutoff = coincident ? 1e-10 : 0.0;
1047+
double cutoff = coincident ? TORUS_TOL : 0.0;
10481048
for (int i = 0; i < 4; ++i) {
10491049
if (roots[i].imag() == 0) {
10501050
double root = roots[i].real();

tests/unit_tests/test_cylindrical_mesh.py

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def label(p):
7575
return f'estimator:{p}'
7676

7777
@pytest.mark.parametrize('estimator,origin', test_cases, ids=label)
78-
def test_offset_mesh(model, estimator, origin):
78+
def test_offset_mesh(run_in_tmpdir, model, estimator, origin):
7979
"""Tests that the mesh has been moved based on tally results
8080
"""
8181
mesh = model.tallies[0].filters[0].mesh
@@ -98,8 +98,99 @@ def test_offset_mesh(model, estimator, origin):
9898
centroids = mesh.centroids
9999
for ijk in mesh.indices:
100100
i, j, k = np.array(ijk) - 1
101-
print(centroids[:, i, j, k])
102101
if model.geometry.find(centroids[:, i, j, k]):
103102
mean[i, j, k] == 0.0
104103
else:
105104
mean[i, j, k] != 0.0
105+
106+
@pytest.fixture()
107+
def void_coincident_geom_model():
108+
"""A model with many geometric boundaries coincident with mesh boundaries
109+
across many scales
110+
"""
111+
openmc.reset_auto_ids()
112+
model = openmc.model.Model()
113+
114+
model.materials = openmc.Materials()
115+
radii = [0.1,1, 5, 50, 100, 150, 250]
116+
cylinders = [openmc.ZCylinder(r=ri) for ri in radii]
117+
cylinders[-1].boundary_type = 'vacuum'
118+
119+
regions = openmc.model.subdivide(cylinders)[:-1]
120+
cells = [openmc.Cell(region=r, fill=None) for r in regions]
121+
geom = openmc.Geometry(cells)
122+
123+
model.geometry = geom
124+
125+
settings = openmc.Settings(run_mode='fixed source')
126+
settings.batches = 2
127+
settings.particles = 1000
128+
model.settings = settings
129+
130+
mesh = openmc.CylindricalMesh()
131+
mesh.r_grid = np.linspace(0, 250, 501)
132+
mesh.z_grid = [-250, 250]
133+
mesh.phi_grid = np.linspace(0, 2*np.pi, 2)
134+
mesh_filter = openmc.MeshFilter(mesh)
135+
136+
tally = openmc.Tally()
137+
tally.scores = ['flux']
138+
tally.filters = [mesh_filter]
139+
140+
model.tallies = openmc.Tallies([tally])
141+
142+
return model
143+
144+
145+
# convenience function for checking tally results
146+
# in the following tests
147+
def _check_void_cylindrical_tally(statepoint_filename):
148+
with openmc.StatePoint(statepoint_filename) as sp:
149+
flux_tally = sp.tallies[1]
150+
mesh = flux_tally.find_filter(openmc.MeshFilter).mesh
151+
neutron_flux = flux_tally.get_reshaped_data().squeeze()
152+
# we expect the tally results to be the same as the mesh grid width
153+
# for these cases
154+
d_r = mesh.r_grid[1] - mesh.r_grid[0]
155+
assert neutron_flux == pytest.approx(d_r)
156+
157+
def test_void_geom_pnt_src(run_in_tmpdir, void_coincident_geom_model):
158+
src = openmc.Source()
159+
src.space = openmc.stats.Point()
160+
src.angle = openmc.stats.PolarAzimuthal(mu=openmc.stats.Discrete([0.0], [1.0]))
161+
src.energy = openmc.stats.Discrete([14.06e6], [1])
162+
void_coincident_geom_model.settings.source = src
163+
164+
sp_filename = void_coincident_geom_model.run()
165+
_check_void_cylindrical_tally(sp_filename)
166+
167+
168+
def test_void_geom_boundary_src(run_in_tmpdir, void_coincident_geom_model):
169+
# update source to a number of points on the outside of the cylinder
170+
# with directions pointing toward the origin
171+
bbox = void_coincident_geom_model.geometry.bounding_box
172+
173+
# can't source particle directly on the geometry boundary
174+
outer_r = bbox[1][0] - 1e-08
175+
176+
n_sources = 100
177+
radial_vals = np.linspace(0.0, 2.0*np.pi, n_sources)
178+
179+
sources = []
180+
181+
energy = openmc.stats.Discrete([14.06e6], [1])
182+
for val in radial_vals:
183+
src = openmc.Source()
184+
src.energy = energy
185+
186+
pnt = np.array([np.cos(val), np.sin(val), 0.0])
187+
u = -pnt
188+
src.space = openmc.stats.Point(outer_r*pnt)
189+
src.angle = openmc.stats.Monodirectional(u)
190+
src.strength = 0.5/n_sources
191+
sources.append(src)
192+
193+
void_coincident_geom_model.settings.source = sources
194+
sp_filename = void_coincident_geom_model.run()
195+
196+
_check_void_cylindrical_tally(sp_filename)

tests/unit_tests/test_spherical_mesh.py

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def label(p):
7979
return f'estimator:{p}'
8080

8181
@pytest.mark.parametrize('estimator,origin', test_cases, ids=label)
82-
def test_offset_mesh(model, estimator, origin):
82+
def test_offset_mesh(run_in_tmpdir, model, estimator, origin):
8383
"""Tests that the mesh has been moved based on tally results
8484
"""
8585
mesh = model.tallies[0].filters[0].mesh
@@ -102,8 +102,108 @@ def test_offset_mesh(model, estimator, origin):
102102
centroids = mesh.centroids
103103
for ijk in mesh.indices:
104104
i, j, k = np.array(ijk) - 1
105-
print(centroids[:, i, j, k])
106105
if model.geometry.find(centroids[:, i, j, k]):
107106
mean[i, j, k] == 0.0
108107
else:
109108
mean[i, j, k] != 0.0
109+
110+
# Some void geometry tests to check our radial intersection methods on
111+
# spherical and cylindrical meshes
112+
113+
@pytest.fixture()
114+
def void_coincident_geom_model():
115+
"""A model with many geometric boundaries coincident with mesh boundaries
116+
across many scales
117+
"""
118+
openmc.reset_auto_ids()
119+
120+
model = openmc.Model()
121+
122+
model.materials = openmc.Materials()
123+
radii = [0.1, 1, 5, 50, 100, 150, 250]
124+
spheres = [openmc.Sphere(r=ri) for ri in radii]
125+
spheres[-1].boundary_type = 'vacuum'
126+
127+
regions = openmc.model.subdivide(spheres)[:-1]
128+
cells = [openmc.Cell(region=r, fill=None) for r in regions]
129+
geom = openmc.Geometry(cells)
130+
131+
model.geometry = geom
132+
133+
settings = openmc.Settings(run_mode='fixed source')
134+
settings.batches = 2
135+
settings.particles = 5000
136+
model.settings = settings
137+
138+
mesh = openmc.SphericalMesh()
139+
mesh.r_grid = np.linspace(0, 250, 501)
140+
mesh_filter = openmc.MeshFilter(mesh)
141+
142+
tally = openmc.Tally()
143+
tally.scores = ['flux']
144+
tally.filters = [mesh_filter]
145+
146+
model.tallies = openmc.Tallies([tally])
147+
148+
return model
149+
150+
151+
# convenience function for checking tally results
152+
# in the following tests
153+
def _check_void_spherical_tally(statepoint_filename):
154+
with openmc.StatePoint(statepoint_filename) as sp:
155+
flux_tally = sp.tallies[1]
156+
mesh = flux_tally.find_filter(openmc.MeshFilter).mesh
157+
neutron_flux = flux_tally.get_reshaped_data().squeeze()
158+
# the flux values for each bin should equal the width
159+
# width of the mesh bins
160+
d_r = mesh.r_grid[1] - mesh.r_grid[0]
161+
assert neutron_flux == pytest.approx(d_r)
162+
163+
164+
def test_void_geom_pnt_src(run_in_tmpdir, void_coincident_geom_model):
165+
# add isotropic point source
166+
src = openmc.Source()
167+
src.space = openmc.stats.Point()
168+
src.energy = openmc.stats.Discrete([14.06e6], [1])
169+
void_coincident_geom_model.settings.source = src
170+
171+
# run model and check tally results
172+
sp_filename = void_coincident_geom_model.run()
173+
_check_void_spherical_tally(sp_filename)
174+
175+
176+
def test_void_geom_boundary_src(run_in_tmpdir, void_coincident_geom_model):
177+
# update source to a number of points on the outside of the sphere
178+
# with directions pointing toward the origin
179+
n_sources = 20
180+
phi_vals = np.linspace(0, np.pi, n_sources)
181+
theta_vals = np.linspace(0, 2.0*np.pi, n_sources)
182+
183+
bbox = void_coincident_geom_model.geometry.bounding_box
184+
# can't source particles directly on the geometry boundary
185+
outer_r = bbox[1][0] - 1e-08
186+
187+
sources = []
188+
189+
energy = openmc.stats.Discrete([14.06e6], [1])
190+
191+
for phi, theta in zip(phi_vals, theta_vals):
192+
193+
src = openmc.Source()
194+
src.energy = energy
195+
196+
pnt = np.array([np.sin(phi)*np.cos(theta), np.sin(phi)*np.sin(theta), np.cos(phi)])
197+
u = -pnt
198+
src.space = openmc.stats.Point(outer_r*pnt)
199+
src.angle = openmc.stats.Monodirectional(u)
200+
# set source strengths so that we can still expect
201+
# a tally value of 0.5
202+
src.strength = 0.5/n_sources
203+
204+
sources.append(src)
205+
206+
void_coincident_geom_model.settings.source = sources
207+
208+
sp_filename = void_coincident_geom_model.run()
209+
_check_void_spherical_tally(sp_filename)

0 commit comments

Comments
 (0)