mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-21 14:35:27 -04:00
Make BoundingBox a mutable type and add convenience methods. (#2717)
Co-authored-by: Paul Romano <paul.k.romano@gmail.com>
This commit is contained in:
parent
4d3a198c52
commit
bd1c2286f8
6 changed files with 184 additions and 76 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -134,7 +134,7 @@ jobs:
|
|||
~/nndc_hdf5
|
||||
~/endf-b-vii.1
|
||||
key: ${{ runner.os }}-build-xs-cache
|
||||
|
||||
|
||||
- name: before
|
||||
shell: bash
|
||||
run: $GITHUB_WORKSPACE/tools/ci/gha-before-script.sh
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import numpy as np
|
|||
from .checkvalue import check_length
|
||||
|
||||
|
||||
class BoundingBox(tuple):
|
||||
class BoundingBox:
|
||||
"""Axis-aligned bounding box.
|
||||
|
||||
.. versionadded:: 0.13.4
|
||||
|
|
@ -35,17 +35,66 @@ class BoundingBox(tuple):
|
|||
The width of the x, y and z axis in [cm]
|
||||
"""
|
||||
|
||||
def __new__(cls, lower_left: Iterable[float], upper_right: Iterable[float]):
|
||||
def __init__(self, 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))
|
||||
self._bounds = np.asarray([lower_left, upper_right], dtype=float)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "BoundingBox(lower_left={}, upper_right={})".format(
|
||||
tuple(self.lower_left), tuple(self.upper_right))
|
||||
|
||||
def __getitem__(self, key) -> np.ndarray:
|
||||
return self._bounds[key]
|
||||
|
||||
def __len__(self):
|
||||
return 2
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
self._bounds[key] = val
|
||||
|
||||
def __iand__(self, other: BoundingBox) -> BoundingBox:
|
||||
"""Updates the box be the intersection of itself and another box
|
||||
|
||||
Parameters
|
||||
----------
|
||||
other : BoundingBox
|
||||
The box used to resize this box
|
||||
|
||||
Returns
|
||||
-------
|
||||
An updated bounding box
|
||||
"""
|
||||
self.lower_left = np.maximum(self.lower_left, other.lower_left)
|
||||
self.upper_right = np.minimum(self.upper_right, other.upper_right)
|
||||
return self
|
||||
|
||||
def __and__(self, other: BoundingBox) -> BoundingBox:
|
||||
new = BoundingBox(*self)
|
||||
new &= other
|
||||
return new
|
||||
|
||||
def __ior__(self, other: BoundingBox) -> BoundingBox:
|
||||
"""Updates the box be the union of itself and another box
|
||||
|
||||
Parameters
|
||||
----------
|
||||
other : BoundingBox
|
||||
The box used to resize this box
|
||||
|
||||
Returns
|
||||
-------
|
||||
An updated bounding box
|
||||
"""
|
||||
self.lower_left = np.minimum(self.lower_left, other.lower_left)
|
||||
self.upper_right = np.maximum(self.upper_right, other.upper_right)
|
||||
return self
|
||||
|
||||
def __or__(self, other: BoundingBox) -> BoundingBox:
|
||||
new = BoundingBox(*self)
|
||||
new |= other
|
||||
return new
|
||||
|
||||
@property
|
||||
def center(self) -> np.ndarray:
|
||||
return (self[0] + self[1]) / 2
|
||||
|
|
@ -54,10 +103,20 @@ class BoundingBox(tuple):
|
|||
def lower_left(self) -> np.ndarray:
|
||||
return self[0]
|
||||
|
||||
@lower_left.setter
|
||||
def lower_left(self, llc):
|
||||
check_length('lower_left', llc, 3, 3)
|
||||
self[0] = llc
|
||||
|
||||
@property
|
||||
def upper_right(self) -> np.ndarray:
|
||||
return self[1]
|
||||
|
||||
@upper_right.setter
|
||||
def upper_right(self, urc):
|
||||
check_length('upper_right', urc, 3, 3)
|
||||
self[1] = urc
|
||||
|
||||
@property
|
||||
def volume(self) -> float:
|
||||
return np.abs(np.prod(self[1] - self[0]))
|
||||
|
|
@ -89,29 +148,36 @@ class BoundingBox(tuple):
|
|||
def width(self):
|
||||
return self.upper_right - self.lower_left
|
||||
|
||||
def extend(self, padding_distance: float) -> BoundingBox:
|
||||
"""Returns an extended bounding box
|
||||
def expand(self, padding_distance: float, inplace: bool = False) -> BoundingBox:
|
||||
"""Returns an expanded bounding box
|
||||
|
||||
Parameters
|
||||
----------
|
||||
padding_distance : float
|
||||
The distance to enlarge the bounding box by
|
||||
inplace : bool
|
||||
Whether or not to return a new BoundingBox instance or to modify the
|
||||
current BoundingBox object.
|
||||
|
||||
Returns
|
||||
-------
|
||||
An enlarged bounding box
|
||||
|
||||
An expanded bounding box
|
||||
"""
|
||||
return BoundingBox(np.array(
|
||||
[
|
||||
self[0][0] - padding_distance,
|
||||
self[0][1] - padding_distance,
|
||||
self[0][2] - padding_distance
|
||||
]
|
||||
), np.array(
|
||||
[
|
||||
self[1][0] + padding_distance,
|
||||
self[1][1] + padding_distance,
|
||||
self[1][2] + padding_distance
|
||||
]
|
||||
))
|
||||
if inplace:
|
||||
self[0] -= padding_distance
|
||||
self[1] += padding_distance
|
||||
return self
|
||||
else:
|
||||
return BoundingBox(self[0] - padding_distance, self[1] + padding_distance)
|
||||
|
||||
@classmethod
|
||||
def infinite(cls) -> BoundingBox:
|
||||
"""Create an infinite box. Useful as a starting point for determining
|
||||
geometry bounds.
|
||||
|
||||
Returns
|
||||
-------
|
||||
An infinitely large bounding box.
|
||||
"""
|
||||
infs = np.full((3,), np.inf)
|
||||
return cls(-infs, infs)
|
||||
|
|
|
|||
|
|
@ -414,13 +414,10 @@ class Intersection(Region, MutableSequence):
|
|||
|
||||
@property
|
||||
def bounding_box(self):
|
||||
lower_left = np.array([-np.inf, -np.inf, -np.inf])
|
||||
upper_right = np.array([np.inf, np.inf, np.inf])
|
||||
box = BoundingBox.infinite()
|
||||
for n in self:
|
||||
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)
|
||||
box &= n.bounding_box
|
||||
return box
|
||||
|
||||
|
||||
class Union(Region, MutableSequence):
|
||||
|
|
@ -505,13 +502,11 @@ class Union(Region, MutableSequence):
|
|||
|
||||
@property
|
||||
def bounding_box(self):
|
||||
lower_left = np.array([np.inf, np.inf, np.inf])
|
||||
upper_right = np.array([-np.inf, -np.inf, -np.inf])
|
||||
bbox = BoundingBox(np.array([np.inf]*3),
|
||||
np.array([-np.inf]*3))
|
||||
for n in self:
|
||||
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)
|
||||
bbox |= n.bounding_box
|
||||
return bbox
|
||||
|
||||
|
||||
class Complement(Region):
|
||||
|
|
|
|||
|
|
@ -233,10 +233,7 @@ class Surface(IDManagerMixin, ABC):
|
|||
desired half-space
|
||||
|
||||
"""
|
||||
return BoundingBox(
|
||||
np.array([-np.inf, -np.inf, -np.inf]),
|
||||
np.array([np.inf, np.inf, np.inf])
|
||||
)
|
||||
return BoundingBox.infinite()
|
||||
|
||||
def clone(self, memo=None):
|
||||
"""Create a copy of this surface with a new unique ID.
|
||||
|
|
@ -1204,13 +1201,9 @@ class Cylinder(QuadricMixin, Surface):
|
|||
else -np.inf for xi, dxi in zip(self._origin, self._axis)]
|
||||
ur = [xi + r if np.isclose(dxi, 0., rtol=0., atol=self._atol)
|
||||
else np.inf for xi, dxi in zip(self._origin, self._axis)]
|
||||
return (np.array(ll), np.array(ur))
|
||||
|
||||
return BoundingBox(np.array(ll), np.array(ur))
|
||||
elif side == '+':
|
||||
return BoundingBox(
|
||||
np.array([-np.inf, -np.inf, -np.inf]),
|
||||
np.array([np.inf, np.inf, np.inf])
|
||||
)
|
||||
return BoundingBox.infinite()
|
||||
|
||||
def _get_base_coeffs(self):
|
||||
# Get x, y, z coordinates of two points
|
||||
|
|
@ -1377,10 +1370,7 @@ class XCylinder(QuadricMixin, Surface):
|
|||
np.array([np.inf, self.y0 + self.r, self.z0 + self.r])
|
||||
)
|
||||
elif side == '+':
|
||||
return BoundingBox(
|
||||
np.array([-np.inf, -np.inf, -np.inf]),
|
||||
np.array([np.inf, np.inf, np.inf])
|
||||
)
|
||||
return BoundingBox.infinite()
|
||||
|
||||
def evaluate(self, point):
|
||||
y = point[1] - self.y0
|
||||
|
|
@ -1472,10 +1462,7 @@ class YCylinder(QuadricMixin, Surface):
|
|||
np.array([self.x0 + self.r, np.inf, self.z0 + self.r])
|
||||
)
|
||||
elif side == '+':
|
||||
return BoundingBox(
|
||||
np.array([-np.inf, -np.inf, -np.inf]),
|
||||
np.array([np.inf, np.inf, np.inf])
|
||||
)
|
||||
return BoundingBox.infinite()
|
||||
|
||||
def evaluate(self, point):
|
||||
x = point[0] - self.x0
|
||||
|
|
@ -1567,10 +1554,7 @@ class ZCylinder(QuadricMixin, Surface):
|
|||
np.array([self.x0 + self.r, self.y0 + self.r, np.inf])
|
||||
)
|
||||
elif side == '+':
|
||||
return BoundingBox(
|
||||
np.array([-np.inf, -np.inf, -np.inf]),
|
||||
np.array([np.inf, np.inf, np.inf])
|
||||
)
|
||||
return BoundingBox.infinite()
|
||||
|
||||
def evaluate(self, point):
|
||||
x = point[0] - self.x0
|
||||
|
|
@ -1661,10 +1645,7 @@ class Sphere(QuadricMixin, Surface):
|
|||
np.array([self.x0 + self.r, self.y0 + self.r, self.z0 + self.r])
|
||||
)
|
||||
elif side == '+':
|
||||
return BoundingBox(
|
||||
np.array([-np.inf, -np.inf, -np.inf]),
|
||||
np.array([np.inf, np.inf, np.inf])
|
||||
)
|
||||
return BoundingBox.infinite()
|
||||
|
||||
def evaluate(self, point):
|
||||
x = point[0] - self.x0
|
||||
|
|
@ -2285,10 +2266,8 @@ class XTorus(TorusMixin, Surface):
|
|||
np.array([x0 + b, y0 + a + c, z0 + a + c])
|
||||
)
|
||||
elif side == '+':
|
||||
return BoundingBox(
|
||||
np.array([-np.inf, -np.inf, -np.inf]),
|
||||
np.array([np.inf, np.inf, np.inf])
|
||||
)
|
||||
return BoundingBox.infinite()
|
||||
|
||||
|
||||
class YTorus(TorusMixin, Surface):
|
||||
r"""A torus of the form :math:`(y - y_0)^2/B^2 + (\sqrt{(x - x_0)^2 + (z -
|
||||
|
|
@ -2360,10 +2339,7 @@ class YTorus(TorusMixin, Surface):
|
|||
np.array([x0 + a + c, y0 + b, z0 + a + c])
|
||||
)
|
||||
elif side == '+':
|
||||
return BoundingBox(
|
||||
np.array([-np.inf, -np.inf, -np.inf]),
|
||||
np.array([np.inf, np.inf, np.inf])
|
||||
)
|
||||
return BoundingBox.infinite()
|
||||
|
||||
|
||||
class ZTorus(TorusMixin, Surface):
|
||||
|
|
@ -2436,10 +2412,7 @@ class ZTorus(TorusMixin, Surface):
|
|||
np.array([x0 + a + c, y0 + a + c, z0 + b])
|
||||
)
|
||||
elif side == '+':
|
||||
return BoundingBox(
|
||||
np.array([-np.inf, -np.inf, -np.inf]),
|
||||
np.array([np.inf, np.inf, np.inf])
|
||||
)
|
||||
return BoundingBox.infinite()
|
||||
|
||||
|
||||
class Halfspace(Region):
|
||||
|
|
|
|||
|
|
@ -234,8 +234,7 @@ class Universe(UniverseBase):
|
|||
if regions:
|
||||
return openmc.Union(regions).bounding_box
|
||||
else:
|
||||
# Infinite bounding box
|
||||
return openmc.Intersection([]).bounding_box
|
||||
return openmc.BoundingBox.infinite()
|
||||
|
||||
@classmethod
|
||||
def from_hdf5(cls, group, cells):
|
||||
|
|
@ -993,7 +992,7 @@ class DAGMCUniverse(UniverseBase):
|
|||
check_type('bounded type', bounded_type, str)
|
||||
check_value('bounded type', bounded_type, ('box', 'sphere'))
|
||||
|
||||
bbox = self.bounding_box.extend(padding_distance)
|
||||
bbox = self.bounding_box.expand(padding_distance, True)
|
||||
|
||||
if bounded_type == 'sphere':
|
||||
radius = np.linalg.norm(bbox.upper_right - bbox.center)
|
||||
|
|
|
|||
|
|
@ -81,3 +81,78 @@ def test_bounding_box_extents():
|
|||
assert test_bb_1.extent['xy'] == (-10., 1., -20., 2.)
|
||||
assert test_bb_1.extent['xz'] == (-10., 1., -30., 3.)
|
||||
assert test_bb_1.extent['yz'] == (-20., 2., -30., 3.)
|
||||
|
||||
|
||||
def test_bounding_box_methods():
|
||||
test_bb = openmc.BoundingBox.infinite()
|
||||
|
||||
# check assignment operator
|
||||
test_bb[0] = [-10, -11, -12]
|
||||
test_bb[1] = [13, 14, 15]
|
||||
|
||||
assert all(test_bb[0] == [-10, -11, -12])
|
||||
assert all(test_bb[1] == [13, 14, 15])
|
||||
|
||||
# check length and iteration
|
||||
assert len(test_bb) == 2
|
||||
ll, ur = test_bb
|
||||
assert all(ll == [-10, -11, -12])
|
||||
assert all(ur == [13, 14, 15])
|
||||
|
||||
# test expand/reduce methods
|
||||
other_bb = openmc.BoundingBox([-5, -5, -50], [5, 50, 5])
|
||||
|
||||
reduced_bb = test_bb & other_bb
|
||||
|
||||
# inplace was False by default. BoundingBox.reduce should return a new object
|
||||
assert test_bb is not reduced_bb
|
||||
|
||||
# the original bounding box should be unchanged
|
||||
assert all(test_bb[0] == [-10, -11, -12])
|
||||
assert all(test_bb[1] == [13, 14, 15])
|
||||
|
||||
assert all(reduced_bb[0] == [-5, -5, -12])
|
||||
assert all(reduced_bb[1] == [5, 14, 5])
|
||||
|
||||
test_bb &= other_bb
|
||||
|
||||
assert all(test_bb[0] == [-5, -5, -12])
|
||||
assert all(test_bb[1] == [5, 14, 5])
|
||||
|
||||
other_bb = openmc.BoundingBox([-50, -50, -1], [50, 1, 50])
|
||||
|
||||
expanded_bb = test_bb | other_bb
|
||||
|
||||
# inplace was False by default. BoundingBox.expand should return a new object
|
||||
assert test_bb is not expanded_bb
|
||||
|
||||
# the original bounding box should be unchanged
|
||||
assert all(test_bb[0] == [-5, -5, -12])
|
||||
assert all(test_bb[1] == [5, 14, 5])
|
||||
|
||||
assert all(expanded_bb[0] == [-50, -50, -12])
|
||||
assert all(expanded_bb[1] == [50, 14, 50])
|
||||
|
||||
test_bb |= other_bb
|
||||
|
||||
assert all(test_bb[0] == [-50, -50, -12])
|
||||
assert all(test_bb[1] == [50, 14, 50])
|
||||
|
||||
extended_bbox = test_bb.expand(0.1)
|
||||
|
||||
assert extended_bbox is not test_bb
|
||||
|
||||
# the original bounding box should not be changed with inplace as False
|
||||
assert all(test_bb[0] == [-50, -50, -12])
|
||||
assert all(test_bb[1] == [50, 14, 50])
|
||||
|
||||
assert all(extended_bbox[0] == [-50.1, -50.1, -12.1])
|
||||
assert all(extended_bbox[1] == [50.1, 14.1, 50.1])
|
||||
|
||||
extended_bbox = test_bb.expand(0.1, True)
|
||||
|
||||
# inplace was set to True. BoundingBox.reduce should return the same object
|
||||
assert extended_bbox is test_bb
|
||||
|
||||
assert all(test_bb[0] == [-50.1, -50.1, -12.1])
|
||||
assert all(test_bb[1] == [50.1, 14.1, 50.1])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue