From 5c48f325b09e15e01887844155dda465fbaee511 Mon Sep 17 00:00:00 2001 From: Giud Date: Mon, 14 Oct 2019 15:28:48 -0400 Subject: [PATCH 01/14] Implement degenerate and recursive LNS lattice discretization schemes --- openmc/cell.py | 19 ++- openmc/geometry.py | 3 +- openmc/lattice.py | 319 ++++++++++++++++++++++++++++++++++++++++++++- openmc/universe.py | 7 +- 4 files changed, 336 insertions(+), 12 deletions(-) diff --git a/openmc/cell.py b/openmc/cell.py index 3fd70b3455..4a9ff9574d 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -409,12 +409,15 @@ class Cell(IDManagerMixin): return universes - def clone(self, memo=None): + def clone(self, clone_materials=True, memo=None): """Create a copy of this cell with a new unique ID, and clones the cell's region and fill. Parameters ---------- + clone_materials : boolean + Whether to create separates copies of the materials filling cells + under this cell in the CSG tree, or the material filling this cell memo : dict or None A nested dictionary of previously cloned objects. This parameter is used internally and should not be specified by the user. @@ -446,10 +449,18 @@ class Cell(IDManagerMixin): 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] + if not clone_materials: + clone.fill = self.fill + else: + clone.fill = [fill.clone(memo) if fill is not None else + None for fill in self.fill] + elif self.fill_type == 'material': + if not clone_materials: + clone.fill = self.fill + else: + clone.fill = self.fill.clone(memo) else: - clone.fill = self.fill.clone(memo) + clone.fill = self.fill.clone(clone_materials, memo) # Memoize the clone memo[self] = clone diff --git a/openmc/geometry.py b/openmc/geometry.py index 260bb9e83c..71eca37a6e 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -376,7 +376,8 @@ class Geometry(object): surfaces = OrderedDict() for cell in self.get_all_cells().values(): - surfaces = cell.region.get_surfaces(surfaces) + if cell.region is not None: + surfaces = cell.region.get_surfaces(surfaces) return surfaces def get_materials_by_name(self, name, case_sensitive=False, matching=False): diff --git a/openmc/lattice.py b/openmc/lattice.py index 4776222da2..a0f75c315c 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -7,6 +7,7 @@ from numbers import Real, Integral from xml.etree import ElementTree as ET import numpy as np +import warnings import openmc.checkvalue as cv import openmc @@ -448,12 +449,15 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): return [] return [(self, idx)] + u.find(p) - def clone(self, memo=None): + def clone(self, clone_materials=True, memo=None): """Create a copy of this lattice with a new unique ID, and clones all universes within this lattice. Parameters ---------- + clone_materials : boolean + Whether to create separates copies of the materials filling cells + under this lattice in the CSG tree memo : dict or None A nested dictionary of previously cloned objects. This parameter is used internally and should not be specified by the user. @@ -474,19 +478,22 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): clone.id = None if self.outer is not None: - clone.outer = self.outer.clone(memo) + clone.outer = self.outer.clone(clone_materials, 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) + clone.universes[i] = self.universes[i].clone( + clone_materials, memo) else: if self.ndim == 2: clone.universes[i[0]][i[1]] = \ - self.universes[i[0]][i[1]].clone(memo) + self.universes[i[0]][i[1]].clone(clone_materials, + memo) else: clone.universes[i[0]][i[1]][i[2]] = \ - self.universes[i[0]][i[1]][i[2]].clone(memo) + self.universes[i[0]][i[1]][i[2]].clone( + clone_materials, memo) # Memoize the clone memo[self] = clone @@ -753,6 +760,308 @@ class RectLattice(Lattice): 0 <= idx[1] < self.shape[1] and 0 <= idx[2] < self.shape[2]) + def discretize(self, strategy="degenerate", universes_to_ignore=[], + materials_to_clone=[], + lattice_neighbors=[], attribute="id"): + """Discretize the lattice with either degenerate or a recursive local + neighbor symmetry strategy + + Degenerate clones every universe in the lattice, thus making them all + uniquely defined. This is typically required if depletion or thermal + hydraulics will make every fuel pin environment unique. + + Local neighbor symmetry separates fuel pins with similar neighborhoods. + These clusters of cells and materials provide increased convergence + speed to multi-group cross sections tallies. The recursion allows + the modelling of neighbor universes on multiple scales, for example + to discriminate between the baffle and the neighbor lattices for a + latttice in the outer part of a light water reactor. The recursion is + only implemented for lattices exactly right below the starting lattice + (starting lattice contains universe contains cell contains the lattice) + + Parameters + ---------- + strategy : String + Which strategy to adopt when discretizing the lattice + universes_to_ignore : Iterable of Universe + Lattice universes that need not be discretized + materials_to_clone : Iterable of Material + List of materials that should be cloned when discretizing + lattice_neighbors : Iterable of Int or String + List of attributes of the lattice's neighbors. By default, if + present, the lattice outer universe will be used. The neighbors + are represented as follows [top left, top, top right, left, right, + bottom left, bottom, bottom right] + attribute : String + Which universe attribute to use when detecting neighbor symmetry + patterns. Can be the 'id' or the 'name' of the lattice universes + + """ + + # Check routine inputs + if self.ndim == 3: + raise NotImplementedError("LNS discretization is not implemented for 3D lattices") + + cv.check_value('strategy', strategy, ('degenerate', 'lns')) + cv.check_type('universes_to_ignore', universes_to_ignore, Iterable, + openmc.Universe) + cv.check_type('materials_to_clone', materials_to_clone, Iterable, + openmc.Material) + cv.check_value('number of lattice_neighbors', len(lattice_neighbors), (0, 8)) + cv.check_value('attribute', attribute, ('id', 'name')) + + if len(lattice_neighbors) > 0: + if attribute == 'id': + cv.check_type('lattice_neighbors', lattice_neighbors, + Iterable, int) + elif attribute == 'name': + cv.check_type('lattice_neighbors', lattice_neighbors, + Iterable, str) + + # Use outer universe if neighbors are missing and outer is defined + if self.outer is not None and len(lattice_neighbors) == 0: + lattice_neighbors = [getattr(self.outer, attribute) for i in range(8)] + + # Create dummy neighbor for when the neighbor is not known at the edge + if len(lattice_neighbors) == 0: + if attribute == 'id': + dummy_neighbor = -1 + elif attribute == 'name': + dummy_neighbor = "dummy" + + # Dictionary that will keep track of where each pattern appears, how + # it was rotated and/or symmetrized + patterns = {} + + # Analyze lattice, find unique patterns, ignoring rotations and symmetries + for j in range(self.shape[1]): + for i in range(self.shape[0]): + + # Skip universes to ignore + if self.universes[j][i] in universes_to_ignore: + continue + + # Form the pattern at and around each universe + if attribute == "id": + pattern = np.empty(shape=(3, 3), dtype=np.int) + if attribute == "name": + pattern = np.empty(shape=(3, 3), dtype=np.dtype('U100')) + if len(self.universes[j][i].name) > 100: + warning.warn("Universe name is larger than 100 " + "characters, which may hinder neighbor search.") + + pattern[1, 1] = getattr(self.universes[j][i], attribute) + + # Create a neighborhood pattern based on the universe's + # neighbors in the grid, and the lattice neighbors at the edges + if i == 0: + if len(lattice_neighbors) > 0: + pattern[:, 0] = lattice_neighbors[3] + if j == 0: + pattern[0, 0] = lattice_neighbors[0] + elif j == self.shape[1] - 1: + pattern[2, 0] = lattice_neighbors[5] + else: + pattern[:, 0] = dummy_neighbor + else: + if j > 0: + pattern[0, 0] = getattr(self.universes[j-1][i-1], + attribute) + pattern[1, 0] = getattr(self.universes[j][i-1], attribute) + if j < self.shape[1] - 1: + pattern[2, 0] = getattr(self.universes[j+1][i-1], + attribute) + + if j == 0: + if len(lattice_neighbors) > 0: + pattern[0, 1] = lattice_neighbors[1] + if i != 0: + pattern[0, 0] = lattice_neighbors[1] + if i != self.shape[0] - 1: + pattern[0, 2] = lattice_neighbors[1] + else: + pattern[0, :] = dummy_neighbor + else: + if i > 0: + pattern[0, 0] = getattr(self.universes[j-1][i-1], + attribute) + pattern[0, 1] = getattr(self.universes[j-1][i], attribute) + if i < self.shape[0] - 1: + pattern[0, 2] = getattr(self.universes[j-1][i+1], + attribute) + + if i == self.shape[0] - 1: + if len(lattice_neighbors) > 0: + pattern[:, 2] = lattice_neighbors[4] + if j == 0: + pattern[0, 2] = lattice_neighbors[2] + elif j == self.shape[1] - 1: + pattern[2, 2] = lattice_neighbors[7] + else: + pattern[:, 2] = dummy_neighbor + else: + if j > 0: + pattern[0, 2] = getattr(self.universes[j-1][i+1], + attribute) + pattern[1, 2] = getattr(self.universes[j][i+1], attribute) + if j < self.shape[1] - 1: + pattern[2, 2] = getattr(self.universes[j+1][i+1], + attribute) + + if j == self.shape[1] - 1: + if len(lattice_neighbors) > 0: + pattern[2, 1] = lattice_neighbors[6] + if i != 0: + pattern[2, 0] = lattice_neighbors[6] + if i != self.shape[0] - 1: + pattern[2, 2] = lattice_neighbors[6] + else: + pattern[2, :] = dummy_neighbor + else: + if i > 0: + pattern[2, 0] = getattr(self.universes[j+1][i-1], + attribute) + pattern[2, 1] = getattr(self.universes[j+1][i], attribute) + if i < self.shape[0] - 1: + pattern[2, 2] = getattr(self.universes[j+1][i+1], + attribute) + + # Look for pattern in dictionary of patterns found + found = False + for known_pattern, pattern_data in patterns.items(): + + # Degenerate discretizations use unique patterns + if strategy == "degenerate": + break + + # Look at all rotations of pattern + for rot in range(4): + if not found and tuple(map(tuple, pattern)) == known_pattern: + found = True + + # Save location of the pattern in the lattice + pattern_data['locations'].append((i, j)) + + # Save the pattern rotation at this location + pattern_data['rotations'].append(rot) + + # Save that the pattern was not transposed + pattern_data['transpositions'].append(False) + + # Rotate pattern + pattern = np.rot90(pattern) + + # Look at transpose of pattern and its rotations + pattern = np.transpose(pattern) + for rot in range(4): + if not found and tuple(map(tuple, pattern)) == known_pattern: + found = True + + # Save location of the pattern in the lattice + pattern_data['locations'].append((i, j)) + + # Save the pattern rotation at this location + pattern_data['rotations'].append(rot) + + # Save that the pattern was transposed + pattern_data['transpositions'].append(True) + + # Rotate pattern + pattern = np.rot90(pattern) + + # Transpose pattern back for the next search + pattern = np.transpose(pattern) + + # Create new pattern and add to the patterns dictionary + if not found: + pattern_data = {} + pattern_data['locations'] = [(i, j)] + pattern_data['rotations'] = [0] + pattern_data['transpositions'] = [False] + if strategy == "lns": + patterns[tuple(map(tuple, pattern))] = pattern_data + elif strategy == "degenerate": + patterns[(i,j)] = pattern_data + + # Discretize lattice + for pattern, pattern_data in patterns.items(): + + first_pos = pattern_data['locations'][0] + + # Create a clone of the universe, without cloning materials + new_universe = self.universes[first_pos[1]][first_pos[0]].clone( + clone_materials=False) + + # Call LNS on the immediate sub lattices of this universe + for cell_id, sub_cell in new_universe.cells.items(): + + sub_universe = sub_cell.fill + + try: + sub_neighbors = [pattern[0][0], pattern[0][1], pattern[0][2], + pattern[1][0], pattern[1][2], + pattern[2][0], pattern[2][1], pattern[2][2]] + sub_universe.discretize(strategy, universes_to_ignore, + materials_to_clone, + lattice_neighbors=sub_neighbors, + attribute=attribute) + except: + continue + + # Replace only the materials in materials_to_clone + for material in materials_to_clone: + material_cloned = False + + for cell in new_universe.get_all_cells().values(): + if cell.fill.id == material.id: + + # Only a single clone of each material is necessary + if not material_cloned: + material_clone = material.clone() + material_cloned = True + + cell.fill = material_clone + + # Build a symmetric universe for the transposed lattices + if any(pattern_data['transpositions']): + sym_universe = new_universe.clone(clone_materials=False) + for cell_id, sub_cell in sym_universe.cells.items(): + sub_universe = sub_cell.fill + # NOTE: this will fail if there are two nested lattices + if "Lattice" in str(type(sub_universe)): + sub_universe.universes = np.transpose( + sub_universe.universes) + + # Rebuild lattice from pattern using rotation and symmetries + for index, location in enumerate(pattern_data['locations']): + + i = location[0] + j = location[1] + + # Unrotated and untransposed case + if (pattern_data['rotations'][index] == 0 and + not pattern_data['transpositions'][index]): + self.universes[j][i] = new_universe + + else: + fill_universe = new_universe + rotation_direction = -1 + + # If pattern of neighbors has to be transposed + if pattern_data['transpositions'][index]: + rotation_direction = 1 + fill_universe = sym_universe + + # Create a container universe and cell for the rotation + holder_universe = openmc.Universe(name= + "LNS rotation universe") + holder_cell = openmc.Cell(name="LNS rotation cell") + holder_cell.fill = fill_universe + holder_cell.rotation = (0, 0, 90 * rotation_direction * + pattern_data['rotations'][index]) + holder_universe.add_cell(holder_cell) + self.universes[j][i] = holder_universe + def create_xml_subelement(self, xml_element): # Determine if XML element already contains subelement for this Lattice diff --git a/openmc/universe.py b/openmc/universe.py index ee038ebef4..fd411cc015 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -477,12 +477,15 @@ class Universe(IDManagerMixin): return universes - def clone(self, memo=None): + def clone(self, clone_materials=True, memo=None): """Create a copy of this universe with a new unique ID, and clones all cells within this universe. Parameters ---------- + clone_materials : boolean + Whether to create separates copies of the materials filling cells + under this universe in the CSG tree memo : dict or None A nested dictionary of previously cloned objects. This parameter is used internally and should not be specified by the user. @@ -505,7 +508,7 @@ class Universe(IDManagerMixin): # Clone all cells for the universe clone clone._cells = OrderedDict() for cell in self._cells.values(): - clone.add_cell(cell.clone(memo)) + clone.add_cell(cell.clone(clone_materials, memo)) # Memoize the clone memo[self] = clone From 9dd2c450bb2c5df1640157e8b062f02033d4eee4 Mon Sep 17 00:00:00 2001 From: Giud Date: Wed, 16 Oct 2019 11:27:13 -0400 Subject: [PATCH 02/14] Add option to avoid cloning cell regions --- openmc/cell.py | 17 +++++++++++++---- openmc/lattice.py | 41 +++++++++++++++++++++++++---------------- openmc/universe.py | 10 +++++++--- 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/openmc/cell.py b/openmc/cell.py index 4a9ff9574d..16919ce024 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -409,7 +409,7 @@ class Cell(IDManagerMixin): return universes - def clone(self, clone_materials=True, memo=None): + def clone(self, clone_materials=True, clone_regions=True, memo=None): """Create a copy of this cell with a new unique ID, and clones the cell's region and fill. @@ -417,7 +417,12 @@ class Cell(IDManagerMixin): ---------- clone_materials : boolean Whether to create separates copies of the materials filling cells - under this cell in the CSG tree, or the material filling this cell + contained in this cell, or the material filling this cell. + Default is True. + clone_regions : boolean + Whether to create separates copies of the regions bounding cells + contained in this cell, and the region bounding this cell. + Default is True. memo : dict or None A nested dictionary of previously cloned objects. This parameter is used internally and should not be specified by the user. @@ -446,7 +451,10 @@ class Cell(IDManagerMixin): self._paths = paths if self.region is not None: - clone.region = self.region.clone(memo) + if clone_regions: + clone.region = self.region.clone(memo) + else: + clone.region = self.region if self.fill is not None: if self.fill_type == 'distribmat': if not clone_materials: @@ -460,7 +468,8 @@ class Cell(IDManagerMixin): else: clone.fill = self.fill.clone(memo) else: - clone.fill = self.fill.clone(clone_materials, memo) + clone.fill = self.fill.clone(clone_materials, + clone_regions, memo) # Memoize the clone memo[self] = clone diff --git a/openmc/lattice.py b/openmc/lattice.py index a0f75c315c..204fe1704d 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -449,7 +449,7 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): return [] return [(self, idx)] + u.find(p) - def clone(self, clone_materials=True, memo=None): + def clone(self, clone_materials=True, clone_regions=True, memo=None): """Create a copy of this lattice with a new unique ID, and clones all universes within this lattice. @@ -457,7 +457,10 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): ---------- clone_materials : boolean Whether to create separates copies of the materials filling cells - under this lattice in the CSG tree + contained in this lattice and its outer universe. Default is True. + clone_regions : boolean + Whether to create separates copies of the regions bounding cells + contained in this lattice and its outer universe. Default is True. memo : dict or None A nested dictionary of previously cloned objects. This parameter is used internally and should not be specified by the user. @@ -478,22 +481,23 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): clone.id = None if self.outer is not None: - clone.outer = self.outer.clone(clone_materials, memo) + clone.outer = self.outer.clone(clone_materials, clone_regions, + memo) # Assign universe clones to the lattice clone for i in self.indices: if isinstance(self, RectLattice): clone.universes[i] = self.universes[i].clone( - clone_materials, memo) + clone_materials, clone_regions, memo) else: if self.ndim == 2: clone.universes[i[0]][i[1]] = \ self.universes[i[0]][i[1]].clone(clone_materials, - memo) + clone_regions, memo) else: clone.universes[i[0]][i[1]][i[2]] = \ self.universes[i[0]][i[1]][i[2]].clone( - clone_materials, memo) + clone_materials, clone_regions, memo) # Memoize the clone memo[self] = clone @@ -800,7 +804,8 @@ class RectLattice(Lattice): # Check routine inputs if self.ndim == 3: - raise NotImplementedError("LNS discretization is not implemented for 3D lattices") + raise NotImplementedError("LNS discretization is not implemented " + "for 3D lattices") cv.check_value('strategy', strategy, ('degenerate', 'lns')) cv.check_type('universes_to_ignore', universes_to_ignore, Iterable, @@ -853,7 +858,7 @@ class RectLattice(Lattice): pattern[1, 1] = getattr(self.universes[j][i], attribute) # Create a neighborhood pattern based on the universe's - # neighbors in the grid, and the lattice neighbors at the edges + # neighbors in the grid, and lattice's neighbors at the edges if i == 0: if len(lattice_neighbors) > 0: pattern[:, 0] = lattice_neighbors[3] @@ -954,7 +959,8 @@ class RectLattice(Lattice): # Look at transpose of pattern and its rotations pattern = np.transpose(pattern) for rot in range(4): - if not found and tuple(map(tuple, pattern)) == known_pattern: + if not found and \ + tuple(map(tuple, pattern)) == known_pattern: found = True # Save location of the pattern in the lattice @@ -990,7 +996,7 @@ class RectLattice(Lattice): # Create a clone of the universe, without cloning materials new_universe = self.universes[first_pos[1]][first_pos[0]].clone( - clone_materials=False) + clone_materials=False, clone_regions=False) # Call LNS on the immediate sub lattices of this universe for cell_id, sub_cell in new_universe.cells.items(): @@ -998,9 +1004,9 @@ class RectLattice(Lattice): sub_universe = sub_cell.fill try: - sub_neighbors = [pattern[0][0], pattern[0][1], pattern[0][2], - pattern[1][0], pattern[1][2], - pattern[2][0], pattern[2][1], pattern[2][2]] + sub_neighbors = [pattern[0][0], pattern[0][1], + pattern[0][2], pattern[1][0], pattern[1][2], + pattern[2][0], pattern[2][1], pattern[2][2]] sub_universe.discretize(strategy, universes_to_ignore, materials_to_clone, lattice_neighbors=sub_neighbors, @@ -1024,13 +1030,16 @@ class RectLattice(Lattice): # Build a symmetric universe for the transposed lattices if any(pattern_data['transpositions']): - sym_universe = new_universe.clone(clone_materials=False) + sym_universe = new_universe.clone(clone_materials=False, + clone_regions=False) for cell_id, sub_cell in sym_universe.cells.items(): sub_universe = sub_cell.fill - # NOTE: this will fail if there are two nested lattices - if "Lattice" in str(type(sub_universe)): + + try: sub_universe.universes = np.transpose( sub_universe.universes) + except: + continue # Rebuild lattice from pattern using rotation and symmetries for index, location in enumerate(pattern_data['locations']): diff --git a/openmc/universe.py b/openmc/universe.py index fd411cc015..d2cf699abc 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -477,7 +477,7 @@ class Universe(IDManagerMixin): return universes - def clone(self, clone_materials=True, memo=None): + def clone(self, clone_materials=True, clone_regions=True, memo=None): """Create a copy of this universe with a new unique ID, and clones all cells within this universe. @@ -485,7 +485,10 @@ class Universe(IDManagerMixin): ---------- clone_materials : boolean Whether to create separates copies of the materials filling cells - under this universe in the CSG tree + contained in this universe. Default is True. + clone_regions : boolean + Whether to create separates copies of the regions bounding cells + contained in this universe. Default is True. memo : dict or None A nested dictionary of previously cloned objects. This parameter is used internally and should not be specified by the user. @@ -508,7 +511,8 @@ class Universe(IDManagerMixin): # Clone all cells for the universe clone clone._cells = OrderedDict() for cell in self._cells.values(): - clone.add_cell(cell.clone(clone_materials, memo)) + clone.add_cell(cell.clone(clone_materials, clone_regions, + memo)) # Memoize the clone memo[self] = clone From 5bc10e89a6435b332f8f8d631ad4539944b797d1 Mon Sep 17 00:00:00 2001 From: Giud Date: Wed, 16 Oct 2019 11:44:49 -0400 Subject: [PATCH 03/14] Add unit test for restricting cloning on cell regions and materials --- tests/unit_tests/test_cell.py | 12 ++++++++++++ tests/unit_tests/test_lattice.py | 10 ++++++++++ tests/unit_tests/test_universe.py | 22 ++++++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/tests/unit_tests/test_cell.py b/tests/unit_tests/test_cell.py index d01d94a324..6ac4ae9c07 100644 --- a/tests/unit_tests/test_cell.py +++ b/tests/unit_tests/test_cell.py @@ -54,6 +54,18 @@ def test_clone(): assert c2.region != c.region assert c2.temperature == c.temperature + c3 = c.clone(clone_materials=False) + assert c3.id != c.id + assert c3.fill == c.fill + assert c3.region != c.region + assert c3.temperature == c.temperature + + c4 = c.clone(clone_regions=False) + assert c4.id != c.id + assert c4.fill != c.fill + assert c4.region == c.region + assert c4.temperature == c.temperature + def test_temperature(cell_with_lattice): # Make sure temperature propagates through universes diff --git a/tests/unit_tests/test_lattice.py b/tests/unit_tests/test_lattice.py index c4a3517769..31433af7dc 100644 --- a/tests/unit_tests/test_lattice.py +++ b/tests/unit_tests/test_lattice.py @@ -278,6 +278,16 @@ def test_clone(rlat2, hlat2, hlat3): assert hlat_clone.center == hlat3.center assert hlat_clone.pitch == hlat3.pitch + rlat_clone = rlat2.clone(clone_materials=False) + assert rlat_clone.get_all_materials() == rlat2.get_all_materials() + + rlat_clone = rlat2.clone(clone_materials=False, clone_regions=False) + for c1 in rlat_clone.cells: + for c2 in rlat2.cells: + if c1.fill == c2.fill: + print(c1.fill) + assert c1.region == c2.region + def test_repr(rlat2, rlat3, hlat2, hlat3): repr(rlat2) diff --git a/tests/unit_tests/test_universe.py b/tests/unit_tests/test_universe.py index 674fc5c185..ab3fd6e33a 100644 --- a/tests/unit_tests/test_universe.py +++ b/tests/unit_tests/test_universe.py @@ -99,6 +99,28 @@ def test_get_all_universes(): assert not (univs ^ {u1, u2}) +def test_clone(): + + c1 = openmc.Cell(cell_id=1) + c1.region = -openmc.ZCylinder(r=1.0) + c2 = openmc.Cell(cell_id=2) + c2.fill = openmc.Material() + c3 = openmc.Cell() + u1 = openmc.Universe(name='cool', cells=(c1, c2, c3)) + + u2 = u1.clone() + assert u2.name == u1.name + assert u2.cells != u1.cells + assert u2.get_all_materials() != u1.get_all_materials() + + u2 = u1.clone(clone_materials=False) + assert u2.get_all_materials() == u1.get_all_materials() + + u3 = u1.clone(clone_regions=False) + assert next(iter(u3.cells.values())).region ==\ + next(iter(u1.cells.values())).region + + def test_create_xml(cell_with_lattice): cells = [openmc.Cell() for i in range(5)] u = openmc.Universe(cells=cells) From 16ba46150d7557df1b8c04774b928145555610b5 Mon Sep 17 00:00:00 2001 From: Giud Date: Wed, 16 Oct 2019 11:45:18 -0400 Subject: [PATCH 04/14] Add unit test for lattice discretization, degenerate and LNS --- .../unit_tests/test_lattice_discretization.py | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 tests/unit_tests/test_lattice_discretization.py diff --git a/tests/unit_tests/test_lattice_discretization.py b/tests/unit_tests/test_lattice_discretization.py new file mode 100644 index 0000000000..bd78527cb5 --- /dev/null +++ b/tests/unit_tests/test_lattice_discretization.py @@ -0,0 +1,126 @@ +from math import sqrt +import xml.etree.ElementTree as ET + +import openmc +import pytest + +from test_lattice import zr, pincell1, pincell2, rlat2 + + +def test_discretization_degenerate(rlat2): + + rlat_clone = rlat2.clone() + rlat_clone.discretize() + + assert rlat_clone.get_universe((0, 0)).id !=\ + rlat_clone.get_universe((1, 0)).id + assert rlat_clone.get_universe((1, 0)).id !=\ + rlat_clone.get_universe((0, 1)).id + assert rlat_clone.get_universe((0, 1)).id !=\ + rlat_clone.get_universe((0, 0)).id + assert rlat_clone.get_universe((0, 2)).id !=\ + rlat_clone.get_universe((2, 0)).id + assert rlat_clone.get_universe((2, 1)).id !=\ + rlat_clone.get_universe((1, 2)).id + + +def test_discretization_skip_universe(rlat2): + + rlat_clone = rlat2.clone() + rlat_clone.discretize( + universes_to_ignore=[rlat_clone.get_universe((0, 0))]) + + assert rlat_clone.get_universe((0, 0)) == rlat_clone.get_universe((0, 1)) + assert rlat_clone.get_universe((0, 1)) == rlat_clone.get_universe((2, 1)) + assert rlat_clone.get_universe((0, 1)) == rlat_clone.get_universe((1, 2)) + + +def test_discretization_clone_only_some_materials(rlat2): + + rlat_clone = rlat2.clone() + fuel1 = next(iter(rlat_clone.get_universe((0, 0)).cells.values())).fill + rlat_clone.discretize(materials_to_clone=[fuel1]) + + assert next(reversed(rlat_clone.get_universe((0, 0)).cells.values())).fill\ + == next(reversed(rlat_clone.get_universe((1, 0)).cells.values())).fill + assert next(iter(rlat_clone.get_universe((0, 0)).cells.values())).fill\ + != next(iter(rlat_clone.get_universe((1, 0)).cells.values())).fill + + +def test_discretization_lns(rlat2): + + rlat_clone = rlat2.clone() + rlat_clone.discretize(strategy="lns") + + assert rlat_clone.get_universe((0, 2)) == \ + next(iter(rlat_clone.get_universe((2, 2)).cells.values())).fill + + +def test_discretization_lns_using_names(rlat2): + + rlat_clone = rlat2.clone() + rlat_clone.discretize(strategy="lns", attribute="name") + + assert rlat_clone.get_universe((0, 2)) == \ + rlat_clone.get_universe((2, 2)) + + rlat_clone = rlat2.clone() + rlat_clone.get_universe((1, 0)).name="u1" + rlat_clone.discretize(strategy="lns", attribute="name") + + assert rlat_clone.get_universe((0, 2)) == \ + next(iter(rlat_clone.get_universe((2, 2)).cells.values())).fill + + +def test_discretization_lns_with_neighbor_list(rlat2): + + rlat_clone = rlat2.clone() + u1 = rlat_clone.get_universe((1, 0)) + u2 = rlat_clone.get_universe((0, 0)) + rlat_clone.discretize(strategy="lns", + lattice_neighbors=[u1.id,u1.id,u2.id,u1.id,u2.id,u2.id,u1.id,u2.id]) + + assert rlat_clone.get_universe((0, 1)) == \ + next(iter(rlat_clone.get_universe((0, 0)).cells.values())).fill + assert rlat_clone.get_universe((1, 1)) != \ + next(iter(rlat_clone.get_universe((0, 0)).cells.values())).fill + + +def test_discretization_lns_nested_lattices(rlat2): + + # Name a universe in rlat2 to be able to compare + rlat2.get_universe((1, 2)).name = "universe" + + # Create a lattice of lattices + main_lattice = openmc.RectLattice(name="main lattice") + u1 = openmc.Universe() + cell = openmc.Cell() + cell.fill=rlat2 + u1.add_cell(cell) + u2 = u1.clone() + main_lattice.pitch = (20, 20) + main_lattice.universes = [[u1,u1,u1], [u2,u1,u2], [u1,u1,u2]] + + main_lattice.discretize(strategy="lns") + + # Both involved a transposition from (2,0) neighbor pattern + assert next(iter(main_lattice.get_universe((0, 0)).cells.values())).fill\ + == next(iter(main_lattice.get_universe((2, 2)).cells.values())).fill + + # Transposition creates a clone so the two universes cant match + assert main_lattice.get_universe((2, 0)) != \ + next(iter(main_lattice.get_universe((2, 2)).cells.values())).fill + + # Different neighbor pattern + assert main_lattice.get_universe((1, 1)) != \ + next(iter(main_lattice.get_universe((0, 0)).cells.values())).fill + + # Look inside inner lattices + for i in range(3): + for j in range(3): + assert next(iter(main_lattice.get_universe((0, 2)).cells.values())\ + ).fill.get_universe((i, j)).name == \ + next(iter(next(iter(main_lattice.get_universe((2, 2)).cells\ + .values())).fill.cells.values())).fill.get_universe((2-j, + 2-i)).name + From 59e97b261d8346712c7e84f44bac6e5c16640f2c Mon Sep 17 00:00:00 2001 From: Giud Date: Wed, 16 Oct 2019 13:16:52 -0400 Subject: [PATCH 05/14] Fix unit test import for lattice discretization test --- tests/unit_tests/test_lattice_discretization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit_tests/test_lattice_discretization.py b/tests/unit_tests/test_lattice_discretization.py index bd78527cb5..b4a89ea19b 100644 --- a/tests/unit_tests/test_lattice_discretization.py +++ b/tests/unit_tests/test_lattice_discretization.py @@ -4,7 +4,7 @@ import xml.etree.ElementTree as ET import openmc import pytest -from test_lattice import zr, pincell1, pincell2, rlat2 +from tests.unit_tests.test_lattice import zr, pincell1, pincell2, rlat2 def test_discretization_degenerate(rlat2): From 0bd520287d068474339aba40230f474af7cf032e Mon Sep 17 00:00:00 2001 From: guillaume Date: Wed, 30 Oct 2019 11:18:29 -0400 Subject: [PATCH 06/14] Address @smharper's review --- openmc/lattice.py | 53 +++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/openmc/lattice.py b/openmc/lattice.py index 204fe1704d..ee21363355 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -764,22 +764,24 @@ class RectLattice(Lattice): 0 <= idx[1] < self.shape[1] and 0 <= idx[2] < self.shape[2]) - def discretize(self, strategy="degenerate", universes_to_ignore=[], + def discretize(self, strategy="degenerate", + rotate_universe_with_neighbors=False, + universes_to_ignore=[], materials_to_clone=[], lattice_neighbors=[], attribute="id"): - """Discretize the lattice with either degenerate or a recursive local + """Discretize the lattice with either a degenerate or a recursive local neighbor symmetry strategy - Degenerate clones every universe in the lattice, thus making them all + 'Degenerate' clones every universe in the lattice, thus making them all uniquely defined. This is typically required if depletion or thermal - hydraulics will make every fuel pin environment unique. + hydraulics will make every universe's environment unique. - Local neighbor symmetry separates fuel pins with similar neighborhoods. + 'Local neighbor symmetry' groups universes with similar neighborhoods. These clusters of cells and materials provide increased convergence speed to multi-group cross sections tallies. The recursion allows the modelling of neighbor universes on multiple scales, for example - to discriminate between the baffle and the neighbor lattices for a - latttice in the outer part of a light water reactor. The recursion is + to discriminate between the baffle and the neighbor lattices of a + latttice in the outer ring of a light water reactor. The recursion is only implemented for lattices exactly right below the starting lattice (starting lattice contains universe contains cell contains the lattice) @@ -787,11 +789,15 @@ class RectLattice(Lattice): ---------- strategy : String Which strategy to adopt when discretizing the lattice + rotate_universe_with_neighbors : boolean + Whether lattice universes (especially sublattices) with rotated/ + symmetric neighbor patterns should also be rotated/symmetrized. + Default is False. universes_to_ignore : Iterable of Universe Lattice universes that need not be discretized materials_to_clone : Iterable of Material List of materials that should be cloned when discretizing - lattice_neighbors : Iterable of Int or String + lattice_neighbors : Iterable of Integral or String List of attributes of the lattice's neighbors. By default, if present, the lattice outer universe will be used. The neighbors are represented as follows [top left, top, top right, left, right, @@ -808,6 +814,8 @@ class RectLattice(Lattice): "for 3D lattices") cv.check_value('strategy', strategy, ('degenerate', 'lns')) + cv.check_type('rotate_universe_with_neighbors', + rotate_universe_with_neighbors, bool) cv.check_type('universes_to_ignore', universes_to_ignore, Iterable, openmc.Universe) cv.check_type('materials_to_clone', materials_to_clone, Iterable, @@ -818,7 +826,7 @@ class RectLattice(Lattice): if len(lattice_neighbors) > 0: if attribute == 'id': cv.check_type('lattice_neighbors', lattice_neighbors, - Iterable, int) + Iterable, Integral) elif attribute == 'name': cv.check_type('lattice_neighbors', lattice_neighbors, Iterable, str) @@ -1007,11 +1015,13 @@ class RectLattice(Lattice): sub_neighbors = [pattern[0][0], pattern[0][1], pattern[0][2], pattern[1][0], pattern[1][2], pattern[2][0], pattern[2][1], pattern[2][2]] - sub_universe.discretize(strategy, universes_to_ignore, + sub_universe.discretize(strategy, + rotate_universe_with_neighbors, + universes_to_ignore, materials_to_clone, lattice_neighbors=sub_neighbors, attribute=attribute) - except: + except AttributeError: continue # Replace only the materials in materials_to_clone @@ -1019,26 +1029,28 @@ class RectLattice(Lattice): material_cloned = False for cell in new_universe.get_all_cells().values(): - if cell.fill.id == material.id: - # Only a single clone of each material is necessary - if not material_cloned: - material_clone = material.clone() - material_cloned = True + if cell.fill is not None: + if cell.fill.id == material.id: - cell.fill = material_clone + # Only a single clone of each material is necessary + if not material_cloned: + material_clone = material.clone() + material_cloned = True + + cell.fill = material_clone # Build a symmetric universe for the transposed lattices if any(pattern_data['transpositions']): sym_universe = new_universe.clone(clone_materials=False, - clone_regions=False) + clone_regions=False) for cell_id, sub_cell in sym_universe.cells.items(): sub_universe = sub_cell.fill try: sub_universe.universes = np.transpose( sub_universe.universes) - except: + except AttributeError: continue # Rebuild lattice from pattern using rotation and symmetries @@ -1048,7 +1060,8 @@ class RectLattice(Lattice): j = location[1] # Unrotated and untransposed case - if (pattern_data['rotations'][index] == 0 and + if not rotate_universe_with_neighbors or\ + (pattern_data['rotations'][index] == 0 and not pattern_data['transpositions'][index]): self.universes[j][i] = new_universe From 4754d7fd403342614a8033137d552d79a11bfb85 Mon Sep 17 00:00:00 2001 From: guillaume Date: Thu, 31 Oct 2019 10:31:47 -0400 Subject: [PATCH 07/14] Update unit tests --- openmc/lattice.py | 8 ++- .../unit_tests/test_lattice_discretization.py | 51 +++++++++++++------ 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/openmc/lattice.py b/openmc/lattice.py index ee21363355..441c81b651 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -1012,9 +1012,12 @@ class RectLattice(Lattice): sub_universe = sub_cell.fill try: - sub_neighbors = [pattern[0][0], pattern[0][1], + if strategy == "lns": + sub_neighbors = [pattern[0][0], pattern[0][1], pattern[0][2], pattern[1][0], pattern[1][2], pattern[2][0], pattern[2][1], pattern[2][2]] + elif strategy == "degenerate": + sub_neighbors = [] sub_universe.discretize(strategy, rotate_universe_with_neighbors, universes_to_ignore, @@ -1041,7 +1044,8 @@ class RectLattice(Lattice): cell.fill = material_clone # Build a symmetric universe for the transposed lattices - if any(pattern_data['transpositions']): + if rotate_universe_with_neighbors and\ + any(pattern_data['transpositions']): sym_universe = new_universe.clone(clone_materials=False, clone_regions=False) for cell_id, sub_cell in sym_universe.cells.items(): diff --git a/tests/unit_tests/test_lattice_discretization.py b/tests/unit_tests/test_lattice_discretization.py index b4a89ea19b..cf4e101d64 100644 --- a/tests/unit_tests/test_lattice_discretization.py +++ b/tests/unit_tests/test_lattice_discretization.py @@ -52,6 +52,11 @@ def test_discretization_lns(rlat2): rlat_clone = rlat2.clone() rlat_clone.discretize(strategy="lns") + assert rlat_clone.get_universe((0, 2)) == rlat_clone.get_universe((2, 2)) + + rlat_clone = rlat2.clone() + rlat_clone.discretize(strategy="lns", rotate_universe_with_neighbors=True) + assert rlat_clone.get_universe((0, 2)) == \ next(iter(rlat_clone.get_universe((2, 2)).cells.values())).fill @@ -61,15 +66,20 @@ def test_discretization_lns_using_names(rlat2): rlat_clone = rlat2.clone() rlat_clone.discretize(strategy="lns", attribute="name") - assert rlat_clone.get_universe((0, 2)) == \ - rlat_clone.get_universe((2, 2)) + assert rlat_clone.get_universe((0, 2)) == rlat_clone.get_universe((2, 2)) rlat_clone = rlat2.clone() + rlat_clone.get_universe((0, 1)).name="u1" rlat_clone.get_universe((1, 0)).name="u1" rlat_clone.discretize(strategy="lns", attribute="name") - assert rlat_clone.get_universe((0, 2)) == \ - next(iter(rlat_clone.get_universe((2, 2)).cells.values())).fill + assert rlat_clone.get_universe((0, 2)) == rlat_clone.get_universe((2, 0)) + assert rlat_clone.get_universe((2, 2)) == rlat_clone.get_universe((0, 0)) + assert rlat_clone.get_universe((0, 2)) == rlat_clone.get_universe((2, 2)) + assert rlat_clone.get_universe((1, 2)) == rlat_clone.get_universe((1, 0)) + assert rlat_clone.get_universe((2, 1)) == rlat_clone.get_universe((0, 1)) + assert rlat_clone.get_universe((1, 2)) == rlat_clone.get_universe((0, 1)) + assert rlat_clone.get_universe((0, 0)) != rlat_clone.get_universe((0, 1)) def test_discretization_lns_with_neighbor_list(rlat2): @@ -80,10 +90,8 @@ def test_discretization_lns_with_neighbor_list(rlat2): rlat_clone.discretize(strategy="lns", lattice_neighbors=[u1.id,u1.id,u2.id,u1.id,u2.id,u2.id,u1.id,u2.id]) - assert rlat_clone.get_universe((0, 1)) == \ - next(iter(rlat_clone.get_universe((0, 0)).cells.values())).fill - assert rlat_clone.get_universe((1, 1)) != \ - next(iter(rlat_clone.get_universe((0, 0)).cells.values())).fill + assert rlat_clone.get_universe((0, 1)) == rlat_clone.get_universe((0, 0)) + assert rlat_clone.get_universe((1, 1)) != rlat_clone.get_universe((0, 0)) def test_discretization_lns_nested_lattices(rlat2): @@ -101,26 +109,39 @@ def test_discretization_lns_nested_lattices(rlat2): main_lattice.pitch = (20, 20) main_lattice.universes = [[u1,u1,u1], [u2,u1,u2], [u1,u1,u2]] + main_lattice_clone = main_lattice.clone() main_lattice.discretize(strategy="lns") # Both involved a transposition from (2,0) neighbor pattern - assert next(iter(main_lattice.get_universe((0, 0)).cells.values())).fill\ - == next(iter(main_lattice.get_universe((2, 2)).cells.values())).fill + assert main_lattice.get_universe((0, 0)) == \ + main_lattice.get_universe((2, 2)) # Transposition creates a clone so the two universes cant match assert main_lattice.get_universe((2, 0)) != \ - next(iter(main_lattice.get_universe((2, 2)).cells.values())).fill + main_lattice.get_universe((2, 2)) # Different neighbor pattern assert main_lattice.get_universe((1, 1)) != \ - next(iter(main_lattice.get_universe((0, 0)).cells.values())).fill + main_lattice.get_universe((0, 0)) # Look inside inner lattices for i in range(3): for j in range(3): assert next(iter(main_lattice.get_universe((0, 2)).cells.values())\ ).fill.get_universe((i, j)).name == \ - next(iter(next(iter(main_lattice.get_universe((2, 2)).cells\ - .values())).fill.cells.values())).fill.get_universe((2-j, - 2-i)).name + next(iter(main_lattice.get_universe((2, 2)).cells.values( + ))).fill.get_universe((i, j)).name + + # Test when rotating the sublattices with their neighbors + main_lattice_clone.discretize(strategy="lns", + rotate_universe_with_neighbors=True) + + # Look inside inner lattices + for i in range(3): + for j in range(3): + assert next(iter(main_lattice_clone.get_universe((0, 2) + ).cells.values())).fill.get_universe((i, j)).name == \ + next(iter(next(iter(main_lattice_clone.get_universe((2, 2) + ).cells.values())).fill.cells.values())).fill.get_universe( + (2-j, 2-i)).name From 6e4e69075bd1b74c975a46edc371de8ea836cfe0 Mon Sep 17 00:00:00 2001 From: guillaume Date: Thu, 31 Oct 2019 11:16:23 -0400 Subject: [PATCH 08/14] Revert to regular LNS for sublattices if neighbor rotation is not present in sub lattice --- openmc/lattice.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openmc/lattice.py b/openmc/lattice.py index 441c81b651..4175e29f31 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -1018,6 +1018,12 @@ class RectLattice(Lattice): pattern[2][0], pattern[2][1], pattern[2][2]] elif strategy == "degenerate": sub_neighbors = [] + + # Revert to regular LNS if neighbor rotations are not + # present in the sub_universe (sub-lattice) + if not rotate_universe_with_neighbors: + sub_neighbors = [] + sub_universe.discretize(strategy, rotate_universe_with_neighbors, universes_to_ignore, From b08933a653471f732a5c495c61a6d636c6e86fec Mon Sep 17 00:00:00 2001 From: Giud Date: Sun, 3 Nov 2019 14:57:44 -0500 Subject: [PATCH 09/14] Apply suggestions from code review Co-Authored-By: Sterling Harper --- openmc/cell.py | 8 ++++---- openmc/lattice.py | 18 +++++++++--------- openmc/universe.py | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/openmc/cell.py b/openmc/cell.py index 16919ce024..57d1fc209d 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -415,12 +415,12 @@ class Cell(IDManagerMixin): Parameters ---------- - clone_materials : boolean - Whether to create separates copies of the materials filling cells + clone_materials : bool + Whether to create separate copies of the materials filling cells contained in this cell, or the material filling this cell. Default is True. - clone_regions : boolean - Whether to create separates copies of the regions bounding cells + clone_regions : bool + Whether to create separate copies of the regions bounding cells contained in this cell, and the region bounding this cell. Default is True. memo : dict or None diff --git a/openmc/lattice.py b/openmc/lattice.py index 4175e29f31..a4fd403f61 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -455,11 +455,11 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): Parameters ---------- - clone_materials : boolean - Whether to create separates copies of the materials filling cells + clone_materials : bool + Whether to create separate copies of the materials filling cells contained in this lattice and its outer universe. Default is True. - clone_regions : boolean - Whether to create separates copies of the regions bounding cells + clone_regions : bool + Whether to create separate copies of the regions bounding cells contained in this lattice and its outer universe. Default is True. memo : dict or None A nested dictionary of previously cloned objects. This parameter @@ -787,9 +787,9 @@ class RectLattice(Lattice): Parameters ---------- - strategy : String + strategy : str Which strategy to adopt when discretizing the lattice - rotate_universe_with_neighbors : boolean + rotate_universe_with_neighbors : bool Whether lattice universes (especially sublattices) with rotated/ symmetric neighbor patterns should also be rotated/symmetrized. Default is False. @@ -797,12 +797,12 @@ class RectLattice(Lattice): Lattice universes that need not be discretized materials_to_clone : Iterable of Material List of materials that should be cloned when discretizing - lattice_neighbors : Iterable of Integral or String + lattice_neighbors : Iterable of Integral or str List of attributes of the lattice's neighbors. By default, if present, the lattice outer universe will be used. The neighbors are represented as follows [top left, top, top right, left, right, bottom left, bottom, bottom right] - attribute : String + attribute : str Which universe attribute to use when detecting neighbor symmetry patterns. Can be the 'id' or the 'name' of the lattice universes @@ -1007,7 +1007,7 @@ class RectLattice(Lattice): clone_materials=False, clone_regions=False) # Call LNS on the immediate sub lattices of this universe - for cell_id, sub_cell in new_universe.cells.items(): + for sub_cell in new_universe.cells.values(): sub_universe = sub_cell.fill diff --git a/openmc/universe.py b/openmc/universe.py index d2cf699abc..957f03f9e9 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -483,10 +483,10 @@ class Universe(IDManagerMixin): Parameters ---------- - clone_materials : boolean + clone_materials : bool Whether to create separates copies of the materials filling cells contained in this universe. Default is True. - clone_regions : boolean + clone_regions : bool Whether to create separates copies of the regions bounding cells contained in this universe. Default is True. memo : dict or None From 5fc1216708a362ed45a468d0ec161a618258397c Mon Sep 17 00:00:00 2001 From: guillaume Date: Tue, 5 Nov 2019 13:57:11 -0500 Subject: [PATCH 10/14] Address @smharper's review --- openmc/cell.py | 2 - openmc/lattice.py | 165 ++++++++++-------- openmc/universe.py | 4 +- .../unit_tests/test_lattice_discretization.py | 6 - 4 files changed, 93 insertions(+), 84 deletions(-) diff --git a/openmc/cell.py b/openmc/cell.py index 57d1fc209d..522e854260 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -418,11 +418,9 @@ class Cell(IDManagerMixin): clone_materials : bool Whether to create separate copies of the materials filling cells contained in this cell, or the material filling this cell. - Default is True. clone_regions : bool Whether to create separate copies of the regions bounding cells contained in this cell, and the region bounding this cell. - Default is True. memo : dict or None A nested dictionary of previously cloned objects. This parameter is used internally and should not be specified by the user. diff --git a/openmc/lattice.py b/openmc/lattice.py index a4fd403f61..a7c1b4b854 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -457,10 +457,10 @@ class Lattice(IDManagerMixin, metaclass=ABCMeta): ---------- clone_materials : bool Whether to create separate copies of the materials filling cells - contained in this lattice and its outer universe. Default is True. + contained in this lattice and its outer universe. clone_regions : bool Whether to create separate copies of the regions bounding cells - contained in this lattice and its outer universe. Default is True. + contained in this lattice and its outer universe. memo : dict or None A nested dictionary of previously cloned objects. This parameter is used internally and should not be specified by the user. @@ -792,7 +792,6 @@ class RectLattice(Lattice): rotate_universe_with_neighbors : bool Whether lattice universes (especially sublattices) with rotated/ symmetric neighbor patterns should also be rotated/symmetrized. - Default is False. universes_to_ignore : Iterable of Universe Lattice universes that need not be discretized materials_to_clone : Iterable of Material @@ -809,9 +808,9 @@ class RectLattice(Lattice): """ # Check routine inputs - if self.ndim == 3: + if self.ndim != 2: raise NotImplementedError("LNS discretization is not implemented " - "for 3D lattices") + "for 1D and 3D lattices") cv.check_value('strategy', strategy, ('degenerate', 'lns')) cv.check_type('rotate_universe_with_neighbors', @@ -846,6 +845,86 @@ class RectLattice(Lattice): # it was rotated and/or symmetrized patterns = {} + # Define an auxiliary function that returns a universe's neighbors + # that are outside the lattice + def find_edge_neighbors(i, j): + + # Left edge + if i == 0: + pattern[:, 0] = lattice_neighbors[3] + if j == 0: + pattern[0, 0] = lattice_neighbors[0] + elif j == self.shape[1] - 1: + pattern[2, 0] = lattice_neighbors[5] + + # Bottom edge + if j == 0: + pattern[0, 1] = lattice_neighbors[1] + if i != 0: + pattern[0, 0] = lattice_neighbors[1] + if i != self.shape[0] - 1: + pattern[0, 2] = lattice_neighbors[1] + + # Right edge + if i == self.shape[0] - 1: + pattern[:, 2] = lattice_neighbors[4] + if j == 0: + pattern[0, 2] = lattice_neighbors[2] + elif j == self.shape[1] - 1: + pattern[2, 2] = lattice_neighbors[7] + + # Top edge + if j == self.shape[1] - 1: + pattern[2, 1] = lattice_neighbors[6] + if i != 0: + pattern[2, 0] = lattice_neighbors[6] + if i != self.shape[0] - 1: + pattern[2, 2] = lattice_neighbors[6] + + # Define an auxiliary function that returns a universe's neighbors + # among the universes inside the lattice + def find_lattice_neighbors(i, j): + + # Away from left edge + if i != 0: + if j > 0: + pattern[0, 0] = getattr(self.universes[j-1][i-1], + attribute) + pattern[1, 0] = getattr(self.universes[j][i-1], attribute) + if j < self.shape[1] - 1: + pattern[2, 0] = getattr(self.universes[j+1][i-1], + attribute) + + # Away from bottom edge + if j != 0: + if i > 0: + pattern[0, 0] = getattr(self.universes[j-1][i-1], + attribute) + pattern[0, 1] = getattr(self.universes[j-1][i], attribute) + if i < self.shape[0] - 1: + pattern[0, 2] = getattr(self.universes[j-1][i+1], + attribute) + + # Away from right edge + if i != self.shape[0] - 1: + if j > 0: + pattern[0, 2] = getattr(self.universes[j-1][i+1], + attribute) + pattern[1, 2] = getattr(self.universes[j][i+1], attribute) + if j < self.shape[1] - 1: + pattern[2, 2] = getattr(self.universes[j+1][i+1], + attribute) + + # Away from top edge + if j != self.shape[1] - 1: + if i > 0: + pattern[2, 0] = getattr(self.universes[j+1][i-1], + attribute) + pattern[2, 1] = getattr(self.universes[j+1][i], attribute) + if i < self.shape[0] - 1: + pattern[2, 2] = getattr(self.universes[j+1][i+1], + attribute) + # Analyze lattice, find unique patterns, ignoring rotations and symmetries for j in range(self.shape[1]): for i in range(self.shape[0]): @@ -863,81 +942,19 @@ class RectLattice(Lattice): warning.warn("Universe name is larger than 100 " "characters, which may hinder neighbor search.") - pattern[1, 1] = getattr(self.universes[j][i], attribute) - # Create a neighborhood pattern based on the universe's # neighbors in the grid, and lattice's neighbors at the edges - if i == 0: - if len(lattice_neighbors) > 0: - pattern[:, 0] = lattice_neighbors[3] - if j == 0: - pattern[0, 0] = lattice_neighbors[0] - elif j == self.shape[1] - 1: - pattern[2, 0] = lattice_neighbors[5] - else: - pattern[:, 0] = dummy_neighbor - else: - if j > 0: - pattern[0, 0] = getattr(self.universes[j-1][i-1], - attribute) - pattern[1, 0] = getattr(self.universes[j][i-1], attribute) - if j < self.shape[1] - 1: - pattern[2, 0] = getattr(self.universes[j+1][i-1], - attribute) - if j == 0: - if len(lattice_neighbors) > 0: - pattern[0, 1] = lattice_neighbors[1] - if i != 0: - pattern[0, 0] = lattice_neighbors[1] - if i != self.shape[0] - 1: - pattern[0, 2] = lattice_neighbors[1] - else: - pattern[0, :] = dummy_neighbor + # Find neighbors among lattice's neighbors at the edges + if len(lattice_neighbors) > 0: + find_edge_neighbors(i, j) else: - if i > 0: - pattern[0, 0] = getattr(self.universes[j-1][i-1], - attribute) - pattern[0, 1] = getattr(self.universes[j-1][i], attribute) - if i < self.shape[0] - 1: - pattern[0, 2] = getattr(self.universes[j-1][i+1], - attribute) + pattern[:, :] = dummy_neighbor - if i == self.shape[0] - 1: - if len(lattice_neighbors) > 0: - pattern[:, 2] = lattice_neighbors[4] - if j == 0: - pattern[0, 2] = lattice_neighbors[2] - elif j == self.shape[1] - 1: - pattern[2, 2] = lattice_neighbors[7] - else: - pattern[:, 2] = dummy_neighbor - else: - if j > 0: - pattern[0, 2] = getattr(self.universes[j-1][i+1], - attribute) - pattern[1, 2] = getattr(self.universes[j][i+1], attribute) - if j < self.shape[1] - 1: - pattern[2, 2] = getattr(self.universes[j+1][i+1], - attribute) + # Find neighbors among the lattice's universes + find_lattice_neighbors(i, j) - if j == self.shape[1] - 1: - if len(lattice_neighbors) > 0: - pattern[2, 1] = lattice_neighbors[6] - if i != 0: - pattern[2, 0] = lattice_neighbors[6] - if i != self.shape[0] - 1: - pattern[2, 2] = lattice_neighbors[6] - else: - pattern[2, :] = dummy_neighbor - else: - if i > 0: - pattern[2, 0] = getattr(self.universes[j+1][i-1], - attribute) - pattern[2, 1] = getattr(self.universes[j+1][i], attribute) - if i < self.shape[0] - 1: - pattern[2, 2] = getattr(self.universes[j+1][i+1], - attribute) + pattern[1, 1] = getattr(self.universes[j][i], attribute) # Look for pattern in dictionary of patterns found found = False diff --git a/openmc/universe.py b/openmc/universe.py index 957f03f9e9..4a41939bcf 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -485,10 +485,10 @@ class Universe(IDManagerMixin): ---------- clone_materials : bool Whether to create separates copies of the materials filling cells - contained in this universe. Default is True. + contained in this universe. clone_regions : bool Whether to create separates copies of the regions bounding cells - contained in this universe. Default is True. + contained in this universe. memo : dict or None A nested dictionary of previously cloned objects. This parameter is used internally and should not be specified by the user. diff --git a/tests/unit_tests/test_lattice_discretization.py b/tests/unit_tests/test_lattice_discretization.py index cf4e101d64..28fd91f170 100644 --- a/tests/unit_tests/test_lattice_discretization.py +++ b/tests/unit_tests/test_lattice_discretization.py @@ -23,7 +23,6 @@ def test_discretization_degenerate(rlat2): assert rlat_clone.get_universe((2, 1)).id !=\ rlat_clone.get_universe((1, 2)).id - def test_discretization_skip_universe(rlat2): rlat_clone = rlat2.clone() @@ -34,7 +33,6 @@ def test_discretization_skip_universe(rlat2): assert rlat_clone.get_universe((0, 1)) == rlat_clone.get_universe((2, 1)) assert rlat_clone.get_universe((0, 1)) == rlat_clone.get_universe((1, 2)) - def test_discretization_clone_only_some_materials(rlat2): rlat_clone = rlat2.clone() @@ -46,7 +44,6 @@ def test_discretization_clone_only_some_materials(rlat2): assert next(iter(rlat_clone.get_universe((0, 0)).cells.values())).fill\ != next(iter(rlat_clone.get_universe((1, 0)).cells.values())).fill - def test_discretization_lns(rlat2): rlat_clone = rlat2.clone() @@ -60,7 +57,6 @@ def test_discretization_lns(rlat2): assert rlat_clone.get_universe((0, 2)) == \ next(iter(rlat_clone.get_universe((2, 2)).cells.values())).fill - def test_discretization_lns_using_names(rlat2): rlat_clone = rlat2.clone() @@ -81,7 +77,6 @@ def test_discretization_lns_using_names(rlat2): assert rlat_clone.get_universe((1, 2)) == rlat_clone.get_universe((0, 1)) assert rlat_clone.get_universe((0, 0)) != rlat_clone.get_universe((0, 1)) - def test_discretization_lns_with_neighbor_list(rlat2): rlat_clone = rlat2.clone() @@ -93,7 +88,6 @@ def test_discretization_lns_with_neighbor_list(rlat2): assert rlat_clone.get_universe((0, 1)) == rlat_clone.get_universe((0, 0)) assert rlat_clone.get_universe((1, 1)) != rlat_clone.get_universe((0, 0)) - def test_discretization_lns_nested_lattices(rlat2): # Name a universe in rlat2 to be able to compare From 35f599564e1aa65e9aa3c413a9b19ff168134ef9 Mon Sep 17 00:00:00 2001 From: guillaume Date: Fri, 6 Dec 2019 01:22:21 -0500 Subject: [PATCH 11/14] Remove automatic handling of subneighbors in recursive LNS --- openmc/lattice.py | 92 ++----------------- .../unit_tests/test_lattice_discretization.py | 26 ++---- 2 files changed, 15 insertions(+), 103 deletions(-) diff --git a/openmc/lattice.py b/openmc/lattice.py index a7c1b4b854..77f5e36a3a 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -765,12 +765,11 @@ class RectLattice(Lattice): 0 <= idx[2] < self.shape[2]) def discretize(self, strategy="degenerate", - rotate_universe_with_neighbors=False, universes_to_ignore=[], materials_to_clone=[], lattice_neighbors=[], attribute="id"): - """Discretize the lattice with either a degenerate or a recursive local - neighbor symmetry strategy + """Discretize the lattice and its immediate sublattices with either a + degenerate or a local neighbor symmetry strategy 'Degenerate' clones every universe in the lattice, thus making them all uniquely defined. This is typically required if depletion or thermal @@ -778,20 +777,14 @@ class RectLattice(Lattice): 'Local neighbor symmetry' groups universes with similar neighborhoods. These clusters of cells and materials provide increased convergence - speed to multi-group cross sections tallies. The recursion allows - the modelling of neighbor universes on multiple scales, for example - to discriminate between the baffle and the neighbor lattices of a - latttice in the outer ring of a light water reactor. The recursion is - only implemented for lattices exactly right below the starting lattice - (starting lattice contains universe contains cell contains the lattice) + speed to multi-group cross sections tallies. The user can specify + the main lattice's neighbors to discriminate between two sides of an + assembly. Note that each sublattice's neighbors are not considered. Parameters ---------- strategy : str Which strategy to adopt when discretizing the lattice - rotate_universe_with_neighbors : bool - Whether lattice universes (especially sublattices) with rotated/ - symmetric neighbor patterns should also be rotated/symmetrized. universes_to_ignore : Iterable of Universe Lattice universes that need not be discretized materials_to_clone : Iterable of Material @@ -813,8 +806,6 @@ class RectLattice(Lattice): "for 1D and 3D lattices") cv.check_value('strategy', strategy, ('degenerate', 'lns')) - cv.check_type('rotate_universe_with_neighbors', - rotate_universe_with_neighbors, bool) cv.check_type('universes_to_ignore', universes_to_ignore, Iterable, openmc.Universe) cv.check_type('materials_to_clone', materials_to_clone, Iterable, @@ -972,12 +963,6 @@ class RectLattice(Lattice): # Save location of the pattern in the lattice pattern_data['locations'].append((i, j)) - # Save the pattern rotation at this location - pattern_data['rotations'].append(rot) - - # Save that the pattern was not transposed - pattern_data['transpositions'].append(False) - # Rotate pattern pattern = np.rot90(pattern) @@ -991,12 +976,6 @@ class RectLattice(Lattice): # Save location of the pattern in the lattice pattern_data['locations'].append((i, j)) - # Save the pattern rotation at this location - pattern_data['rotations'].append(rot) - - # Save that the pattern was transposed - pattern_data['transpositions'].append(True) - # Rotate pattern pattern = np.rot90(pattern) @@ -1007,8 +986,6 @@ class RectLattice(Lattice): if not found: pattern_data = {} pattern_data['locations'] = [(i, j)] - pattern_data['rotations'] = [0] - pattern_data['transpositions'] = [False] if strategy == "lns": patterns[tuple(map(tuple, pattern))] = pattern_data elif strategy == "degenerate": @@ -1029,23 +1006,11 @@ class RectLattice(Lattice): sub_universe = sub_cell.fill try: - if strategy == "lns": - sub_neighbors = [pattern[0][0], pattern[0][1], - pattern[0][2], pattern[1][0], pattern[1][2], - pattern[2][0], pattern[2][1], pattern[2][2]] - elif strategy == "degenerate": - sub_neighbors = [] - - # Revert to regular LNS if neighbor rotations are not - # present in the sub_universe (sub-lattice) - if not rotate_universe_with_neighbors: - sub_neighbors = [] sub_universe.discretize(strategy, - rotate_universe_with_neighbors, universes_to_ignore, materials_to_clone, - lattice_neighbors=sub_neighbors, + lattice_neighbors=[], attribute=attribute) except AttributeError: continue @@ -1066,50 +1031,9 @@ class RectLattice(Lattice): cell.fill = material_clone - # Build a symmetric universe for the transposed lattices - if rotate_universe_with_neighbors and\ - any(pattern_data['transpositions']): - sym_universe = new_universe.clone(clone_materials=False, - clone_regions=False) - for cell_id, sub_cell in sym_universe.cells.items(): - sub_universe = sub_cell.fill - - try: - sub_universe.universes = np.transpose( - sub_universe.universes) - except AttributeError: - continue - - # Rebuild lattice from pattern using rotation and symmetries + # Rebuild lattice from list of locations with this pattern for index, location in enumerate(pattern_data['locations']): - - i = location[0] - j = location[1] - - # Unrotated and untransposed case - if not rotate_universe_with_neighbors or\ - (pattern_data['rotations'][index] == 0 and - not pattern_data['transpositions'][index]): - self.universes[j][i] = new_universe - - else: - fill_universe = new_universe - rotation_direction = -1 - - # If pattern of neighbors has to be transposed - if pattern_data['transpositions'][index]: - rotation_direction = 1 - fill_universe = sym_universe - - # Create a container universe and cell for the rotation - holder_universe = openmc.Universe(name= - "LNS rotation universe") - holder_cell = openmc.Cell(name="LNS rotation cell") - holder_cell.fill = fill_universe - holder_cell.rotation = (0, 0, 90 * rotation_direction * - pattern_data['rotations'][index]) - holder_universe.add_cell(holder_cell) - self.universes[j][i] = holder_universe + self.universes[location[1]][location[0]] = new_universe def create_xml_subelement(self, xml_element): diff --git a/tests/unit_tests/test_lattice_discretization.py b/tests/unit_tests/test_lattice_discretization.py index 28fd91f170..035f9fc569 100644 --- a/tests/unit_tests/test_lattice_discretization.py +++ b/tests/unit_tests/test_lattice_discretization.py @@ -50,12 +50,13 @@ def test_discretization_lns(rlat2): rlat_clone.discretize(strategy="lns") assert rlat_clone.get_universe((0, 2)) == rlat_clone.get_universe((2, 2)) - - rlat_clone = rlat2.clone() - rlat_clone.discretize(strategy="lns", rotate_universe_with_neighbors=True) - - assert rlat_clone.get_universe((0, 2)) == \ - next(iter(rlat_clone.get_universe((2, 2)).cells.values())).fill + assert rlat_clone.get_universe((0, 2)) != rlat_clone.get_universe((0, 0)) + assert rlat_clone.get_universe((0, 2)) != rlat_clone.get_universe((0, 1)) + assert rlat_clone.get_universe((0, 2)) != rlat_clone.get_universe((1, 0)) + assert rlat_clone.get_universe((0, 2)) != rlat_clone.get_universe((1, 1)) + assert rlat_clone.get_universe((0, 2)) != rlat_clone.get_universe((1, 2)) + assert rlat_clone.get_universe((0, 2)) != rlat_clone.get_universe((2, 0)) + assert rlat_clone.get_universe((0, 2)) != rlat_clone.get_universe((2, 1)) def test_discretization_lns_using_names(rlat2): @@ -126,16 +127,3 @@ def test_discretization_lns_nested_lattices(rlat2): next(iter(main_lattice.get_universe((2, 2)).cells.values( ))).fill.get_universe((i, j)).name - # Test when rotating the sublattices with their neighbors - main_lattice_clone.discretize(strategy="lns", - rotate_universe_with_neighbors=True) - - # Look inside inner lattices - for i in range(3): - for j in range(3): - assert next(iter(main_lattice_clone.get_universe((0, 2) - ).cells.values())).fill.get_universe((i, j)).name == \ - next(iter(next(iter(main_lattice_clone.get_universe((2, 2) - ).cells.values())).fill.cells.values())).fill.get_universe( - (2-j, 2-i)).name - From bb406d7842244bb10b97c00179d1bff7127e8065 Mon Sep 17 00:00:00 2001 From: Giud Date: Fri, 6 Dec 2019 01:32:54 -0500 Subject: [PATCH 12/14] Improve comments --- openmc/lattice.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openmc/lattice.py b/openmc/lattice.py index 73f6addb9e..6f845ab16a 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -784,8 +784,9 @@ class RectLattice(Lattice): 'Local neighbor symmetry' groups universes with similar neighborhoods. These clusters of cells and materials provide increased convergence speed to multi-group cross sections tallies. The user can specify - the main lattice's neighbors to discriminate between two sides of an - assembly. Note that each sublattice's neighbors are not considered. + the main lattice's neighbors to discriminate between two sides of a + lattice for example. Note that each sublattice's neighbors are not + considered, only their outer universe. Parameters ---------- @@ -1006,13 +1007,12 @@ class RectLattice(Lattice): new_universe = self.universes[first_pos[1]][first_pos[0]].clone( clone_materials=False, clone_regions=False) - # Call LNS on the immediate sub lattices of this universe + # Discretize the immediate sub-lattices of this universe for sub_cell in new_universe.cells.values(): sub_universe = sub_cell.fill try: - sub_universe.discretize(strategy, universes_to_ignore, materials_to_clone, From 2573c5deeaa433d4ad875f991acf29cfefbd735e Mon Sep 17 00:00:00 2001 From: Giud Date: Tue, 21 Jan 2020 15:56:50 -0500 Subject: [PATCH 13/14] Update openmc/lattice.py Co-Authored-By: Sterling Harper --- openmc/lattice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/lattice.py b/openmc/lattice.py index 6f845ab16a..7eae8ff5a2 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -790,7 +790,7 @@ class RectLattice(Lattice): Parameters ---------- - strategy : str + strategy : {'degenerate', 'lns'} Which strategy to adopt when discretizing the lattice universes_to_ignore : Iterable of Universe Lattice universes that need not be discretized From 1565483a0ffc605873a9bade82eff68312807c03 Mon Sep 17 00:00:00 2001 From: guillaume Date: Tue, 21 Jan 2020 17:48:31 -0500 Subject: [PATCH 14/14] Address @smharper 's review --- openmc/lattice.py | 165 +++++++----------- .../unit_tests/test_lattice_discretization.py | 44 +---- 2 files changed, 70 insertions(+), 139 deletions(-) diff --git a/openmc/lattice.py b/openmc/lattice.py index 7eae8ff5a2..2b2cd567a5 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -8,6 +8,7 @@ from xml.etree import ElementTree as ET import numpy as np import warnings +import types import openmc.checkvalue as cv import openmc @@ -773,9 +774,9 @@ class RectLattice(Lattice): def discretize(self, strategy="degenerate", universes_to_ignore=[], materials_to_clone=[], - lattice_neighbors=[], attribute="id"): - """Discretize the lattice and its immediate sublattices with either a - degenerate or a local neighbor symmetry strategy + lattice_neighbors=[], key=lambda univ: univ.id): + """Discretize the lattice with either a degenerate or a local neighbor + symmetry strategy 'Degenerate' clones every universe in the lattice, thus making them all uniquely defined. This is typically required if depletion or thermal @@ -784,9 +785,8 @@ class RectLattice(Lattice): 'Local neighbor symmetry' groups universes with similar neighborhoods. These clusters of cells and materials provide increased convergence speed to multi-group cross sections tallies. The user can specify - the main lattice's neighbors to discriminate between two sides of a - lattice for example. Note that each sublattice's neighbors are not - considered, only their outer universe. + the lattice's neighbors to discriminate between two sides of a + lattice for example. Parameters ---------- @@ -796,15 +796,16 @@ class RectLattice(Lattice): Lattice universes that need not be discretized materials_to_clone : Iterable of Material List of materials that should be cloned when discretizing - lattice_neighbors : Iterable of Integral or str - List of attributes of the lattice's neighbors. By default, if - present, the lattice outer universe will be used. The neighbors - are represented as follows [top left, top, top right, left, right, + lattice_neighbors : Iterable of Universe + List of the lattice's neighbors. By default, if present, the + lattice outer universe will be used. The neighbors should be + ordered as follows [top left, top, top right, left, right, bottom left, bottom, bottom right] - attribute : str - Which universe attribute to use when detecting neighbor symmetry - patterns. Can be the 'id' or the 'name' of the lattice universes - + key : function + Function of argument a universe that is used to extract a + comparison key. This function will be called on each universe's + neighbors in the lattice to form a neighbor pattern. This pattern + is then used to identify unique neighbor symmetries. """ # Check routine inputs @@ -817,35 +818,33 @@ class RectLattice(Lattice): openmc.Universe) cv.check_type('materials_to_clone', materials_to_clone, Iterable, openmc.Material) - cv.check_value('number of lattice_neighbors', len(lattice_neighbors), (0, 8)) - cv.check_value('attribute', attribute, ('id', 'name')) - - if len(lattice_neighbors) > 0: - if attribute == 'id': - cv.check_type('lattice_neighbors', lattice_neighbors, - Iterable, Integral) - elif attribute == 'name': - cv.check_type('lattice_neighbors', lattice_neighbors, - Iterable, str) + cv.check_type('lattice_neighbors', lattice_neighbors, Iterable, + openmc.Universe) + cv.check_value('number of lattice_neighbors', len(lattice_neighbors), + (0, 8)) + cv.check_type('key', key, types.FunctionType) # Use outer universe if neighbors are missing and outer is defined if self.outer is not None and len(lattice_neighbors) == 0: - lattice_neighbors = [getattr(self.outer, attribute) for i in range(8)] - - # Create dummy neighbor for when the neighbor is not known at the edge - if len(lattice_neighbors) == 0: - if attribute == 'id': - dummy_neighbor = -1 - elif attribute == 'name': - dummy_neighbor = "dummy" + lattice_neighbors = [key(self.outer) for i in range(8)] + elif len(lattice_neighbors) == 8: + lattice_neighbors = [key(universe) for universe in + lattice_neighbors] # Dictionary that will keep track of where each pattern appears, how # it was rotated and/or symmetrized patterns = {} + # Initialize pattern array + pattern = np.empty(shape=(3, 3), dtype=type(key(self.universes[0][0]))) + # Define an auxiliary function that returns a universe's neighbors # that are outside the lattice - def find_edge_neighbors(i, j): + def find_edge_neighbors(pattern, i, j): + + # If no neighbors have been specified, start with an empty array + if len(lattice_neighbors) == 0: + return # Left edge if i == 0: @@ -881,49 +880,41 @@ class RectLattice(Lattice): # Define an auxiliary function that returns a universe's neighbors # among the universes inside the lattice - def find_lattice_neighbors(i, j): + def find_lattice_neighbors(pattern, i, j): # Away from left edge if i != 0: if j > 0: - pattern[0, 0] = getattr(self.universes[j-1][i-1], - attribute) - pattern[1, 0] = getattr(self.universes[j][i-1], attribute) + pattern[0, 0] = key(self.universes[j-1][i-1]) + pattern[1, 0] = key(self.universes[j][i-1]) if j < self.shape[1] - 1: - pattern[2, 0] = getattr(self.universes[j+1][i-1], - attribute) + pattern[2, 0] = key(self.universes[j+1][i-1]) # Away from bottom edge if j != 0: if i > 0: - pattern[0, 0] = getattr(self.universes[j-1][i-1], - attribute) - pattern[0, 1] = getattr(self.universes[j-1][i], attribute) + pattern[0, 0] = key(self.universes[j-1][i-1]) + pattern[0, 1] = key(self.universes[j-1][i]) if i < self.shape[0] - 1: - pattern[0, 2] = getattr(self.universes[j-1][i+1], - attribute) + pattern[0, 2] = key(self.universes[j-1][i+1]) # Away from right edge if i != self.shape[0] - 1: if j > 0: - pattern[0, 2] = getattr(self.universes[j-1][i+1], - attribute) - pattern[1, 2] = getattr(self.universes[j][i+1], attribute) + pattern[0, 2] = key(self.universes[j-1][i+1]) + pattern[1, 2] = key(self.universes[j][i+1]) if j < self.shape[1] - 1: - pattern[2, 2] = getattr(self.universes[j+1][i+1], - attribute) + pattern[2, 2] = key(self.universes[j+1][i+1]) # Away from top edge if j != self.shape[1] - 1: if i > 0: - pattern[2, 0] = getattr(self.universes[j+1][i-1], - attribute) - pattern[2, 1] = getattr(self.universes[j+1][i], attribute) + pattern[2, 0] = key(self.universes[j+1][i-1]) + pattern[2, 1] = key(self.universes[j+1][i]) if i < self.shape[0] - 1: - pattern[2, 2] = getattr(self.universes[j+1][i+1], - attribute) + pattern[2, 2] = key(self.universes[j+1][i+1]) - # Analyze lattice, find unique patterns, ignoring rotations and symmetries + # Analyze lattice, find unique patterns in groups of universes for j in range(self.shape[1]): for i in range(self.shape[0]): @@ -931,40 +922,30 @@ class RectLattice(Lattice): if self.universes[j][i] in universes_to_ignore: continue - # Form the pattern at and around each universe - if attribute == "id": - pattern = np.empty(shape=(3, 3), dtype=np.int) - if attribute == "name": - pattern = np.empty(shape=(3, 3), dtype=np.dtype('U100')) - if len(self.universes[j][i].name) > 100: - warning.warn("Universe name is larger than 100 " - "characters, which may hinder neighbor search.") - # Create a neighborhood pattern based on the universe's # neighbors in the grid, and lattice's neighbors at the edges + # Degenerate discretization has all universes be different + if strategy == "degenerate": + patterns[(i, j)] = {'locations': [(i, j)]} + continue + # Find neighbors among lattice's neighbors at the edges - if len(lattice_neighbors) > 0: - find_edge_neighbors(i, j) - else: - pattern[:, :] = dummy_neighbor + find_edge_neighbors(pattern, i, j) # Find neighbors among the lattice's universes - find_lattice_neighbors(i, j) + find_lattice_neighbors(pattern, i, j) - pattern[1, 1] = getattr(self.universes[j][i], attribute) + pattern[1, 1] = key(self.universes[j][i]) # Look for pattern in dictionary of patterns found found = False for known_pattern, pattern_data in patterns.items(): - # Degenerate discretizations use unique patterns - if strategy == "degenerate": - break - # Look at all rotations of pattern for rot in range(4): - if not found and tuple(map(tuple, pattern)) == known_pattern: + if not found and tuple(map(tuple, pattern)) ==\ + known_pattern: found = True # Save location of the pattern in the lattice @@ -976,8 +957,8 @@ class RectLattice(Lattice): # Look at transpose of pattern and its rotations pattern = np.transpose(pattern) for rot in range(4): - if not found and \ - tuple(map(tuple, pattern)) == known_pattern: + if not found and tuple(map(tuple, pattern)) ==\ + known_pattern: found = True # Save location of the pattern in the lattice @@ -991,12 +972,8 @@ class RectLattice(Lattice): # Create new pattern and add to the patterns dictionary if not found: - pattern_data = {} - pattern_data['locations'] = [(i, j)] - if strategy == "lns": - patterns[tuple(map(tuple, pattern))] = pattern_data - elif strategy == "degenerate": - patterns[(i,j)] = pattern_data + patterns[tuple(map(tuple, pattern))] =\ + {'locations': [(i, j)]} # Discretize lattice for pattern, pattern_data in patterns.items(): @@ -1007,27 +984,13 @@ class RectLattice(Lattice): new_universe = self.universes[first_pos[1]][first_pos[0]].clone( clone_materials=False, clone_regions=False) - # Discretize the immediate sub-lattices of this universe - for sub_cell in new_universe.cells.values(): - - sub_universe = sub_cell.fill - - try: - sub_universe.discretize(strategy, - universes_to_ignore, - materials_to_clone, - lattice_neighbors=[], - attribute=attribute) - except AttributeError: - continue - # Replace only the materials in materials_to_clone for material in materials_to_clone: material_cloned = False for cell in new_universe.get_all_cells().values(): - if cell.fill is not None: + if cell.fill_type == 'material': if cell.fill.id == material.id: # Only a single clone of each material is necessary @@ -1036,6 +999,12 @@ class RectLattice(Lattice): material_cloned = True cell.fill = material_clone + elif cell.fill_type == 'distribmat': + raise(ValueError, "Lattice discretization should not " + "be used with distributed materials") + elif len(cell.temperature) > 1 or len(cell.fill) > 1: + raise(ValueError, "Lattice discretization should not " + "be used with distributed cells") # Rebuild lattice from list of locations with this pattern for index, location in enumerate(pattern_data['locations']): diff --git a/tests/unit_tests/test_lattice_discretization.py b/tests/unit_tests/test_lattice_discretization.py index 035f9fc569..b2766572d7 100644 --- a/tests/unit_tests/test_lattice_discretization.py +++ b/tests/unit_tests/test_lattice_discretization.py @@ -61,14 +61,14 @@ def test_discretization_lns(rlat2): def test_discretization_lns_using_names(rlat2): rlat_clone = rlat2.clone() - rlat_clone.discretize(strategy="lns", attribute="name") + rlat_clone.discretize(strategy="lns", key=lambda univ: univ.name) assert rlat_clone.get_universe((0, 2)) == rlat_clone.get_universe((2, 2)) rlat_clone = rlat2.clone() rlat_clone.get_universe((0, 1)).name="u1" rlat_clone.get_universe((1, 0)).name="u1" - rlat_clone.discretize(strategy="lns", attribute="name") + rlat_clone.discretize(strategy="lns", key=lambda univ: univ.name) assert rlat_clone.get_universe((0, 2)) == rlat_clone.get_universe((2, 0)) assert rlat_clone.get_universe((2, 2)) == rlat_clone.get_universe((0, 0)) @@ -84,46 +84,8 @@ def test_discretization_lns_with_neighbor_list(rlat2): u1 = rlat_clone.get_universe((1, 0)) u2 = rlat_clone.get_universe((0, 0)) rlat_clone.discretize(strategy="lns", - lattice_neighbors=[u1.id,u1.id,u2.id,u1.id,u2.id,u2.id,u1.id,u2.id]) + lattice_neighbors=[u1,u1,u2,u1,u2,u2,u1,u2]) assert rlat_clone.get_universe((0, 1)) == rlat_clone.get_universe((0, 0)) assert rlat_clone.get_universe((1, 1)) != rlat_clone.get_universe((0, 0)) -def test_discretization_lns_nested_lattices(rlat2): - - # Name a universe in rlat2 to be able to compare - rlat2.get_universe((1, 2)).name = "universe" - - # Create a lattice of lattices - main_lattice = openmc.RectLattice(name="main lattice") - u1 = openmc.Universe() - cell = openmc.Cell() - cell.fill=rlat2 - u1.add_cell(cell) - u2 = u1.clone() - main_lattice.pitch = (20, 20) - main_lattice.universes = [[u1,u1,u1], [u2,u1,u2], [u1,u1,u2]] - - main_lattice_clone = main_lattice.clone() - main_lattice.discretize(strategy="lns") - - # Both involved a transposition from (2,0) neighbor pattern - assert main_lattice.get_universe((0, 0)) == \ - main_lattice.get_universe((2, 2)) - - # Transposition creates a clone so the two universes cant match - assert main_lattice.get_universe((2, 0)) != \ - main_lattice.get_universe((2, 2)) - - # Different neighbor pattern - assert main_lattice.get_universe((1, 1)) != \ - main_lattice.get_universe((0, 0)) - - # Look inside inner lattices - for i in range(3): - for j in range(3): - assert next(iter(main_lattice.get_universe((0, 2)).cells.values())\ - ).fill.get_universe((i, j)).name == \ - next(iter(main_lattice.get_universe((2, 2)).cells.values( - ))).fill.get_universe((i, j)).name -