Make Intersection and Union obey the mutable sequence protocol

This commit is contained in:
Paul Romano 2017-06-02 21:58:05 -04:00
parent 8265c75cb0
commit c3c9df6a85
7 changed files with 107 additions and 91 deletions

View file

@ -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

View file

@ -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)

View file

@ -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])

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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.')