mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-27 13:45:36 -04:00
Make Intersection and Union obey the mutable sequence protocol
This commit is contained in:
parent
8265c75cb0
commit
c3c9df6a85
7 changed files with 107 additions and 91 deletions
|
|
@ -357,7 +357,7 @@ class Cell(IDManagerMixin):
|
|||
self.region = region
|
||||
else:
|
||||
if isinstance(self.region, Intersection):
|
||||
self.region.nodes.append(region)
|
||||
self.region &= region
|
||||
else:
|
||||
self.region = Intersection(self.region, region)
|
||||
|
||||
|
|
@ -579,7 +579,7 @@ class Cell(IDManagerMixin):
|
|||
elif isinstance(node, Complement):
|
||||
create_surface_elements(node.node, element)
|
||||
else:
|
||||
for subnode in node.nodes:
|
||||
for subnode in node:
|
||||
create_surface_elements(subnode, element)
|
||||
|
||||
# Call the recursive function from the top node
|
||||
|
|
|
|||
|
|
@ -513,7 +513,7 @@ def create_triso_lattice(trisos, lower_left, pitch, shape, background):
|
|||
universes = np.empty(shape[::-1], dtype=openmc.Universe)
|
||||
for idx, triso_list in sorted(triso_locations.items()):
|
||||
if len(triso_list) > 0:
|
||||
outside_trisos = openmc.Intersection(*[~t.region for t in triso_list])
|
||||
outside_trisos = openmc.Intersection(~t.region for t in triso_list)
|
||||
background_cell = openmc.Cell(fill=background, region=outside_trisos)
|
||||
else:
|
||||
background_cell = openmc.Cell(fill=background)
|
||||
|
|
|
|||
|
|
@ -371,11 +371,11 @@ def get_openmoc_region(openmc_region):
|
|||
openmoc.Halfspace(halfspace, get_openmoc_surface(surface))
|
||||
elif isinstance(openmc_region, openmc.Intersection):
|
||||
openmoc_region = openmoc.Intersection()
|
||||
for openmc_node in openmc_region.nodes:
|
||||
for openmc_node in openmc_region:
|
||||
openmoc_region.addNode(get_openmoc_region(openmc_node))
|
||||
elif isinstance(openmc_region, openmc.Union):
|
||||
openmoc_region = openmoc.Union()
|
||||
for openmc_node in openmc_region.nodes:
|
||||
for openmc_node in openmc_region:
|
||||
openmoc_region.addNode(get_openmoc_region(openmc_node))
|
||||
elif isinstance(openmc_region, openmc.Complement):
|
||||
openmoc_region = openmoc.Complement()
|
||||
|
|
@ -411,12 +411,12 @@ def get_openmc_region(openmoc_region):
|
|||
openmc_region = openmc.Intersection()
|
||||
for openmoc_node in openmoc_region.getNodes():
|
||||
openmc_node = get_openmc_region(openmoc_node)
|
||||
openmc_region.nodes.append(openmc_node)
|
||||
openmc_region.append(openmc_node)
|
||||
elif openmoc_region.getRegionType() == openmoc.UNION:
|
||||
openmc_region = openmc.Union()
|
||||
for openmoc_node in openmoc_region.getNodes():
|
||||
openmc_node = get_openmc_region(openmoc_node)
|
||||
openmc_region.nodes.append(openmc_node)
|
||||
openmc_region.append(openmc_node)
|
||||
elif openmoc_region.getRegionType() == openmoc.COMPLEMENT:
|
||||
openmoc_nodes = openmoc_region.getNodes()
|
||||
openmc_node = get_openmc_region(openmoc_nodes[0])
|
||||
|
|
|
|||
172
openmc/region.py
172
openmc/region.py
|
|
@ -1,5 +1,5 @@
|
|||
from abc import ABCMeta, abstractmethod
|
||||
from collections import Iterable, OrderedDict
|
||||
from collections import Iterable, OrderedDict, MutableSequence
|
||||
from copy import deepcopy
|
||||
|
||||
from six import add_metaclass
|
||||
|
|
@ -20,21 +20,21 @@ class Region(object):
|
|||
|
||||
"""
|
||||
def __and__(self, other):
|
||||
return Intersection(self, other)
|
||||
return Intersection((self, other))
|
||||
|
||||
def __or__(self, other):
|
||||
return Union(self, other)
|
||||
return Union((self, other))
|
||||
|
||||
def __invert__(self):
|
||||
return Complement(self)
|
||||
|
||||
@abstractmethod
|
||||
def __contains__(self, point):
|
||||
return False
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def __str__(self):
|
||||
return ''
|
||||
pass
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, type(self)):
|
||||
|
|
@ -55,7 +55,7 @@ class Region(object):
|
|||
----------
|
||||
surfaces: collections.OrderedDict, optional
|
||||
Dictionary mapping surface IDs to :class:`openmc.Surface` instances
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
surfaces: collections.OrderedDict
|
||||
|
|
@ -150,32 +150,26 @@ class Region(object):
|
|||
r2 = output.pop()
|
||||
if operator == ' ':
|
||||
r1 = output.pop()
|
||||
if isinstance(r1, Intersection) and can_be_combined(r2):
|
||||
r1.nodes.append(r2)
|
||||
if isinstance(r1, Intersection):
|
||||
r1 &= r2
|
||||
output.append(r1)
|
||||
elif isinstance(r2, Intersection) and can_be_combined(r1):
|
||||
r2.nodes.insert(0, r1)
|
||||
r2.insert(0, r1)
|
||||
output.append(r2)
|
||||
elif isinstance(r1, Intersection) and isinstance(r2, Intersection):
|
||||
r1.nodes += r2.nodes
|
||||
output.append(r1)
|
||||
else:
|
||||
output.append(Intersection(r1, r2))
|
||||
output.append(r1 & r2)
|
||||
elif operator == '|':
|
||||
r1 = output.pop()
|
||||
if isinstance(r1, Union) and can_be_combined(r2):
|
||||
r1.nodes.append(r2)
|
||||
if isinstance(r1, Union):
|
||||
r1 |= r2
|
||||
output.append(r1)
|
||||
elif isinstance(r2, Union) and can_be_combined(r1):
|
||||
r2.nodes.insert(0, r1)
|
||||
r2.insert(0, r1)
|
||||
output.append(r2)
|
||||
elif isinstance(r1, Union) and isinstance(r2, Union):
|
||||
r1.nodes += r2.nodes
|
||||
output.append(r1)
|
||||
else:
|
||||
output.append(Union(r1, r2))
|
||||
output.append(r1 | r2)
|
||||
elif operator == '~':
|
||||
output.append(Complement(r2))
|
||||
output.append(~r2)
|
||||
|
||||
# The following is an implementation of the shunting yard algorithm to
|
||||
# generate an abstract syntax tree for the region expression.
|
||||
|
|
@ -248,12 +242,12 @@ class Region(object):
|
|||
'the abstract region class.')
|
||||
|
||||
|
||||
class Intersection(Region):
|
||||
class Intersection(Region, MutableSequence):
|
||||
r"""Intersection of two or more regions.
|
||||
|
||||
Instances of Intersection are generally created via the __and__ operator
|
||||
applied to two instances of :class:`openmc.Region`. This is illustrated in
|
||||
the following example:
|
||||
Instances of Intersection are generally created via the & operator applied
|
||||
to two instances of :class:`openmc.Region`. This is illustrated in the
|
||||
following example:
|
||||
|
||||
>>> equator = openmc.ZPlane(z0=0.0)
|
||||
>>> earth = openmc.Sphere(R=637.1e6)
|
||||
|
|
@ -262,31 +256,51 @@ class Intersection(Region):
|
|||
>>> type(northern_hemisphere)
|
||||
<class 'openmc.region.Intersection'>
|
||||
|
||||
Instances of this class behave like a mutable sequence, e.g., they can be
|
||||
indexed and have an append() method.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
\*nodes
|
||||
nodes : iterable of openmc.Region
|
||||
Regions to take the intersection of
|
||||
|
||||
Attributes
|
||||
----------
|
||||
nodes : list of openmc.Region
|
||||
Regions to take the intersection of
|
||||
bounding_box : tuple of numpy.array
|
||||
Lower-left and upper-right coordinates of an axis-aligned bounding box
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *nodes):
|
||||
self.nodes = list(nodes)
|
||||
def __init__(self, nodes):
|
||||
self._nodes = list(nodes)
|
||||
|
||||
def __and__(self, other):
|
||||
new = Intersection(*self.nodes)
|
||||
new.nodes.append(other)
|
||||
new = Intersection(self)
|
||||
new &= other
|
||||
return new
|
||||
|
||||
def __iter__(self):
|
||||
for n in self.nodes:
|
||||
yield n
|
||||
def __iand__(self, other):
|
||||
if isinstance(other, Intersection):
|
||||
self.extend(other)
|
||||
else:
|
||||
self.append(other)
|
||||
return self
|
||||
|
||||
# Implement mutable sequence protocol by delegating to list
|
||||
def __getitem__(self, key):
|
||||
return self._nodes[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._nodes[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self._nodes[key]
|
||||
|
||||
def __len__(self):
|
||||
return len(self._nodes)
|
||||
|
||||
def insert(self, index, value):
|
||||
self._nodes.insert(index, value)
|
||||
|
||||
def __contains__(self, point):
|
||||
"""Check whether a point is contained in the region.
|
||||
|
|
@ -302,30 +316,21 @@ class Intersection(Region):
|
|||
Whether the point is in the region
|
||||
|
||||
"""
|
||||
return all(point in n for n in self.nodes)
|
||||
return all(point in n for n in self)
|
||||
|
||||
def __str__(self):
|
||||
return '(' + ' '.join(map(str, self.nodes)) + ')'
|
||||
|
||||
@property
|
||||
def nodes(self):
|
||||
return self._nodes
|
||||
return '(' + ' '.join(map(str, self)) + ')'
|
||||
|
||||
@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])
|
||||
for n in self.nodes:
|
||||
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 lower_left, upper_right
|
||||
|
||||
@nodes.setter
|
||||
def nodes(self, nodes):
|
||||
check_type('nodes', nodes, Iterable, Region)
|
||||
self._nodes = nodes
|
||||
|
||||
def clone(self, memo=None):
|
||||
"""Create a copy of this region - each of the surfaces in the
|
||||
intersection's nodes will be cloned and will have new unique IDs.
|
||||
|
|
@ -347,47 +352,67 @@ class Intersection(Region):
|
|||
memo = {}
|
||||
|
||||
clone = deepcopy(self)
|
||||
clone.nodes = [n.clone(memo) for n in self.nodes]
|
||||
clone[:] = [n.clone(memo) for n in self]
|
||||
return clone
|
||||
|
||||
|
||||
class Union(Region):
|
||||
class Union(Region, MutableSequence):
|
||||
r"""Union of two or more regions.
|
||||
|
||||
Instances of Union are generally created via the __or__ operator applied to
|
||||
two instances of :class:`openmc.Region`. This is illustrated in the
|
||||
following example:
|
||||
Instances of Union are generally created via the | operator applied to two
|
||||
instances of :class:`openmc.Region`. This is illustrated in the following
|
||||
example:
|
||||
|
||||
>>> s1 = openmc.ZPlane(z0=0.0)
|
||||
>>> s2 = openmc.Sphere(R=637.1e6)
|
||||
>>> type(-s2 | +s1)
|
||||
<class 'openmc.region.Union'>
|
||||
|
||||
Instances of this class behave like a mutable sequence, e.g., they can be
|
||||
indexed and have an append() method.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
\*nodes
|
||||
nodes : iterable of openmc.Region
|
||||
Regions to take the union of
|
||||
|
||||
Attributes
|
||||
----------
|
||||
nodes : tuple of openmc.Region
|
||||
Regions to take the union of
|
||||
bounding_box : 2-tuple of numpy.array
|
||||
Lower-left and upper-right coordinates of an axis-aligned bounding box
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *nodes):
|
||||
self.nodes = list(nodes)
|
||||
def __init__(self, nodes):
|
||||
self._nodes = list(nodes)
|
||||
|
||||
def __or__(self, other):
|
||||
new = Union(*self.nodes)
|
||||
new.nodes.append(other)
|
||||
new = Union(self)
|
||||
new |= other
|
||||
return new
|
||||
|
||||
def __iter__(self):
|
||||
for n in self.nodes:
|
||||
yield n
|
||||
def __ior__(self, other):
|
||||
if isinstance(other, Union):
|
||||
self.extend(other)
|
||||
else:
|
||||
self.append(other)
|
||||
return self
|
||||
|
||||
# Implement mutable sequence protocol by delegating to list
|
||||
def __getitem__(self, key):
|
||||
return self._nodes[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._nodes[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self._nodes[key]
|
||||
|
||||
def __len__(self):
|
||||
return len(self._nodes)
|
||||
|
||||
def insert(self, index, value):
|
||||
self._nodes.insert(index, value)
|
||||
|
||||
def __contains__(self, point):
|
||||
"""Check whether a point is contained in the region.
|
||||
|
|
@ -403,30 +428,21 @@ class Union(Region):
|
|||
Whether the point is in the region
|
||||
|
||||
"""
|
||||
return any(point in n for n in self.nodes)
|
||||
return any(point in n for n in self)
|
||||
|
||||
def __str__(self):
|
||||
return '(' + ' | '.join(map(str, self.nodes)) + ')'
|
||||
|
||||
@property
|
||||
def nodes(self):
|
||||
return self._nodes
|
||||
return '(' + ' | '.join(map(str, self)) + ')'
|
||||
|
||||
@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])
|
||||
for n in self.nodes:
|
||||
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 lower_left, upper_right
|
||||
|
||||
@nodes.setter
|
||||
def nodes(self, nodes):
|
||||
check_type('nodes', nodes, Iterable, Region)
|
||||
self._nodes = nodes
|
||||
|
||||
def clone(self, memo=None):
|
||||
"""Create a copy of this region - each of the surfaces in the
|
||||
union's nodes will be cloned and will have new unique IDs.
|
||||
|
|
@ -448,7 +464,7 @@ class Union(Region):
|
|||
memo = {}
|
||||
|
||||
clone = copy.deepcopy(self)
|
||||
clone.nodes = [n.clone(memo) for n in self.nodes]
|
||||
clone[:] = [n.clone(memo) for n in self]
|
||||
return clone
|
||||
|
||||
|
||||
|
|
@ -456,7 +472,7 @@ class Complement(Region):
|
|||
"""Complement of a region.
|
||||
|
||||
The Complement of an existing :class:`openmc.Region` can be created by using
|
||||
the __invert__ operator as the following example demonstrates:
|
||||
the ~ operator as the following example demonstrates:
|
||||
|
||||
>>> xl = openmc.XPlane(x0=-10.0)
|
||||
>>> xr = openmc.XPlane(x0=10.0)
|
||||
|
|
@ -518,9 +534,9 @@ class Complement(Region):
|
|||
# only applies to surface half-spaces, thus allowing us to calculate the
|
||||
# bounding box in the usual recursive manner.
|
||||
if isinstance(self.node, Union):
|
||||
temp_region = Intersection(*[~n for n in self.node.nodes])
|
||||
temp_region = Intersection(~n for n in self.node)
|
||||
elif isinstance(self.node, Intersection):
|
||||
temp_region = Union(*[~n for n in self.node.nodes])
|
||||
temp_region = Union(~n for n in self.node)
|
||||
elif isinstance(self.node, Complement):
|
||||
temp_region = self.node.node
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -1843,15 +1843,15 @@ class Halfspace(Region):
|
|||
|
||||
def __and__(self, other):
|
||||
if isinstance(other, Intersection):
|
||||
return Intersection(self, *other.nodes)
|
||||
return Intersection([self] + other[:])
|
||||
else:
|
||||
return Intersection(self, other)
|
||||
return Intersection((self, other))
|
||||
|
||||
def __or__(self, other):
|
||||
if isinstance(other, Union):
|
||||
return Union(self, *other.nodes)
|
||||
return Union([self] + other[:])
|
||||
else:
|
||||
return Union(self, other)
|
||||
return Union((self, other))
|
||||
|
||||
def __invert__(self):
|
||||
return -self.surface if self.side == '+' else +self.surface
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ class Universe(IDManagerMixin):
|
|||
regions = [c.region for c in self.cells.values()
|
||||
if c.region is not None]
|
||||
if regions:
|
||||
return openmc.Union(*regions).bounding_box
|
||||
return openmc.Union(regions).bounding_box
|
||||
else:
|
||||
# Infinite bounding box
|
||||
return openmc.Intersection().bounding_box
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ class VolumeCalculation(object):
|
|||
self.upper_right = upper_right
|
||||
else:
|
||||
if self.domain_type == 'cell':
|
||||
ll, ur = openmc.Union(*[c.region for c in domains]).bounding_box
|
||||
ll, ur = openmc.Union(c.region for c in domains).bounding_box
|
||||
if np.any(np.isinf(ll)) or np.any(np.isinf(ur)):
|
||||
raise ValueError('Could not automatically determine bounding '
|
||||
'box for stochastic volume calculation.')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue