Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix centering of full-symmetry cartesian latticeMaps #346

Merged
merged 5 commits into from
Jul 28, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 35 additions & 12 deletions armi/reactor/blueprints/gridBlueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
"""
import copy
import itertools
from typing import Sequence, Optional
from typing import Sequence, Optional, Tuple

import numpy
import yamlize
Expand Down Expand Up @@ -293,16 +293,7 @@ def _constructSpatialGrid(self):
# Note that the "through center" symmetry check cannot be performed when
# the grid contents has not been provided (i.e., None or empty).
if self.gridContents:
nx = (
max(key[0] for key in self.gridContents)
- min(key[0] for key in self.gridContents)
+ 1
)
ny = (
max(key[1] for key in self.gridContents)
- min(key[1] for key in self.gridContents)
+ 1
)
nx, ny = _getGridSize(self.gridContents.keys())
if nx == ny and nx % 2 == 1:
symmetry.isThroughCenterAssembly = True

Expand Down Expand Up @@ -393,16 +384,33 @@ def _readGridContentsLattice(self):
This update the gridContents attribute, which is a dict mapping grid i,j,k
indices to textual specifiers (e.g. ``IC``))
"""
symmetry = geometry.SymmetryType.fromStr(self.symmetry)
geom = geometry.GeomType.fromStr(self.geom)
latticeCls = asciimaps.asciiMapFromGeomAndSym(self.geom, self.symmetry)
asciimap = latticeCls()
asciimap.readAscii(self.latticeMap)
self.gridContents = dict()

iOffset = 0
jOffset = 0
if (
geom == geometry.GeomType.CARTESIAN
and symmetry.domain == geometry.DomainType.FULL_CORE
):
# asciimaps is not smart about where the center should be, so we need to
# offset appropriately to get (0,0) in the middle
nx, ny = _getGridSize(asciimap.keys())

# turns out this works great for even and odd cases. love it when integer
# math works in your favor
iOffset = int(-nx / 2)
jOffset = int(-ny / 2)

for (i, j), spec in asciimap.items():
if spec == "-":
# skip placeholders
continue
self.gridContents[i, j] = spec
self.gridContents[i + iOffset, j + jOffset] = spec

def getLocators(self, spatialGrid: grids.Grid, latticeIDs: list):
"""
Expand Down Expand Up @@ -454,3 +462,18 @@ def _isMonotonicUnique(l: Sequence[float]) -> bool:
return False

return True


def _getGridSize(idx) -> Tuple[int, int]:
"""
Return the number of spaces between the min and max of a collection of (int, int)
tuples, inclusive.

This essentially returns the number of grid locations along the i, and j dimesions,
given the (i,j) indices of each occupied location. This is useful for determining
certain grid offset behavior.
"""
nx = max(key[0] for key in idx) - min(key[0] for key in idx) + 1
ny = max(key[1] for key in idx) - min(key[1] for key in idx) + 1

return nx, ny
42 changes: 41 additions & 1 deletion armi/reactor/blueprints/tests/test_gridBlueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,27 @@
2 1 3 1 2
2 3 1 1 2
2 2 2 2 2

sfp quarter:
geom: cartesian
symmetry: quarter through center assembly
lattice map: |
2 2 2 2 2
2 1 1 1 2
2 1 3 1 2
2 3 1 1 2
2 2 2 2 2

sfp even:
geom: cartesian
symmetry: full
lattice map: |
1 2 2 2 2 2
1 2 1 1 1 2
1 2 1 4 1 2
1 2 2 1 1 2
1 2 2 2 2 2
1 1 1 1 1 1
"""

