mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-28 06:05:58 -04:00
Merge pull request #2475 from shimwell/adding_bounding_box_class
Adding bounding box class
This commit is contained in:
commit
a12480400b
10 changed files with 223 additions and 49 deletions
|
|
@ -87,6 +87,7 @@ Building geometry
|
|||
openmc.Intersection
|
||||
openmc.Union
|
||||
openmc.Complement
|
||||
openmc.BoundingBox
|
||||
openmc.Cell
|
||||
openmc.Universe
|
||||
openmc.DAGMCUniverse
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from openmc.arithmetic import *
|
||||
from openmc.bounding_box import *
|
||||
from openmc.cell import *
|
||||
from openmc.checkvalue import *
|
||||
from openmc.mesh import *
|
||||
|
|
|
|||
57
openmc/bounding_box.py
Normal file
57
openmc/bounding_box.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
from typing import Iterable
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .checkvalue import check_length
|
||||
|
||||
|
||||
class BoundingBox(tuple):
|
||||
"""Axis-aligned bounding box.
|
||||
|
||||
.. versionadded:: 0.13.4
|
||||
|
||||
Parameters
|
||||
----------
|
||||
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.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 __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))
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "BoundingBox(lower_left={}, upper_right={})".format(
|
||||
tuple(self.lower_left), tuple(self.upper_right))
|
||||
|
||||
@property
|
||||
def center(self) -> np.ndarray:
|
||||
return (self[0] + self[1]) / 2
|
||||
|
||||
@property
|
||||
def lower_left(self) -> np.ndarray:
|
||||
return self[0]
|
||||
|
||||
@property
|
||||
def upper_right(self) -> np.ndarray:
|
||||
return self[1]
|
||||
|
||||
@property
|
||||
def volume(self) -> float:
|
||||
return np.abs(np.prod(self[1] - self[0]))
|
||||
|
|
@ -13,6 +13,7 @@ from ._xml import get_text
|
|||
from .mixin import IDManagerMixin
|
||||
from .region import Region, Complement
|
||||
from .surface import Halfspace
|
||||
from .bounding_box import BoundingBox
|
||||
|
||||
|
||||
class Cell(IDManagerMixin):
|
||||
|
|
@ -91,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
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -249,8 +252,8 @@ class Cell(IDManagerMixin):
|
|||
if self.region is not None:
|
||||
return self.region.bounding_box
|
||||
else:
|
||||
return (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):
|
||||
|
|
|
|||
|
|
@ -28,7 +28,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
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ from . import _dll
|
|||
from .core import _FortranObjectWithID
|
||||
from .error import _error_handler
|
||||
from .material import Material
|
||||
from ..bounding_box import BoundingBox
|
||||
|
||||
|
||||
__all__ = ['Cell', 'cells']
|
||||
|
||||
|
|
@ -101,8 +103,8 @@ class Cell(_FortranObjectWithID):
|
|||
Name of the cell
|
||||
num_instances : int
|
||||
Number of unique cell instances
|
||||
bounding_box : 2-tuple of numpy.ndarray
|
||||
Lower-left and upper-right coordinates of bounding box
|
||||
bounding_box : openmc.BoundingBox
|
||||
Axis-aligned bounding box of the cell
|
||||
translation : Iterable of float
|
||||
3-D coordinates of the translation vector
|
||||
rotation : Iterable of float
|
||||
|
|
@ -289,7 +291,7 @@ class Cell(_FortranObjectWithID):
|
|||
llc[llc == -inf] = -np.inf
|
||||
urc[urc == -inf] = -np.inf
|
||||
|
||||
return llc, urc
|
||||
return BoundingBox(llc, urc)
|
||||
|
||||
|
||||
class _CellMapping(Mapping):
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from copy import deepcopy
|
|||
import numpy as np
|
||||
|
||||
from .checkvalue import check_type
|
||||
from .bounding_box import BoundingBox
|
||||
|
||||
|
||||
class Region(ABC):
|
||||
|
|
@ -355,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
|
||||
|
||||
"""
|
||||
|
|
@ -418,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 lower_left, upper_right
|
||||
return BoundingBox(lower_left, upper_right)
|
||||
|
||||
|
||||
class Union(Region, MutableSequence):
|
||||
|
|
@ -443,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
|
||||
|
||||
"""
|
||||
|
|
@ -506,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 lower_left, upper_right
|
||||
return BoundingBox(lower_left, upper_right)
|
||||
|
||||
|
||||
class Complement(Region):
|
||||
|
|
@ -533,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
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import numpy as np
|
|||
from .checkvalue import check_type, check_value, check_length
|
||||
from .mixin import IDManagerMixin, IDWarning
|
||||
from .region import Region, Intersection, Union
|
||||
from .bounding_box import BoundingBox
|
||||
|
||||
|
||||
_BOUNDARY_TYPES = ['transmission', 'vacuum', 'reflective', 'periodic', 'white']
|
||||
|
|
@ -233,8 +234,10 @@ class Surface(IDManagerMixin, ABC):
|
|||
desired half-space
|
||||
|
||||
"""
|
||||
return (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])
|
||||
)
|
||||
|
||||
def clone(self, memo=None):
|
||||
"""Create a copy of this surface with a new unique ID.
|
||||
|
|
@ -539,7 +542,7 @@ class PlaneMixin:
|
|||
else:
|
||||
ur = np.array([v if not np.isnan(v) else np.inf for v in vals])
|
||||
|
||||
return (ll, ur)
|
||||
return BoundingBox(ll, ur)
|
||||
|
||||
def evaluate(self, point):
|
||||
"""Evaluate the surface equation at a given point.
|
||||
|
|
@ -1205,8 +1208,10 @@ class Cylinder(QuadricMixin, Surface):
|
|||
return (np.array(ll), np.array(ur))
|
||||
|
||||
elif side == '+':
|
||||
return (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])
|
||||
)
|
||||
|
||||
def _get_base_coeffs(self):
|
||||
# Get x, y, z coordinates of two points
|
||||
|
|
@ -1368,11 +1373,15 @@ class XCylinder(QuadricMixin, Surface):
|
|||
|
||||
def bounding_box(self, side):
|
||||
if side == '-':
|
||||
return (np.array([-np.inf, self.y0 - self.r, self.z0 - self.r]),
|
||||
np.array([np.inf, self.y0 + self.r, self.z0 + self.r]))
|
||||
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 (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])
|
||||
)
|
||||
|
||||
def evaluate(self, point):
|
||||
y = point[1] - self.y0
|
||||
|
|
@ -1459,11 +1468,15 @@ class YCylinder(QuadricMixin, Surface):
|
|||
|
||||
def bounding_box(self, side):
|
||||
if side == '-':
|
||||
return (np.array([self.x0 - self.r, -np.inf, self.z0 - self.r]),
|
||||
np.array([self.x0 + self.r, np.inf, self.z0 + self.r]))
|
||||
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 (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])
|
||||
)
|
||||
|
||||
def evaluate(self, point):
|
||||
x = point[0] - self.x0
|
||||
|
|
@ -1550,11 +1563,15 @@ class ZCylinder(QuadricMixin, Surface):
|
|||
|
||||
def bounding_box(self, side):
|
||||
if side == '-':
|
||||
return (np.array([self.x0 - self.r, self.y0 - self.r, -np.inf]),
|
||||
np.array([self.x0 + self.r, self.y0 + self.r, np.inf]))
|
||||
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 (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])
|
||||
)
|
||||
|
||||
def evaluate(self, point):
|
||||
x = point[0] - self.x0
|
||||
|
|
@ -1640,13 +1657,15 @@ class Sphere(QuadricMixin, Surface):
|
|||
|
||||
def bounding_box(self, side):
|
||||
if side == '-':
|
||||
return (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]))
|
||||
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 (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])
|
||||
)
|
||||
|
||||
def evaluate(self, point):
|
||||
x = point[0] - self.x0
|
||||
|
|
@ -2262,11 +2281,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 (np.array([x0 - b, y0 - a - c, z0 - a - c]),
|
||||
np.array([x0 + b, y0 + a + c, z0 + a + c]))
|
||||
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 (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])
|
||||
)
|
||||
|
||||
class YTorus(TorusMixin, Surface):
|
||||
r"""A torus of the form :math:`(y - y_0)^2/B^2 + (\sqrt{(x - x_0)^2 + (z -
|
||||
|
|
@ -2333,11 +2356,16 @@ 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 (np.array([x0 - a - c, y0 - b, z0 - a - c]),
|
||||
np.array([x0 + a + c, y0 + b, z0 + a + c]))
|
||||
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 (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])
|
||||
)
|
||||
|
||||
|
||||
class ZTorus(TorusMixin, Surface):
|
||||
r"""A torus of the form :math:`(z - z_0)^2/B^2 + (\sqrt{(x - x_0)^2 + (y -
|
||||
|
|
@ -2404,11 +2432,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 (np.array([x0 - a - c, y0 - a - c, z0 - b]),
|
||||
np.array([x0 + a + c, y0 + a + c, z0 + b]))
|
||||
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 (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])
|
||||
)
|
||||
|
||||
|
||||
class Halfspace(Region):
|
||||
|
|
@ -2443,7 +2475,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
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -205,7 +205,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.
|
||||
|
||||
|
|
@ -733,7 +733,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.
|
||||
|
||||
|
|
|
|||
77
tests/unit_tests/test_bounding_box.py
Normal file
77
tests/unit_tests/test_bounding_box.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import numpy as np
|
||||
import openmc
|
||||
import pytest
|
||||
|
||||
|
||||
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(
|
||||
"bb, expected",
|
||||
[
|
||||
(test_bb_1, 7986), # 11 * 22 * 33
|
||||
(test_bb_2, 6000), # 10 * 20 * 30
|
||||
(test_bb_3, 4374), # 9 * 18 * 27
|
||||
],
|
||||
)
|
||||
def test_bounding_box_volume(bb, expected):
|
||||
assert bb.volume == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"bb, expected",
|
||||
[
|
||||
(test_bb_1, np.array([-10.0, -20.0, -30.0])),
|
||||
(test_bb_2, np.array([1.0, 2.0, 3.0])),
|
||||
(test_bb_3, np.array([-10.0, -20.0, -30.0])),
|
||||
],
|
||||
)
|
||||
def test_bounding_lower_left(bb, expected):
|
||||
assert np.array_equiv(expected, bb.lower_left)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"bb, expected",
|
||||
[
|
||||
(test_bb_1, np.array([1.0, 2.0, 3.0])),
|
||||
(test_bb_2, np.array([11.0, 22.0, 33.0])),
|
||||
(test_bb_3, np.array([-1.0, -2.0, -3.0])),
|
||||
],
|
||||
)
|
||||
def test_bounding_upper_right(bb, expected):
|
||||
assert np.array_equiv(expected, bb.upper_right)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"bb, expected",
|
||||
[
|
||||
(test_bb_1, np.array([-4.5, -9.0, -13.5])),
|
||||
(test_bb_2, np.array([6.0, 12.0, 18.0])),
|
||||
(test_bb_3, np.array([-5.5, -11.0, -16.5])),
|
||||
],
|
||||
)
|
||||
def test_bounding_box_center(bb, expected):
|
||||
assert np.array_equiv(expected, bb.center)
|
||||
|
||||
|
||||
def test_bounding_box_input_checking():
|
||||
# checks that only passing lower_left is not accepted
|
||||
with pytest.raises(TypeError):
|
||||
openmc.BoundingBox((-10, -20, -3))
|
||||
# checks that a tuple with three entry is not accepted
|
||||
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]))
|
||||
# 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]))
|
||||
# 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]))
|
||||
# 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]))
|
||||
Loading…
Add table
Add a link
Reference in a new issue