Refactor BoundingBox implementation

This commit is contained in:
Paul Romano 2023-04-18 22:04:46 -05:00
parent c4aafdd508
commit b5d30ff553
9 changed files with 96 additions and 104 deletions

View file

@ -87,6 +87,7 @@ Building geometry
openmc.Intersection
openmc.Union
openmc.Complement
openmc.BoundingBox
openmc.Cell
openmc.Universe
openmc.DAGMCUniverse

View file

@ -1,54 +1,57 @@
from typing import Iterable
import numpy as np
from .checkvalue import check_type, check_length
from .checkvalue import check_length
class BoundingBox(tuple):
"""Axis-aligned bounding box.
.. versionadded:: 0.13.4
Parameters
----------
corners : 2-tuple of numpy.array
Lower-left and upper-right coordinates of an axis-aligned bounding box
of the domain.
lower_left : iterable of float
The x, y, z coordinates of the lower left corner of the bounding box in [cm]
upper_right : iterable of float
The x, y, z coordinates of the upper right corner of the bounding box in [cm]
Attributes
----------
center: numpy.array
x, y, z coordinates of the center of the bounding box in cm.
lower_left: numpy.array
The x, y, z coordinates of the lower left corner of the bounding box
upper_right
The x, y, z coordinates of the upper right corner of the bounding box
volume: float
The volume of the bounding box in cm3
center : numpy.ndarray
x, y, z coordinates of the center of the bounding box in [cm].
lower_left : numpy.ndarray
The x, y, z coordinates of the lower left corner of the bounding box in [cm]
upper_right : numpy.ndarray
The x, y, z coordinates of the upper right corner of the bounding box in [cm]
volume : float
The volume of the bounding box in [cm^3]
"""
def __init__(self, corners):
def __new__(cls, lower_left: Iterable[float], upper_right: Iterable[float]):
check_length("lower_left", lower_left, 3, 3)
check_length("upper_right", upper_right, 3, 3)
lower_left = np.array(lower_left, dtype=float)
upper_right = np.array(upper_right, dtype=float)
return tuple.__new__(cls, (lower_left, upper_right))
check_type("corners", corners, tuple)
check_type("corners", corners[0], np.ndarray)
check_type("corners", corners[1], np.ndarray)
check_length("corners", corners, 2, 2)
check_length("corners", corners[0], 3, 3)
check_length("corners", corners[1], 3, 3)
self.corners = corners
def __repr__(self) -> str:
return "BoundingBox(lower_left={}, upper_right={})".format(
tuple(self.lower_left), tuple(self.upper_right))
@property
def center(self):
"""The center x, y, z coordinates of the bounding box"""
return (self.corners[0] + self.corners[1]) / 2
def center(self) -> np.ndarray:
return (self[0] + self[1]) / 2
@property
def lower_left(self):
"""The x, y, z coordinates of the lower left corner of the bounding box"""
return self.corners[0]
def lower_left(self) -> np.ndarray:
return self[0]
@property
def upper_right(self):
"""The x, y, z coordinates of the upper right corner of the bounding box"""
return self.corners[1]
def upper_right(self) -> np.ndarray:
return self[1]
@property
def volume(self):
"""The volume of the bounding box"""
return np.abs(np.prod(self.corners[1] - self.corners[0]))
def volume(self) -> float:
return np.abs(np.prod(self[1] - self[0]))

View file

