diff --git a/openmc/cell.py b/openmc/cell.py index 5f8baa589d..d962c27253 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -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 diff --git a/openmc/model/triso.py b/openmc/model/triso.py index 2090dc977d..67bf3bc442 100644 --- a/openmc/model/triso.py +++ b/openmc/model/triso.py @@ -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) diff --git a/openmc/openmoc_compatible.py b/openmc/openmoc_compatible.py index 21dc4b6a69..d18070740c 100644 --- a/openmc/openmoc_compatible.py +++ b/openmc/openmoc_compatible.py @@ -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]) diff --git a/openmc/region.py b/openmc/region.py index 854852ba41..eeafd28326 100644 --- a/openmc/region.py +++ b/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) + 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) + 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: diff --git a/openmc/surface.py b/openmc/surface.py index dca9a76bd1..b415ecb93a 100644 --- a/openmc/surface.py +++ b/openmc/surface.py @@ -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 diff --git a/openmc/universe.py b/openmc/universe.py index 95a1341fd2..26977ac0a2 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -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 diff --git a/openmc/volume.py b/openmc/volume.py index b81004ba11..721dcf2485 100644 --- a/openmc/volume.py +++ b/openmc/volume.py @@ -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.')