Allow Mesh.from_domain to use bounding boxes directly (#3828)

This commit is contained in:
Jonathan Shimwell 2026-02-25 06:44:30 +01:00 committed by GitHub
parent e130701f10
commit 3ff6b59a49
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 49 additions and 25 deletions

View file

@ -1188,7 +1188,7 @@ class RegularMesh(StructuredMesh):
@classmethod
def from_domain(
cls,
domain: HasBoundingBox,
domain: HasBoundingBox | BoundingBox,
dimension: Sequence[int] | int = 1000,
mesh_id: int | None = None,
name: str = ''
@ -1197,10 +1197,12 @@ class RegularMesh(StructuredMesh):
Parameters
----------
domain : HasBoundingBox
domain : HasBoundingBox | openmc.BoundingBox
The object passed in will be used as a template for this mesh. The
bounding box of the property of the object passed will be used to
set the lower_left and upper_right and of the mesh instance
set the lower_left and upper_right and of the mesh instance.
Alternatively, a :class:`openmc.BoundingBox` can be passed
directly.
dimension : Iterable of int | int
The number of mesh cells in total or number of mesh cells in each
direction (x, y, z). If a single integer is provided, the domain
@ -1217,21 +1219,26 @@ class RegularMesh(StructuredMesh):
RegularMesh instance
"""
if not hasattr(domain, 'bounding_box'):
raise TypeError("Domain must have a bounding_box property")
if isinstance(domain, BoundingBox):
bb = domain
elif hasattr(domain, 'bounding_box'):
bb = domain.bounding_box
else:
raise TypeError("Domain must be a BoundingBox or have a "
"bounding_box property")
mesh = cls(mesh_id=mesh_id, name=name)
mesh.lower_left = domain.bounding_box[0]
mesh.upper_right = domain.bounding_box[1]
mesh.lower_left = bb[0]
mesh.upper_right = bb[1]
if isinstance(dimension, int):
cv.check_greater_than("dimension", dimension, 1, equality=True)
# If a single integer is provided, divide the domain into that many
# mesh cells with roughly equal lengths in each direction
ideal_cube_volume = domain.bounding_box.volume / dimension
ideal_cube_volume = bb.volume / dimension
ideal_cube_size = ideal_cube_volume ** (1 / 3)
dimension = [
max(1, int(round(side / ideal_cube_size)))
for side in domain.bounding_box.width
for side in bb.width
]
mesh.dimension = dimension
@ -1904,7 +1911,7 @@ class CylindricalMesh(StructuredMesh):
@classmethod
def from_domain(
cls,
domain: HasBoundingBox,
domain: HasBoundingBox | BoundingBox,
dimension: Sequence[int] = (10, 10, 10),
mesh_id: int | None = None,
phi_grid_bounds: Sequence[float] = (0.0, 2*pi),
@ -1915,10 +1922,11 @@ class CylindricalMesh(StructuredMesh):
Parameters
----------
domain : HasBoundingBox
domain : HasBoundingBox | openmc.BoundingBox
The object passed in will be used as a template for this mesh. The
bounding box of the property of the object passed will be used to
set the r_grid, z_grid ranges.
set the r_grid, z_grid ranges. Alternatively, a
:class:`openmc.BoundingBox` can be passed directly.
dimension : Iterable of int
The number of equally spaced mesh cells in each direction (r_grid,
phi_grid, z_grid)
@ -1939,11 +1947,13 @@ class CylindricalMesh(StructuredMesh):
CylindricalMesh instance
"""
if not hasattr(domain, 'bounding_box'):
raise TypeError("Domain must have a bounding_box property")
# loaded once to avoid recalculating bounding box
cached_bb = domain.bounding_box
if isinstance(domain, BoundingBox):
cached_bb = domain
elif hasattr(domain, 'bounding_box'):
cached_bb = domain.bounding_box
else:
raise TypeError("Domain must be a BoundingBox or have a "
"bounding_box property")
if enclose_domain:
outer_radius = 0.5 * np.linalg.norm(cached_bb.width[:2])
@ -2290,7 +2300,7 @@ class SphericalMesh(StructuredMesh):
@classmethod
def from_domain(
cls,
domain: HasBoundingBox,
domain: HasBoundingBox | BoundingBox,
dimension: Sequence[int] = (10, 10, 10),
mesh_id: int | None = None,
phi_grid_bounds: Sequence[float] = (0.0, 2*pi),
@ -2302,10 +2312,11 @@ class SphericalMesh(StructuredMesh):
Parameters
----------
domain : HasBoundingBox
domain : HasBoundingBox | openmc.BoundingBox
The object passed in will be used as a template for this mesh. The
bounding box of the property of the object passed will be used to
set the r_grid, phi_grid, and theta_grid ranges.
set the r_grid, phi_grid, and theta_grid ranges. Alternatively, a
:class:`openmc.BoundingBox` can be passed directly.
dimension : Iterable of int
The number of equally spaced mesh cells in each direction (r_grid,
phi_grid, theta_grid). Spacing is in angular space (radians) for
@ -2330,11 +2341,13 @@ class SphericalMesh(StructuredMesh):
SphericalMesh instance
"""
if not hasattr(domain, 'bounding_box'):
raise TypeError("Domain must have a bounding_box property")
# loaded once to avoid recalculating bounding box
cached_bb = domain.bounding_box
if isinstance(domain, BoundingBox):
cached_bb = domain
elif hasattr(domain, 'bounding_box'):
cached_bb = domain.bounding_box
else:
raise TypeError("Domain must be a BoundingBox or have a "
"bounding_box property")
if enclose_domain:
outer_radius = 0.5 * np.linalg.norm(cached_bb.width)

View file

@ -16,6 +16,17 @@ def test_reg_mesh_from_cell():
assert np.array_equal(mesh.upper_right, cell.bounding_box[1])
def test_reg_mesh_from_bounding_box():
"""Tests a RegularMesh can be made from a BoundingBox directly."""
bb = openmc.BoundingBox([-8, -7, -5], [12, 13, 15])
mesh = openmc.RegularMesh.from_domain(domain=bb, dimension=[7, 11, 13])
assert isinstance(mesh, openmc.RegularMesh)
assert np.array_equal(mesh.dimension, (7, 11, 13))
assert np.array_equal(mesh.lower_left, bb[0])
assert np.array_equal(mesh.upper_right, bb[1])
def test_cylindrical_mesh_from_cell():
"""Tests a CylindricalMesh can be made from a Cell and the specified
dimensions are propagated through."""