@ -92,6 +92,8 @@ class Cell(IDManagerMixin):
fill. For example, {'U235': 1.0e22, 'U238': 5.0e22, ...}.
.. versionadded:: 0.12
bounding_box : openmc.BoundingBox
Axis-aligned bounding box of the cell
"""
@ -250,8 +252,8 @@ class Cell(IDManagerMixin):
if self.region is not None:
return self.region.bounding_box
else:
return BoundingBox((np.array([-np.inf, -np.inf, -np.inf]),
np.array([np.inf, np.inf, np.inf])))
return BoundingBox(np.array([-np.inf, -np.inf, -np.inf]),
np.array([np.inf, np.inf, np.inf]))
@property
def num_instances(self):

View file

@ -25,7 +25,7 @@ class Geometry:
----------
root_universe : openmc.UniverseBase
Root universe which contains all others
bounding_box : 2-tuple of numpy.array
bounding_box : openmc.BoundingBox
Lower-left and upper-right coordinates of an axis-aligned bounding box
of the universe.
merge_surfaces : bool

View file

@ -103,7 +103,7 @@ class Cell(_FortranObjectWithID):
Name of the cell
num_instances : int
Number of unique cell instances
bounding_box : 2-tuple of numpy.ndarray
bounding_box : openmc.BoundingBox
Lower-left and upper-right coordinates of bounding box
translation : Iterable of float
3-D coordinates of the translation vector
@ -291,7 +291,7 @@ class Cell(_FortranObjectWithID):
llc[llc == -inf] = -np.inf
urc[urc == -inf] = -np.inf
return BoundingBox((llc, urc))
return BoundingBox(llc, urc)
class _CellMapping(Mapping):

View file

@ -356,7 +356,7 @@ class Intersection(Region, MutableSequence):
Attributes
----------
bounding_box : tuple of numpy.array
bounding_box : openmc.BoundingBox
Lower-left and upper-right coordinates of an axis-aligned bounding box
"""
@ -419,7 +419,7 @@ class Intersection(Region, MutableSequence):
lower_left_n, upper_right_n = n.bounding_box
lower_left[:] = np.maximum(lower_left, lower_left_n)
upper_right[:] = np.minimum(upper_right, upper_right_n)
return BoundingBox((lower_left, upper_right))
return BoundingBox(lower_left, upper_right)
class Union(Region, MutableSequence):
@ -444,7 +444,7 @@ class Union(Region, MutableSequence):
Attributes
----------
bounding_box : 2-tuple of numpy.array
bounding_box : openmc.BoundingBox
Lower-left and upper-right coordinates of an axis-aligned bounding box
"""
@ -507,7 +507,7 @@ class Union(Region, MutableSequence):
lower_left_n, upper_right_n = n.bounding_box
lower_left[:] = np.minimum(lower_left, lower_left_n)
upper_right[:] = np.maximum(upper_right, upper_right_n)
return BoundingBox((lower_left, upper_right))
return BoundingBox(lower_left, upper_right)
class Complement(Region):
@ -534,7 +534,7 @@ class Complement(Region):
----------
node : openmc.Region
Regions to take the complement of
bounding_box : tuple of numpy.array
bounding_box : openmc.BoundingBox
Lower-left and upper-right coordinates of an axis-aligned bounding box
"""

View file

@ -234,10 +234,10 @@ class Surface(IDManagerMixin, ABC):
desired half-space
"""
return BoundingBox((
return BoundingBox(
np.array([-np.inf, -np.inf, -np.inf]),
np.array([np.inf, np.inf, np.inf])
))
)
def clone(self, memo=None):
"""Create a copy of this surface with a new unique ID.
@ -542,7 +542,7 @@ class PlaneMixin:
else:
ur = np.array([v if not np.isnan(v) else np.inf for v in vals])
return BoundingBox((ll, ur))
return BoundingBox(ll, ur)
def evaluate(self, point):
"""Evaluate the surface equation at a given point.
@ -1199,10 +1199,10 @@ class Cylinder(QuadricMixin, Surface):
return (np.array(ll), np.array(ur))
elif side == '+':
return BoundingBox((
return BoundingBox(
np.array([-np.inf, -np.inf, -np.inf]),
np.array([np.inf, np.inf, np.inf])
))
)
def _get_base_coeffs(self):
# Get x, y, z coordinates of two points
@ -1364,15 +1364,15 @@ class XCylinder(QuadricMixin, Surface):
def bounding_box(self, side):
if side == '-':
return BoundingBox((
return BoundingBox(
np.array([-np.inf, self.y0 - self.r, self.z0 - self.r]),
np.array([np.inf, self.y0 + self.r, self.z0 + self.r])
))
)
elif side == '+':
return BoundingBox((
return BoundingBox(
np.array([-np.inf, -np.inf, -np.inf]),
np.array([np.inf, np.inf, np.inf])
))
)
def evaluate(self, point):
y = point[1] - self.y0
@ -1459,15 +1459,15 @@ class YCylinder(QuadricMixin, Surface):
def bounding_box(self, side):
if side == '-':
return BoundingBox((
return BoundingBox(
np.array([self.x0 - self.r, -np.inf, self.z0 - self.r]),
np.array([self.x0 + self.r, np.inf, self.z0 + self.r])
))
)
elif side == '+':
return BoundingBox((
return BoundingBox(
np.array([-np.inf, -np.inf, -np.inf]),
np.array([np.inf, np.inf, np.inf])
))
)
def evaluate(self, point):
x = point[0] - self.x0
@ -1554,15 +1554,15 @@ class ZCylinder(QuadricMixin, Surface):
def bounding_box(self, side):
if side == '-':
return BoundingBox((
return BoundingBox(
np.array([self.x0 - self.r, self.y0 - self.r, -np.inf]),
np.array([self.x0 + self.r, self.y0 + self.r, np.inf])
))
)
elif side == '+':
return BoundingBox((
return BoundingBox(
np.array([-np.inf, -np.inf, -np.inf]),
np.array([np.inf, np.inf, np.inf])
))
)
def evaluate(self, point):
x = point[0] - self.x0
@ -1648,15 +1648,15 @@ class Sphere(QuadricMixin, Surface):
def bounding_box(self, side):
if side == '-':
return BoundingBox((
return BoundingBox(
np.array([self.x0 - self.r, self.y0 - self.r, self.z0 - self.r]),
np.array([self.x0 + self.r, self.y0 + self.r, self.z0 + self.r])
))
)
elif side == '+':
return BoundingBox((
return BoundingBox(
np.array([-np.inf, -np.inf, -np.inf]),
np.array([np.inf, np.inf, np.inf])
))
)
def evaluate(self, point):
x = point[0] - self.x0
@ -2272,15 +2272,15 @@ class XTorus(TorusMixin, Surface):
x0, y0, z0 = self.x0, self.y0, self.z0
a, b, c = self.a, self.b, self.c
if side == '-':
return BoundingBox((
return BoundingBox(
np.array([x0 - b, y0 - a - c, z0 - a - c]),
np.array([x0 + b, y0 + a + c, z0 + a + c])
))
)
elif side == '+':
return BoundingBox((
return BoundingBox(
np.array([-np.inf, -np.inf, -np.inf]),
np.array([np.inf, np.inf, np.inf])
))
)
class YTorus(TorusMixin, Surface):
r"""A torus of the form :math:`(y - y_0)^2/B^2 + (\sqrt{(x - x_0)^2 + (z -
@ -2347,15 +2347,15 @@ class YTorus(TorusMixin, Surface):
x0, y0, z0 = self.x0, self.y0, self.z0
a, b, c = self.a, self.b, self.c
if side == '-':
return BoundingBox((
return BoundingBox(
np.array([x0 - a - c, y0 - b, z0 - a - c]),
np.array([x0 + a + c, y0 + b, z0 + a + c])
))
)
elif side == '+':
return BoundingBox((
return BoundingBox(
np.array([-np.inf, -np.inf, -np.inf]),
np.array([np.inf, np.inf, np.inf])
))
)
class ZTorus(TorusMixin, Surface):
@ -2423,15 +2423,15 @@ class ZTorus(TorusMixin, Surface):
x0, y0, z0 = self.x0, self.y0, self.z0
a, b, c = self.a, self.b, self.c
if side == '-':
return BoundingBox((
return BoundingBox(
np.array([x0 - a - c, y0 - a - c, z0 - b]),
np.array([x0 + a + c, y0 + a + c, z0 + b])
))
)
elif side == '+':
return BoundingBox((
return BoundingBox(
np.array([-np.inf, -np.inf, -np.inf]),
np.array([np.inf, np.inf, np.inf])
))
)
class Halfspace(Region):
@ -2466,7 +2466,7 @@ class Halfspace(Region):
Surface which divides Euclidean space.
side : {'+', '-'}
Indicates whether the positive or negative half-space is used.
bounding_box : tuple of numpy.ndarray
bounding_box : openmc.BoundingBox
Lower-left and upper-right coordinates of an axis-aligned bounding box
"""

View file

@ -202,7 +202,7 @@ class Universe(UniverseBase):
Volume of the universe in cm^3. This can either be set manually or
calculated in a stochastic volume calculation and added via the
:meth:`Universe.add_volume_information` method.
bounding_box : 2-tuple of numpy.array
bounding_box : openmc.BoundingBox
Lower-left and upper-right coordinates of an axis-aligned bounding box
of the universe.
@ -662,7 +662,7 @@ class DAGMCUniverse(UniverseBase):
auto_mat_ids : bool
Set IDs automatically on initialization (True) or report overlaps in ID
space between OpenMC and UWUW materials (False)
bounding_box : 2-tuple of numpy.array
bounding_box : openmc.BoundingBox
Lower-left and upper-right coordinates of an axis-aligned bounding box
of the universe.
@ -972,4 +972,4 @@ class DAGMCUniverse(UniverseBase):
clone.volume = self.volume
clone.auto_geom_ids = self.auto_geom_ids
clone.auto_mat_ids = self.auto_mat_ids
return clone
return clone

View file

@ -3,15 +3,9 @@ import openmc
import pytest
test_bb_1 = openmc.BoundingBox(
(np.array([-10.0, -20.0, -30.0]), np.array([1.0, 2.0, 3.0]))
)
test_bb_2 = openmc.BoundingBox(
(np.array([1.0, 2.0, 3.0]), np.array([11.0, 22.0, 33.0]))
)
test_bb_3 = openmc.BoundingBox(
(np.array([-10.0, -20.0, -30.0]), np.array([-1.0, -2.0, -3.0]))
)
test_bb_1 = openmc.BoundingBox((-10.0, -20.0, -30.0), (1.0, 2.0, 3.0))
test_bb_2 = openmc.BoundingBox((1.0, 2.0, 3.0), (11.0, 22.0, 33.0))
test_bb_3 = openmc.BoundingBox((-10.0, -20.0, -30.0), (-1.0, -2.0, -3.0))
@pytest.mark.parametrize(
@ -63,29 +57,21 @@ def test_bounding_box_center(bb, expected):
def test_bounding_box_input_checking():
# checks that a list is not accepted instead of np.array
# checks that only passing lower_left is not accepted
with pytest.raises(TypeError):
openmc.BoundingBox(([-10, -20, -30], [1, 2, 3]))
# checks that a list is not accepted instead of tuple
with pytest.raises(TypeError):
openmc.BoundingBox([np.array([-10, -20, -30]), np.array([1, 2, 3])])
# checks that a tuple with one entry is not accepted
with pytest.raises(TypeError):
openmc.BoundingBox((np.array([-10, -20, -30])))
openmc.BoundingBox((-10, -20, -3))
# checks that a tuple with three entry is not accepted
with pytest.raises(ValueError):
openmc.BoundingBox(
(np.array([-1, -2, -3]), np.array([-1, -2, -3]), np.array([-1, -2, -3]))
)
with pytest.raises(TypeError):
openmc.BoundingBox((-1, -2, -3), (-1, -2, -3), (-1, -2, -3))
# checks that a numpy array with two entries is not accepted
with pytest.raises(ValueError):
openmc.BoundingBox((np.array([-10, -30]), np.array([1, 2, 3])))
openmc.BoundingBox(np.array([-10, -30]), np.array([1, 2, 3]))
# checks that a numpy array with two entries is not accepted
with pytest.raises(ValueError):
openmc.BoundingBox((np.array([-10, -20, -30]), np.array([1, 3])))
openmc.BoundingBox(np.array([-10, -20, -30]), np.array([1, 3]))
# checks that a numpy array with four entries is not accepted
with pytest.raises(ValueError):
openmc.BoundingBox((np.array([-10, -20, -3, -4]), np.array([1, 2, 3])))
openmc.BoundingBox(np.array([-10, -20, -3, -4]), np.array([1, 2, 3]))
# checks that a numpy array with four entries is not accepted
with pytest.raises(ValueError):
openmc.BoundingBox((np.array([-10, -20, -4]), np.array([1, 2, 3, 4])))
openmc.BoundingBox(np.array([-10, -20, -4]), np.array([1, 2, 3, 4]))