RZT_BLUEPRINT = """
Expand Down Expand Up @@ -240,9 +261,28 @@ def test_simple_read(self):
_grid = gridDesign.construct()
self.assertEqual(gridDesign.gridContents[0, -8], "6")

# Cartesian full, odd
gridDesign2 = self.grids["sfp"]
_grid = gridDesign2.construct()
self.assertEqual(gridDesign2.gridContents[1, 1], "3")
self.assertEqual(gridDesign2.gridContents[1, 1], "1")
self.assertEqual(gridDesign2.gridContents[0, 0], "3")
self.assertEqual(gridDesign2.gridContents[-1, -1], "3")
Comment on lines 265 to +269
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add unit tests to cover the following scenarios:

  • Cartesian, Full Core, Even
  • Cartesian, Quarter Core, Odd
  • Cartesian, Quarter Core, Even

The SFP test is nice, but let's make sure that we have the other variations tested too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added quarter and even cases


# Cartesian quarter, odd
gridDesign3 = self.grids["sfp quarter"]
_grid = gridDesign3.construct()
self.assertEqual(gridDesign3.gridContents[0, 0], "2")
self.assertEqual(gridDesign3.gridContents[1, 1], "3")
self.assertEqual(gridDesign3.gridContents[2, 2], "3")
self.assertEqual(gridDesign3.gridContents[3, 3], "1")

# Cartesian full, even/odd hybrid
gridDesign4 = self.grids["sfp even"]
_grid = gridDesign4.construct()
self.assertEqual(gridDesign4.gridContents[0, 0], "4")
self.assertEqual(gridDesign4.gridContents[-1, -1], "2")
self.assertEqual(gridDesign4.gridContents[2, 2], "2")
self.assertEqual(gridDesign4.gridContents[-3, -3], "1")

youngmit marked this conversation as resolved.
Show resolved Hide resolved

class TestRZTGridBlueprint(unittest.TestCase):
Expand Down
16 changes: 16 additions & 0 deletions armi/utils/asciimaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ def _updateDimensionsFromData(self):
_updateDimensionsFromAsciiLines : used when reading info from ascii lines
"""
self._ijMax = max(sum(key) for key in self.asciiLabelByIndices)
self._asciiMaxCol = max(key[0] for key in self.asciiLabelByIndices) + 1
self._asciiMaxLine = max(key[1] for key in self.asciiLabelByIndices) + 1

@staticmethod
def fromReactor(reactor):
Expand Down Expand Up @@ -245,6 +247,9 @@ def _makeOffsets(self):
def items(self):
return self.asciiLabelByIndices.items()

def keys(self):
return self.asciiLabelByIndices.keys()


class AsciiMapCartesian(AsciiMap):
"""
Expand All @@ -263,6 +268,17 @@ def _asciiLinesToIndices(self):
ij = self._getIJFromColRow(ci, li)
self.asciiLabelByIndices[ij] = asciiLabel

def _updateDimensionsFromData(self):
AsciiMap._updateDimensionsFromData(self)
iMin = min(key[0] for key in self.asciiLabelByIndices)
jMin = min(key[1] for key in self.asciiLabelByIndices)

if iMin > 0 or jMin > 0:
raise ValueError(
"Asciimaps only supports sets of indices that "
"start at less than or equal to zero, got {}, {}".format(iMin, jMin)
)

def _getIJFromColRow(self, columNum, lineNum):
return columNum, lineNum

Expand Down
17 changes: 14 additions & 3 deletions armi/utils/gridEditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1487,11 +1487,22 @@ def save(self, stream=None, full=False):
aMap = asciimaps.asciiMapFromGeomAndSym(
self.grid.geomType, self.grid.symmetry
)()
aMap.asciiLabelByIndices = gridDesign.gridContents
# asciimaps can't handle negative indices, so we bump everything
# forward if needed
offset = (
min(key[0] for key in gridDesign.gridContents.keys()),
min(key[1] for key in gridDesign.gridContents.keys()),
)
aMap.asciiLabelByIndices = {
(key[0] - offset[0], key[1] - offset[1]): val
for key, val in gridDesign.gridContents.items()
}
aMap.gridContentsToAscii()
except:
except Exception as e:
runLog.warning(
"Cannot write geometry with asciimap. Defaulting to dict."
"Cannot write geometry with asciimap. Defaulting to dict. Issue: {}".format(
e
)
)
aMap = None

Expand Down
16 changes: 10 additions & 6 deletions armi/utils/tests/test_asciimaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@


CARTESIAN_MAP = """2 2 2 2 2
2 2 2 2 2
2 1 1 1 2
2 1 3 1 2
2 3 1 1 2
Expand Down Expand Up @@ -178,19 +179,22 @@ def test_cartesian(self):
stream.seek(0)
asciimap.readAscii(stream.read())

with io.StringIO() as stream:
asciimap.writeAscii(stream)
stream.seek(0)
output = stream.read()
self.assertEqual(output, CARTESIAN_MAP)

self.assertEqual(asciimap[0, 0], "2")
self.assertEqual(asciimap[1, 1], "3")
self.assertEqual(asciimap[2, 2], "3")
self.assertEqual(asciimap[3, 3], "1")
with self.assertRaises(KeyError):
asciimap[5, 2] # pylint: disable=pointless-statement

outMap = asciimaps.AsciiMapCartesian()
outMap.asciiLabelByIndices = asciimap.asciiLabelByIndices
outMap.gridContentsToAscii()
with io.StringIO() as stream:
outMap.writeAscii(stream)
stream.seek(0)
output = stream.read()
self.assertEqual(output, CARTESIAN_MAP)

def test_hexThird(self):
"""Read 1/3 core flats-up maps."""
asciimap = asciimaps.AsciiMapHexThirdFlatsUp()
Expand Down