Skip to content

Commit

Permalink
Add a vessel composite surface with ellipsoids on top and bottom. (#3168
Browse files Browse the repository at this point in the history
)

Co-authored-by: Patrick Shriwise <[email protected]>
Co-authored-by: Paul Romano <[email protected]>
  • Loading branch information
3 people authored Nov 12, 2024
1 parent 3e2a042 commit 0ecd45c
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 5 deletions.
1 change: 1 addition & 0 deletions docs/source/pythonapi/model.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Composite Surfaces
openmc.model.RectangularParallelepiped
openmc.model.RectangularPrism
openmc.model.RightCircularCylinder
openmc.model.Vessel
openmc.model.XConeOneSided
openmc.model.YConeOneSided
openmc.model.ZConeOneSided
Expand Down
105 changes: 100 additions & 5 deletions openmc/model/surface_composite.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ def boundary_type(self):
def boundary_type(self, boundary_type):
# Set boundary type on underlying surfaces, but not for ambiguity plane
# on one-sided cones
classes = (XConeOneSided, YConeOneSided, ZConeOneSided, Vessel)
for name in self._surface_names:
if name != 'plane':
getattr(self, name).boundary_type = boundary_type
if isinstance(self, classes) and name.startswith('plane'):
continue
getattr(self, name).boundary_type = boundary_type

def __repr__(self):
return f"<{type(self).__name__} at 0x{id(self):x}>"
Expand Down Expand Up @@ -90,8 +92,8 @@ class CylinderSector(CompositeSurface):
counterclockwise direction with respect to the first basis axis
(+y, +z, or +x). Must be greater than :attr:`theta1`.
center : iterable of float
Coordinate for central axes of cylinders in the (y, z), (x, z), or (x, y)
basis. Defaults to (0,0).
Coordinate for central axes of cylinders in the (y, z), (x, z), or (x, y)
basis. Defaults to (0,0).
axis : {'x', 'y', 'z'}
Central axis of the cylinders defining the inner and outer surfaces of
the sector. Defaults to 'z'.
Expand Down Expand Up @@ -119,7 +121,7 @@ def __init__(self,
r2,
theta1,
theta2,
center=(0.,0.),
center=(0., 0.),
axis='z',
**kwargs):

Expand Down Expand Up @@ -1856,3 +1858,96 @@ def __init__(self, center_base: Sequence[float], axis: Sequence[float],

def __neg__(self) -> openmc.Region:
return +self.plane_bottom & -self.plane_top & -self.cone


class Vessel(CompositeSurface):
"""Vessel composed of cylinder with semi-ellipsoid top and bottom.
This composite surface is represented by a finite cylinder with ellipsoidal
top and bottom surfaces. This surface is equivalent to the 'vesesl' surface
in Serpent.
.. versionadded:: 0.15.1
Parameters
----------
r : float
Radius of vessel.
p1 : float
Minimum coordinate for cylindrical part of vessel.
p2 : float
Maximum coordinate for cylindrical part of vessel.
h1 : float
Height of bottom ellipsoidal part of vessel.
h2 : float
Height of top ellipsoidal part of vessel.
center : 2-tuple of float
Coordinate for central axis of the cylinder in the (y, z), (x, z), or
(x, y) basis. Defaults to (0,0).
axis : {'x', 'y', 'z'}
Central axis of the cylinder.
"""

_surface_names = ('cyl', 'plane_bottom', 'plane_top', 'bottom', 'top')

def __init__(self, r: float, p1: float, p2: float, h1: float, h2: float,
center: Sequence[float] = (0., 0.), axis: str = 'z', **kwargs):
if p1 >= p2:
raise ValueError('p1 must be less than p2')
check_value('axis', axis, {'x', 'y', 'z'})

c1, c2 = center
cyl_class = getattr(openmc, f'{axis.upper()}Cylinder')
plane_class = getattr(openmc, f'{axis.upper()}Plane')
self.cyl = cyl_class(c1, c2, r, **kwargs)
self.plane_bottom = plane_class(p1)
self.plane_top = plane_class(p2)

# General equation for an ellipsoid:
# (x-x₀)²/r² + (y-y₀)²/r² + (z-z₀)²/h² = 1
# (x-x₀)² + (y-y₀)² + (z-z₀)²s² = r²
# Let s = r/h:
# (x² - 2x₀x + x₀²) + (y² - 2y₀y + y₀²) + (z² - 2z₀z + z₀²)s² = r²
# x² + y² + s²z² - 2x₀x - 2y₀y - 2s²z₀z + (x₀² + y₀² + z₀²s² - r²) = 0

sb = (r/h1)
st = (r/h2)
kwargs['a'] = kwargs['b'] = kwargs['c'] = 1.0
kwargs_bottom = kwargs
kwargs_top = kwargs.copy()

sb2 = sb*sb
st2 = st*st
kwargs_bottom['k'] = c1*c1 + c2*c2 + p1*p1*sb2 - r*r
kwargs_top['k'] = c1*c1 + c2*c2 + p2*p2*st2 - r*r

if axis == 'x':
kwargs_bottom['a'] *= sb2
kwargs_top['a'] *= st2
kwargs_bottom['g'] = -2*p1*sb2
kwargs_top['g'] = -2*p2*st2
kwargs_top['h'] = kwargs_bottom['h'] = -2*c1
kwargs_top['j'] = kwargs_bottom['j'] = -2*c2
elif axis == 'y':
kwargs_bottom['b'] *= sb2
kwargs_top['b'] *= st2
kwargs_top['g'] = kwargs_bottom['g'] = -2*c1
kwargs_bottom['h'] = -2*p1*sb2
kwargs_top['h'] = -2*p2*st2
kwargs_top['j'] = kwargs_bottom['j'] = -2*c2
elif axis == 'z':
kwargs_bottom['c'] *= sb2
kwargs_top['c'] *= st2
kwargs_top['g'] = kwargs_bottom['g'] = -2*c1
kwargs_top['h'] = kwargs_bottom['h'] = -2*c2
kwargs_bottom['j'] = -2*p1*sb2
kwargs_top['j'] = -2*p2*st2

self.bottom = openmc.Quadric(**kwargs_bottom)
self.top = openmc.Quadric(**kwargs_top)

def __neg__(self):
return ((-self.cyl & +self.plane_bottom & -self.plane_top) |
(-self.bottom & -self.plane_bottom) |
(-self.top & +self.plane_top))
49 changes: 49 additions & 0 deletions tests/unit_tests/test_surface_composite.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,3 +599,52 @@ def test_conical_frustum():
# Denegenerate case with r1 = r2
s = openmc.model.ConicalFrustum(center_base, axis, r1, r1)
assert (1., 1., -0.01) in -s


def test_vessel():
center = (3.0, 2.0)
r = 1.0
p1, p2 = -5.0, 5.0
h1 = h2 = 1.0
s = openmc.model.Vessel(r, p1, p2, h1, h2, center)
assert isinstance(s.cyl, openmc.Cylinder)
assert isinstance(s.plane_bottom, openmc.Plane)
assert isinstance(s.plane_top, openmc.Plane)
assert isinstance(s.bottom, openmc.Quadric)
assert isinstance(s.top, openmc.Quadric)

# Make sure boundary condition propagates (but not for planes)
s.boundary_type = 'reflective'
assert s.boundary_type == 'reflective'
assert s.cyl.boundary_type == 'reflective'
assert s.bottom.boundary_type == 'reflective'
assert s.top.boundary_type == 'reflective'
assert s.plane_bottom.boundary_type == 'transmission'
assert s.plane_top.boundary_type == 'transmission'

# Check bounding box
ll, ur = (+s).bounding_box
assert np.all(np.isinf(ll))
assert np.all(np.isinf(ur))
ll, ur = (-s).bounding_box
assert np.all(np.isinf(ll))
assert np.all(np.isinf(ur))

# __contains__ on associated half-spaces
assert (3., 2., 0.) in -s
assert (3., 2., -5.0) in -s
assert (3., 2., 5.0) in -s
assert (3., 2., -5.9) in -s
assert (3., 2., 5.9) in -s
assert (3., 2., -6.1) not in -s
assert (3., 2., 6.1) not in -s
assert (4.5, 2., 0.) in +s
assert (3., 3.2, 0.) in +s
assert (3., 2., 7.) in +s

# translate method
s_t = s.translate((0., 0., 1.))
assert (3., 2., 6.1) in -s_t

# Make sure repr works
repr(s)

0 comments on commit 0ecd45c

Please sign in to comment.