diff --git a/openmc/cell.py b/openmc/cell.py index 9993175efd..4496062f7f 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -1,4 +1,5 @@ from collections import OrderedDict, Iterable +from copy import deepcopy from math import cos, sin, pi from numbers import Real, Integral from xml.etree import ElementTree as ET @@ -504,6 +505,44 @@ class Cell(object): return universes + def clone(self, memo=None): + """Create a copy of this cell with a new unique ID, and clones + the cell's region and fill. + + Parameters + ---------- + memo : dict or None + A nested dictionary of previously cloned objects. This parameter + is used internally and should not be specified by the user. + + Returns + ------- + clone : openmc.Cell + The clone of this cell + + """ + + if memo is None: + memo = {} + + # If no nemoize'd clone exists, instantiate one + if self not in memo: + clone = deepcopy(self) + clone.id = None + if self.region is not None: + clone.region = self.region.clone(memo) + if self.fill is not None: + if self.fill_type == 'distribmat': + clone.fill = [fill.clone(memo) if fill is not None else None + for fill in self.fill] + else: + clone.fill = self.fill.clone(memo) + + # Memoize the clone + memo[self] = clone + + return memo[self] + def create_xml_subelement(self, xml_element): element = ET.Element("cell") element.set("id", str(self.id)) diff --git a/openmc/geometry.py b/openmc/geometry.py index ce7d47d9e2..27fee65739 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -1,4 +1,5 @@ from collections import OrderedDict, Iterable +from copy import deepcopy from xml.etree import ElementTree as ET from six import string_types @@ -497,3 +498,11 @@ class Geometry(object): # Recursively traverse the CSG tree to count all cell instances self.root_universe._determine_paths() + + def clone(self): + """Create a copy of this geometry with new unique IDs for all of its + enclosed materials, surfaces, cells, universes and lattices.""" + + clone = deepcopy(self) + clone.root_universe = self.root_universe.clone() + return clone diff --git a/openmc/lattice.py b/openmc/lattice.py index 08ca24a192..3bb6e825e1 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -2,6 +2,7 @@ from __future__ import division from abc import ABCMeta from collections import OrderedDict, Iterable +from copy import deepcopy from math import sqrt, floor from numbers import Real, Integral from xml.etree import ElementTree as ET @@ -414,6 +415,51 @@ class Lattice(object): return [] return [(self, idx)] + u.find(p) + def clone(self, memo=None): + """Create a copy of this lattice with a new unique ID, and clones + all universes within this lattice. + + Parameters + ---------- + memo : dict or None + A nested dictionary of previously cloned objects. This parameter + is used internally and should not be specified by the user. + + Returns + ------- + clone : openmc.Lattice + The clone of this lattice + + """ + + if memo is None: + memo = {} + + # If no nemoize'd clone exists, instantiate one + if self not in memo: + clone = deepcopy(self) + clone.id = None + + if self.outer is not None: + clone.outer = self.outer.clone(memo) + + # Assign universe clones to the lattice clone + for i in self.indices: + if isinstance(self, RectLattice): + clone.universes[i] = self.universes[i].clone(memo) + else: + if self.ndim == 2: + clone.universes[i[0]][i[1]] = \ + self.universes[i[0]][i[1]].clone(memo) + else: + clone.universes[i[0]][i[1]][i[2]] = \ + self.universes[i[0]][i[1]][i[2]].clone(memo) + + # Memoize the clone + memo[self] = clone + + return memo[self] + class RectLattice(Lattice): """A lattice consisting of rectangular prisms. diff --git a/openmc/material.py b/openmc/material.py index 26b1bce8fc..87cc6befaa 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -799,6 +799,35 @@ class Material(object): return nuclides + def clone(self, memo=None): + """Create a copy of this material with a new unique ID. + + Parameters + ---------- + memo : dict or None + A nested dictionary of previously cloned objects. This parameter + is used internally and should not be specified by the user. + + Returns + ------- + clone : openmc.Material + The clone of this material + + """ + + if memo is None: + memo = {} + + # If no nemoize'd clone exists, instantiate one + if self not in memo: + clone = deepcopy(self) + clone.id = None + + # Memoize the clone + memo[self] = clone + + return memo[self] + def _get_nuclide_xml(self, nuclide, distrib=False): xml_element = ET.Element("nuclide") xml_element.set("name", nuclide[0].name) diff --git a/openmc/region.py b/openmc/region.py index a2a92c15a3..854852ba41 100644 --- a/openmc/region.py +++ b/openmc/region.py @@ -1,5 +1,6 @@ from abc import ABCMeta, abstractmethod from collections import Iterable, OrderedDict +from copy import deepcopy from six import add_metaclass import numpy as np @@ -221,6 +222,31 @@ class Region(object): # at the end return output[0] + @abstractmethod + def clone(self, memo=None): + """Create a copy of this region - each of the surfaces in the + region's nodes will be cloned and will have new unique IDs. + + Parameters + ---------- + memo : dict or None + A nested dictionary of previously cloned objects. This parameter + is used internally and should not be specified by the user. + + Returns + ------- + clone : openmc.Region + The clone of this region + + Raises + ------ + NotImplementedError + This method is not implemented for the abstract region class. + + """ + raise NotImplementedError('The clone method is not implemented for ' + 'the abstract region class.') + class Intersection(Region): r"""Intersection of two or more regions. @@ -300,6 +326,30 @@ class Intersection(Region): 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. + + Parameters + ---------- + memo : dict or None + A nested dictionary of previously cloned objects. This parameter + is used internally and should not be specified by the user. + + Returns + ------- + clone : openmc.Intersection + The clone of this intersection + + """ + + if memo is None: + memo = {} + + clone = deepcopy(self) + clone.nodes = [n.clone(memo) for n in self.nodes] + return clone + class Union(Region): r"""Union of two or more regions. @@ -377,6 +427,30 @@ class Union(Region): 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. + + Parameters + ---------- + memo : dict or None + A nested dictionary of previously cloned objects. This parameter + is used internally and should not be specified by the user. + + Returns + ------- + clone : openmc.Union + The clone of this union + + """ + + if memo is None: + memo = {} + + clone = copy.deepcopy(self) + clone.nodes = [n.clone(memo) for n in self.nodes] + return clone + class Complement(Region): """Complement of a region. @@ -473,3 +547,27 @@ class Complement(Region): for region in self.node: surfaces = region.get_surfaces(surfaces) return surfaces + + def clone(self, memo=None): + """Create a copy of this region - each of the surfaces in the + complement's node will be cloned and will have new unique IDs. + + Parameters + ---------- + memo : dict or None + A nested dictionary of previously cloned objects. This parameter + is used internally and should not be specified by the user. + + Returns + ------- + clone : openmc.Complement + The clone of this complement + + """ + + if memo is None: + memo = {} + + clone = copy.deepcopy(self) + clone.node = self.node.clone(memo) + return clone diff --git a/openmc/surface.py b/openmc/surface.py index 9c6be78a77..5db15138d4 100644 --- a/openmc/surface.py +++ b/openmc/surface.py @@ -1,5 +1,6 @@ from abc import ABCMeta from collections import Iterable, OrderedDict +from copy import deepcopy from numbers import Real, Integral from xml.etree import ElementTree as ET from math import sqrt @@ -82,6 +83,9 @@ class Surface(object): def __pos__(self): return Halfspace(self, '+') + def __hash__(self): + return hash(repr(self)) + def __repr__(self): string = 'Surface\n' string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id) @@ -171,6 +175,35 @@ class Surface(object): return (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. + + Parameters + ---------- + memo : dict or None + A nested dictionary of previously cloned objects. This parameter + is used internally and should not be specified by the user. + + Returns + ------- + clone : openmc.Surface + The clone of this surface + + """ + + if memo is None: + memo = {} + + # If no nemoize'd clone exists, instantiate one + if self not in memo: + clone = deepcopy(self) + clone.id = None + + # Memoize the clone + memo[self] = clone + + return memo[self] + def to_xml_element(self): """Return XML representation of the surface @@ -1821,7 +1854,7 @@ class Halfspace(Region): def __init__(self, surface, side): self.surface = surface self.side = side - + def __and__(self, other): if isinstance(other, Intersection): return Intersection(self, *other.nodes) @@ -1902,6 +1935,30 @@ class Halfspace(Region): surfaces[self.surface.id] = self.surface return surfaces + def clone(self, memo=None): + """Create a copy of this halfspace, with a cloned surface with a + unique ID. + + Parameters + ---------- + memo : dict or None + A nested dictionary of previously cloned objects. This parameter + is used internally and should not be specified by the user. + + Returns + ------- + clone : openmc.Halfspace + The clone of this halfspace + + """ + + if memo is None: + memo = dict + + clone = deepcopy(self) + clone.surface = self.surface.clone(memo) + return clone + def get_rectangular_prism(width, height, axis='z', origin=(0., 0.), boundary_type='transmission'): diff --git a/openmc/universe.py b/openmc/universe.py index 1c631e7e07..c955934f2a 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -1,6 +1,6 @@ from __future__ import division -from copy import copy from collections import OrderedDict, Iterable +from copy import copy, deepcopy from numbers import Integral, Real import random import sys @@ -517,6 +517,41 @@ class Universe(object): return universes + def clone(self, memo=None): + """Create a copy of this universe with a new unique ID, and clones + all cells within this universe. + + Parameters + ---------- + memo : dict or None + A nested dictionary of previously cloned objects. This parameter + is used internally and should not be specified by the user. + + Returns + ------- + clone : openmc.Universe + The clone of this universe + + """ + + if memo is None: + memo = {} + + # If no nemoize'd clone exists, instantiate one + if self not in memo: + clone = deepcopy(self) + clone.id = None + + # Clone all cells for the universe clone + clone._cells = OrderedDict() + for cell in self._cells.values(): + clone.add_cell(cell.clone(memo)) + + # Memoize the clone + memo[self] = clone + + return memo[self] + def create_xml_subelement(self, xml_element): # Iterate over all Cells for cell_id, cell in self._cells.items():