From 5f9abd26cf83b011929c034aca3425ee6e0498fd Mon Sep 17 00:00:00 2001 From: jstilley Date: Fri, 4 Oct 2024 09:22:58 -0700 Subject: [PATCH 1/9] Fixing hex block rotation in plotBlockDiagram --- armi/reactor/blocks.py | 7 ++ armi/reactor/tests/test_blocks.py | 123 +++++++++++++++--------------- armi/utils/hexagon.py | 1 - armi/utils/plotting.py | 104 ++++++++++++------------- 4 files changed, 119 insertions(+), 116 deletions(-) diff --git a/armi/reactor/blocks.py b/armi/reactor/blocks.py index bef0ade54..9242c2f27 100644 --- a/armi/reactor/blocks.py +++ b/armi/reactor/blocks.py @@ -1785,6 +1785,13 @@ def coords(self): round(y, units.FLOAT_DIMENSION_DECIMALS), ) + def cornersUp(self): + """Determine if the hex shape of is corners up or flats up, in relation to the Y axis.""" + if self.spatialGrid is None: + return None + + return self.spatialGrid._unitSteps[0][1] != 0.0 + def createHomogenizedCopy(self, pinSpatialLocators=False): """ Create a new homogenized copy of a block that is less expensive than a full deepcopy. diff --git a/armi/reactor/tests/test_blocks.py b/armi/reactor/tests/test_blocks.py index 08f729e9a..895aa0ab4 100644 --- a/armi/reactor/tests/test_blocks.py +++ b/armi/reactor/tests/test_blocks.py @@ -1876,30 +1876,33 @@ def test_negativeVolume(self): class HexBlock_TestCase(unittest.TestCase): def setUp(self): _ = settings.Settings() - self.HexBlock = blocks.HexBlock("TestHexBlock") + self.hexBlock = blocks.HexBlock("TestHexBlock") hexDims = {"Tinput": 273.0, "Thot": 273.0, "op": 70.6, "ip": 70.0, "mult": 1.0} self.hexComponent = components.Hexagon("duct", "UZr", **hexDims) - self.HexBlock.add(self.hexComponent) - self.HexBlock.add( + self.hexBlock.add(self.hexComponent) + self.hexBlock.add( components.Circle( "clad", "HT9", Tinput=273.0, Thot=273.0, od=0.1, mult=169.0 ) ) - self.HexBlock.add( + self.hexBlock.add( components.Circle( "wire", "HT9", Tinput=273.0, Thot=273.0, od=0.01, mult=169.0 ) ) - self.HexBlock.add( + self.hexBlock.add( components.DerivedShape("coolant", "Sodium", Tinput=273.0, Thot=273.0) ) - self.HexBlock.autoCreateSpatialGrids() + self.hexBlock.autoCreateSpatialGrids() r = tests.getEmptyHexReactor() a = makeTestAssembly(1, 1) - a.add(self.HexBlock) + a.add(self.hexBlock) loc1 = r.core.spatialGrid[0, 1, 0] r.core.add(a, loc1) + def test_cornersUp(self): + self.assertTrue(self.hexBlock.cornersUp()) + def test_getArea(self): """Test that we can correctly calculate the area of a hexagonal block. @@ -1936,7 +1939,7 @@ def test_component_type(self): :id: T_ARMI_BLOCK_HEX1 :tests: R_ARMI_BLOCK_HEX """ - pitch_comp_type = self.HexBlock.PITCH_COMPONENT_TYPE[0] + pitch_comp_type = self.hexBlock.PITCH_COMPONENT_TYPE[0] self.assertEqual(pitch_comp_type.__name__, "Hexagon") def test_coords(self): @@ -1947,17 +1950,17 @@ def test_coords(self): :id: T_ARMI_BLOCK_POSI1 :tests: R_ARMI_BLOCK_POSI """ - core = self.HexBlock.core - a = self.HexBlock.parent + core = self.hexBlock.core + a = self.hexBlock.parent loc1 = core.spatialGrid[0, 1, 0] a.spatialLocator = loc1 - x0, y0 = self.HexBlock.coords() + x0, y0 = self.hexBlock.coords() a.spatialLocator = core.spatialGrid[0, -1, 0] # symmetric - x2, y2 = self.HexBlock.coords() + x2, y2 = self.hexBlock.coords() a.spatialLocator = loc1 - self.HexBlock.p.displacementX = 0.01 - self.HexBlock.p.displacementY = 0.02 - x1, y1 = self.HexBlock.coords() + self.hexBlock.p.displacementX = 0.01 + self.hexBlock.p.displacementY = 0.02 + x1, y1 = self.hexBlock.coords() # make sure displacements are working self.assertAlmostEqual(x1 - x0, 1.0) @@ -1968,7 +1971,7 @@ def test_coords(self): self.assertAlmostEqual(y0, -y2) def test_getNumPins(self): - self.assertEqual(self.HexBlock.getNumPins(), 169) + self.assertEqual(self.hexBlock.getNumPins(), 169) def test_block_dims(self): """ @@ -1978,58 +1981,58 @@ def test_block_dims(self): :id: T_ARMI_BLOCK_DIMS :tests: R_ARMI_BLOCK_DIMS """ - self.assertAlmostEqual(4316.582, self.HexBlock.getVolume(), 3) - self.assertAlmostEqual(70.6, self.HexBlock.getPitch(), 1) - self.assertAlmostEqual(4316.582, self.HexBlock.getMaxArea(), 3) + self.assertAlmostEqual(4316.582, self.hexBlock.getVolume(), 3) + self.assertAlmostEqual(70.6, self.hexBlock.getPitch(), 1) + self.assertAlmostEqual(4316.582, self.hexBlock.getMaxArea(), 3) - self.assertEqual(70, self.HexBlock.getDuctIP()) - self.assertEqual(70.6, self.HexBlock.getDuctOP()) + self.assertEqual(70, self.hexBlock.getDuctIP()) + self.assertEqual(70.6, self.hexBlock.getDuctOP()) - self.assertAlmostEqual(34.273, self.HexBlock.getPinToDuctGap(), 3) - self.assertEqual(0.11, self.HexBlock.getPinPitch()) - self.assertAlmostEqual(300.889, self.HexBlock.getWettedPerimeter(), 3) - self.assertAlmostEqual(4242.184, self.HexBlock.getFlowArea(), 3) - self.assertAlmostEqual(56.395, self.HexBlock.getHydraulicDiameter(), 3) + self.assertAlmostEqual(34.273, self.hexBlock.getPinToDuctGap(), 3) + self.assertEqual(0.11, self.hexBlock.getPinPitch()) + self.assertAlmostEqual(300.889, self.hexBlock.getWettedPerimeter(), 3) + self.assertAlmostEqual(4242.184, self.hexBlock.getFlowArea(), 3) + self.assertAlmostEqual(56.395, self.hexBlock.getHydraulicDiameter(), 3) def test_symmetryFactor(self): # full hex - self.HexBlock.spatialLocator = self.HexBlock.core.spatialGrid[2, 0, 0] - self.HexBlock.clearCache() - self.assertEqual(1.0, self.HexBlock.getSymmetryFactor()) - a0 = self.HexBlock.getArea() - v0 = self.HexBlock.getVolume() - m0 = self.HexBlock.getMass() + self.hexBlock.spatialLocator = self.hexBlock.core.spatialGrid[2, 0, 0] + self.hexBlock.clearCache() + self.assertEqual(1.0, self.hexBlock.getSymmetryFactor()) + a0 = self.hexBlock.getArea() + v0 = self.hexBlock.getVolume() + m0 = self.hexBlock.getMass() # 1/3 symmetric - self.HexBlock.spatialLocator = self.HexBlock.core.spatialGrid[0, 0, 0] - self.HexBlock.clearCache() - self.assertEqual(3.0, self.HexBlock.getSymmetryFactor()) - self.assertEqual(a0 / 3.0, self.HexBlock.getArea()) - self.assertEqual(v0 / 3.0, self.HexBlock.getVolume()) - self.assertAlmostEqual(m0 / 3.0, self.HexBlock.getMass()) + self.hexBlock.spatialLocator = self.hexBlock.core.spatialGrid[0, 0, 0] + self.hexBlock.clearCache() + self.assertEqual(3.0, self.hexBlock.getSymmetryFactor()) + self.assertEqual(a0 / 3.0, self.hexBlock.getArea()) + self.assertEqual(v0 / 3.0, self.hexBlock.getVolume()) + self.assertAlmostEqual(m0 / 3.0, self.hexBlock.getMass()) def test_retainState(self): """Ensure retainState restores params and spatialGrids.""" - self.HexBlock.spatialGrid = grids.HexGrid.fromPitch(1.0) - self.HexBlock.setType("intercoolant") - with self.HexBlock.retainState(): - self.HexBlock.setType("fuel") - self.HexBlock.spatialGrid.changePitch(2.0) - self.assertAlmostEqual(self.HexBlock.spatialGrid.pitch, 1.0) - self.assertTrue(self.HexBlock.hasFlags(Flags.INTERCOOLANT)) + self.hexBlock.spatialGrid = grids.HexGrid.fromPitch(1.0) + self.hexBlock.setType("intercoolant") + with self.hexBlock.retainState(): + self.hexBlock.setType("fuel") + self.hexBlock.spatialGrid.changePitch(2.0) + self.assertAlmostEqual(self.hexBlock.spatialGrid.pitch, 1.0) + self.assertTrue(self.hexBlock.hasFlags(Flags.INTERCOOLANT)) def test_getPinCoords(self): - blockPitch = self.HexBlock.getPitch() - pinPitch = self.HexBlock.getPinPitch() - nPins = self.HexBlock.getNumPins() + blockPitch = self.hexBlock.getPitch() + pinPitch = self.hexBlock.getPinPitch() + nPins = self.hexBlock.getNumPins() side = hexagon.side(blockPitch) - xyz = self.HexBlock.getPinCoordinates() + xyz = self.hexBlock.getPinCoordinates() x, y, _z = zip(*xyz) self.assertAlmostEqual( y[1], y[2] ) # first two pins should be side by side on top. self.assertNotAlmostEqual(x[1], x[2]) - self.assertEqual(len(xyz), self.HexBlock.getNumPins()) + self.assertEqual(len(xyz), self.hexBlock.getNumPins()) # ensure all pins are within the proper bounds of a # flats-up oriented hex block @@ -2122,17 +2125,17 @@ def test_getPitchHomogeneousBlock(self): self.assertAlmostEqual(sum(c.getArea() for c in hexBlock), hexTotalArea) def test_getDuctPitch(self): - ductIP = self.HexBlock.getDuctIP() + ductIP = self.hexBlock.getDuctIP() self.assertAlmostEqual(70.0, ductIP) - ductOP = self.HexBlock.getDuctOP() + ductOP = self.hexBlock.getDuctOP() self.assertAlmostEqual(70.6, ductOP) def test_getPinCenterFlatToFlat(self): - nRings = hexagon.numRingsToHoldNumCells(self.HexBlock.getNumPins()) - pinPitch = self.HexBlock.getPinPitch() + nRings = hexagon.numRingsToHoldNumCells(self.hexBlock.getNumPins()) + pinPitch = self.hexBlock.getPinPitch() pinCenterCornerToCorner = 2 * (nRings - 1) * pinPitch pinCenterFlatToFlat = math.sqrt(3.0) / 2.0 * pinCenterCornerToCorner - f2f = self.HexBlock.getPinCenterFlatToFlat() + f2f = self.hexBlock.getPinCenterFlatToFlat() self.assertAlmostEqual(pinCenterFlatToFlat, f2f) def test_gridCreation(self): @@ -2142,7 +2145,7 @@ def test_gridCreation(self): :id: T_ARMI_GRID_MULT :tests: R_ARMI_GRID_MULT """ - b = self.HexBlock + b = self.hexBlock # The block should have a spatial grid at construction, # since it has mults = 1 or 169 from setup b.autoCreateSpatialGrids() @@ -2205,12 +2208,12 @@ def test_gridNotCreatedMultipleMultiplicities(self): } # add a wire only some places in the block, so grid should not be created. wire = components.Helix("wire", "HT9", **wireDims) - self.HexBlock.add(wire) - self.HexBlock.spatialGrid = None # clear existing + self.hexBlock.add(wire) + self.hexBlock.spatialGrid = None # clear existing with self.assertRaises(ValueError): - self.HexBlock.autoCreateSpatialGrids() + self.hexBlock.autoCreateSpatialGrids() - self.assertIsNone(self.HexBlock.spatialGrid) + self.assertIsNone(self.hexBlock.spatialGrid) class ThRZBlock_TestCase(unittest.TestCase): diff --git a/armi/utils/hexagon.py b/armi/utils/hexagon.py index 126c55e1c..1b0c2e4af 100644 --- a/armi/utils/hexagon.py +++ b/armi/utils/hexagon.py @@ -82,7 +82,6 @@ def corners(rotation=0): ) rotation = rotation / 180.0 * math.pi - rotation = np.array( [ [math.cos(rotation), -math.sin(rotation)], diff --git a/armi/utils/plotting.py b/armi/utils/plotting.py index 8e713eeb1..0fe9a746f 100644 --- a/armi/utils/plotting.py +++ b/armi/utils/plotting.py @@ -943,7 +943,7 @@ def plotBlockFlux(core, fName=None, bList=None, peak=False, adjoint=False, bList a flag that will produce the peak as well as the average on the plot. adjoint : bool, optional plot the adjoint as well. - bList2 : + bList2 : list, optional a separate list of blocks that will also be plotted on a separate axis on the same plot. This is useful for comparing flux in some blocks with flux in some other blocks. """ @@ -1038,7 +1038,7 @@ def getTable(self): bf.makePlotHistograms() if fName: - # write a little flux text file. + # write a little flux text file txtFileName = os.path.splitext(fName)[0] + ".txt" with open(txtFileName, "w") as f: f.write( @@ -1100,7 +1100,8 @@ def getTable(self): def makeHistogram(x, y): """ - Take a list of x and y values, and return a histogram-ified version + Take a list of x and y values, and return a histogram version. + Good for plotting multigroup flux spectrum or cross sections. """ if not len(x) == len(y): @@ -1123,38 +1124,35 @@ def makeHistogram(x, y): def _makeBlockPinPatches(block, cold): - """Return lists of block component patches and corresponding data and names (which relates to material - of the component for later plot-coloring/legend) for a single block. - + """Return lists of block component patches and corresponding data and names (which relates to + material of the component for later plot-coloring/legend) for a single block. - Takes in a block that must have a spatialGrid attached as well as a variable - which signifies whether the dimensions of the components are at hot or cold temps. - When cold is set to true, you would get the BOL cold temp dimensions. + Takes in a block that must have a spatialGrid attached as well as a variable which signifies + whether the dimensions of the components are at hot or cold temps. When cold is set to true, you + would get the BOL cold temp dimensions. Parameters ---------- block : Block - - cold : boolean + cold : bool true for cold temps, hot = false Return ------ patches : list list of patches for block components - data : list list of the materials these components are made of - name : list list of the names of these components """ patches = [] data = [] names = [] + cornersUp = False if isinstance(block.spatialGrid, grids.HexGrid): largestPitch, comp = block.getPitch(returnComp=True) - + cornersUp = block.cornersUp() elif isinstance(block.spatialGrid, grids.ThetaRZGrid): raise TypeError( "This plot function is not currently supported for ThetaRZGrid grids." @@ -1193,12 +1191,13 @@ def _makeBlockPinPatches(block, cold): ) else: raise TypeError( - "Shape of the pitch-defining element is not a Square or Hex it is " - f"{comp.shape}, cannot plot for this type of block." + f"Shape of the pitch-defining element is not a Square or Hex it is {comp.shape}, " + "cannot plot for this type of block." ) patches.append(derivedPatch) data.append(material) names.append(cName) + for component in sortedComps: locs = component.spatialLocator if not isinstance(locs, grids.MultiIndexLocation): @@ -1209,7 +1208,7 @@ def _makeBlockPinPatches(block, cold): # goes through each location # want to place a patch at that location - blockPatches = _makeComponentPatch(component, (x, y), cold) + blockPatches = _makeComponentPatch(component, (x, y), cold, cornersUp) for element in blockPatches: patches.append(element) @@ -1224,27 +1223,27 @@ def _makeBlockPinPatches(block, cold): return patches, data, names -def _makeComponentPatch(component, position, cold): +def _makeComponentPatch(component, position, cold, cornersUp=False): """Makes a component shaped patch to later be used for making block diagrams. Parameters ---------- - component: a component of a block - - position: tuple - (x, y) position - - cold: boolean - True if looking for dimension at cold temps + component: a component of a block + position: tuple + (x, y) position + cold: bool + True if looking for dimension at cold temps + cornersUp: bool, optional + If this is a HexBlock, is it corners-up or flats-up? Return ------ - blockPatch: List - A list of Patch objects that together represent a component in the diagram. + blockPatch: list + A list of Patch objects that together represent a component in the diagram. Notes ----- - Currently accepts components of shape DerivedShape, Helix, Circle, or Square + Currently accepts components of shape DerivedShape, Circle, Helix, Hexagon, or Square """ x = position[0] y = position[1] @@ -1268,7 +1267,6 @@ def _makeComponentPatch(component, position, cold): - (component.getDimension("id", cold=cold) / 2), ) elif isinstance(component, Circle): - blockPatch = matplotlib.patches.Wedge( (x, y), component.getDimension("od", cold=cold) / 2, @@ -1279,11 +1277,12 @@ def _makeComponentPatch(component, position, cold): ) elif isinstance(component, Hexagon): if component.getDimension("ip", cold=cold) != 0: + angle = 0 if cornersUp else 30 innerPoints = np.array( - hexagon.corners(30) * component.getDimension("ip", cold=cold) + hexagon.corners(angle) * component.getDimension("ip", cold=cold) ) outerPoints = np.array( - hexagon.corners(30) * component.getDimension("op", cold=cold) + hexagon.corners(angle) * component.getDimension("op", cold=cold) ) blockPatch = [] for n in range(6): @@ -1296,11 +1295,10 @@ def _makeComponentPatch(component, position, cold): patch = matplotlib.patches.Polygon(corners, fill=True) blockPatch.append(patch) else: - # Just make it a hexagon... + # Just make it a hexagon blockPatch = matplotlib.patches.RegularPolygon( (x, y), 6, radius=component.getDimension("op", cold=cold) / math.sqrt(3) ) - elif isinstance(component, Rectangle): if component.getDimension("widthInner", cold=cold) != 0: innerPoints = np.array( @@ -1355,7 +1353,7 @@ def _makeComponentPatch(component, position, cold): patch = matplotlib.patches.Polygon(corners, fill=True) blockPatch.append(patch) else: - # Just make it a rectangle... + # Just make it a rectangle blockPatch = matplotlib.patches.Rectangle( ( x - component.getDimension("widthOuter", cold=cold) / 2, @@ -1364,27 +1362,29 @@ def _makeComponentPatch(component, position, cold): component.getDimension("widthOuter", cold=cold), component.getDimension("lengthOuter", cold=cold), ) + if isinstance(blockPatch, list): return blockPatch + return [blockPatch] def plotBlockDiagram( block, fName, cold, cmapName="RdYlBu", materialList=None, fileFormat="svg" ): - """Given a Block with a spatial Grid, plot the diagram of - it with all of its components. (wire, duct, coolant, etc...). + """Given a Block with a spatial Grid, plot the diagram of it with all of its components (wire, + duct, coolant, etc). Parameters ---------- - block : block object - fName : String + block : Block + fName : str Name of the file to save to - cold : boolean + cold : bool True is for cold temps, False is hot - cmapName : String + cmapName : str name of a colorMap to use for block colors - materialList : List + materialList : list A list of material names across all blocks to be plotted so that same material on all diagrams will have the same color fileFormat : str @@ -1395,6 +1395,7 @@ def plotBlockDiagram( if block.spatialGrid is None: return None + # building a list of materials if materialList is None: materialList = [] for component in block: @@ -1406,34 +1407,27 @@ def plotBlockDiagram( materialList.append(materialName) materialMap = {material: ai for ai, material in enumerate(np.unique(materialList))} - patches, data, _ = _makeBlockPinPatches(block, cold) + allColors = np.array(list(materialMap.values())) + # build the geometric shapes on the plot + patches, data, _ = _makeBlockPinPatches(block, cold) collection = PatchCollection(patches, cmap=cmapName, alpha=1.0) - allColors = np.array(list(materialMap.values())) ourColors = np.array([materialMap[materialName] for materialName in data]) - collection.set_array(ourColors) ax.add_collection(collection) collection.norm.autoscale(allColors) + # set up plot axis, labels and legends legendMap = [ - ( - materialMap[materialName], - "", - "{}".format(materialName), - ) + (materialMap[materialName], "", "{}".format(materialName)) for materialName in np.unique(data) ] legend = _createLegend(legendMap, collection, size=50, shape=Rectangle) - pltKwargs = { - "bbox_extra_artists": (legend,), - "bbox_inches": "tight", - } + pltKwargs = {"bbox_extra_artists": (legend,), "bbox_inches": "tight"} ax.set_xticks([]) ax.set_yticks([]) - ax.spines["right"].set_visible(False) ax.spines["top"].set_visible(False) ax.spines["left"].set_visible(False) @@ -1449,7 +1443,7 @@ def plotNucXs( isotxs, nucNames, xsNames, fName=None, label=None, noShow=False, title=None ): """ - generates a XS plot for a nuclide on the ISOTXS library. + Generates a XS plot for a nuclide on the ISOTXS library. Parameters ---------- From 64dc20e6aedf7ae9150a96f85fe28e6c80c802b5 Mon Sep 17 00:00:00 2001 From: jstilley Date: Fri, 4 Oct 2024 09:23:50 -0700 Subject: [PATCH 2/9] adding release note --- doc/release/0.4.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/release/0.4.rst b/doc/release/0.4.rst index d2374817f..fbe8c80fd 100644 --- a/doc/release/0.4.rst +++ b/doc/release/0.4.rst @@ -42,6 +42,7 @@ Bug Fixes #. Update height of fluid components after axial expansion (`PR#1828 `_) #. Material theoretical density is serialized to and read from database. (`PR#1852 `_) #. Removed broken and unused column in ``summarizeMaterialData``. (`PR#1925 `_) +#. Fixed hex block rotation in ``plotBlockDiagram``. (`PR#1926 `_) #. TBD Quality Work From 77971338a16a963dd0a7856fba4837491b2dcfb1 Mon Sep 17 00:00:00 2001 From: jstilley Date: Fri, 4 Oct 2024 09:35:09 -0700 Subject: [PATCH 3/9] Fixing block test name and docstring --- armi/reactor/tests/test_blocks.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/armi/reactor/tests/test_blocks.py b/armi/reactor/tests/test_blocks.py index 895aa0ab4..e213d9d85 100644 --- a/armi/reactor/tests/test_blocks.py +++ b/armi/reactor/tests/test_blocks.py @@ -1543,12 +1543,8 @@ def test_expandAllElementalsToIsotopics(self): "It adds to {1}".format(initialM[elemental], newMass, elemental), ) - def test_setPitch(self): - r""" - Checks consistency after adjusting pitch. - - Needed to verify fix to Issue #165. - """ + def test_setPitchDoesNotAffectBOL(self): + """Verify that changing a Blocks pitch doesn't alter physical parameters defined at BOL.""" b = self.block moles1 = b.p.molesHmBOL b.setPitch(17.5) From 09e8907e3d619985c501d64f611d57dbcb300aea Mon Sep 17 00:00:00 2001 From: jstilley Date: Fri, 4 Oct 2024 15:46:13 -0700 Subject: [PATCH 4/9] Responding to comments --- armi/reactor/blocks.py | 1 - armi/utils/plotting.py | 26 ++++++++++++++++---------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/armi/reactor/blocks.py b/armi/reactor/blocks.py index c439b4425..ec8a3007c 100644 --- a/armi/reactor/blocks.py +++ b/armi/reactor/blocks.py @@ -1903,7 +1903,6 @@ def getMaxArea(self): This method first retrieves the pitch of the hexagonal Block (:need:`I_ARMI_UTIL_HEXAGON0`) and then leverages the area calculation via :need:`I_ARMI_UTIL_HEXAGON0`. - """ pitch = self.getPitch() if not pitch: diff --git a/armi/utils/plotting.py b/armi/utils/plotting.py index 0fe9a746f..5c49cede6 100644 --- a/armi/utils/plotting.py +++ b/armi/utils/plotting.py @@ -1243,7 +1243,7 @@ def _makeComponentPatch(component, position, cold, cornersUp=False): Notes ----- - Currently accepts components of shape DerivedShape, Circle, Helix, Hexagon, or Square + Currently accepts components of shape Circle, Helix, Hexagon, or Square """ x = position[0] y = position[1] @@ -1276,15 +1276,17 @@ def _makeComponentPatch(component, position, cold, cornersUp=False): - (component.getDimension("id", cold=cold) / 2), ) elif isinstance(component, Hexagon): + angle = 0 if cornersUp else 30 + outerPoints = np.array( + hexagon.corners(angle) * component.getDimension("op", cold=cold) + ) + blockPatch = [] + if component.getDimension("ip", cold=cold) != 0: - angle = 0 if cornersUp else 30 + # a hexagonal ring innerPoints = np.array( hexagon.corners(angle) * component.getDimension("ip", cold=cold) ) - outerPoints = np.array( - hexagon.corners(angle) * component.getDimension("op", cold=cold) - ) - blockPatch = [] for n in range(6): corners = [ innerPoints[n], @@ -1295,10 +1297,14 @@ def _makeComponentPatch(component, position, cold, cornersUp=False): patch = matplotlib.patches.Polygon(corners, fill=True) blockPatch.append(patch) else: - # Just make it a hexagon - blockPatch = matplotlib.patches.RegularPolygon( - (x, y), 6, radius=component.getDimension("op", cold=cold) / math.sqrt(3) - ) + # a simple hexagon + for n in range(6): + corners = [ + outerPoints[(n + 1) % 6], + outerPoints[n], + ] + patch = matplotlib.patches.Polygon(corners, fill=True) + blockPatch.append(patch) elif isinstance(component, Rectangle): if component.getDimension("widthInner", cold=cold) != 0: innerPoints = np.array( From 877fcbef5a1a1c75e994d03175e61f88f55b92b5 Mon Sep 17 00:00:00 2001 From: jstilley Date: Fri, 4 Oct 2024 16:00:07 -0700 Subject: [PATCH 5/9] Responding to comments --- armi/reactor/tests/test_blocks.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/armi/reactor/tests/test_blocks.py b/armi/reactor/tests/test_blocks.py index e213d9d85..3dd1253b3 100644 --- a/armi/reactor/tests/test_blocks.py +++ b/armi/reactor/tests/test_blocks.py @@ -1897,8 +1897,16 @@ def setUp(self): r.core.add(a, loc1) def test_cornersUp(self): + # check corners-up self.assertTrue(self.hexBlock.cornersUp()) + # replace the spatialGrid with one to make it flats-up + grid0 = grids.HexGrid.fromPitch( + self.hexBlock.getPinPitch(cold=True), numRings=0, cornersUp=False + ) + self.hexBlock.spatialGrid = grid0 + self.assertFalse(self.hexBlock.cornersUp()) + def test_getArea(self): """Test that we can correctly calculate the area of a hexagonal block. From 3d17ec747015970268954517879f9c3508896e96 Mon Sep 17 00:00:00 2001 From: jstilley Date: Tue, 8 Oct 2024 16:04:28 -0700 Subject: [PATCH 6/9] cleanup - reviewer --- armi/reactor/blocks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/armi/reactor/blocks.py b/armi/reactor/blocks.py index 4aaef2c0f..63ac2e732 100644 --- a/armi/reactor/blocks.py +++ b/armi/reactor/blocks.py @@ -1795,7 +1795,7 @@ def cornersUp(self): if self.spatialGrid is None: return None - return self.spatialGrid._unitSteps[0][1] != 0.0 + return self.spatialGrid.cornersUp def createHomogenizedCopy(self, pinSpatialLocators=False): """ From a4e3d955833ba5e3b3bddefee2da7932b06d3405 Mon Sep 17 00:00:00 2001 From: jstilley Date: Tue, 8 Oct 2024 17:20:51 -0700 Subject: [PATCH 7/9] Fixing plot for DerivedShapes --- armi/utils/plotting.py | 10 ++++------ armi/utils/tests/test_plotting.py | 7 +++---- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/armi/utils/plotting.py b/armi/utils/plotting.py index 5c49cede6..cbc553742 100644 --- a/armi/utils/plotting.py +++ b/armi/utils/plotting.py @@ -1180,8 +1180,9 @@ def _makeBlockPinPatches(block, cold): location = location[0] x, y, _ = location.getLocalCoordinates() if isinstance(comp, Hexagon): + orient = math.pi / 6 if cornersUp else 0 derivedPatch = matplotlib.patches.RegularPolygon( - (x, y), 6, radius=largestPitch / math.sqrt(3) + (x, y), 6, radius=largestPitch / math.sqrt(3), orientation=orient ) elif isinstance(comp, Square): derivedPatch = matplotlib.patches.Rectangle( @@ -1206,8 +1207,7 @@ def _makeBlockPinPatches(block, cold): for loc in locs: x, y, _ = loc.getLocalCoordinates() - # goes through each location - # want to place a patch at that location + # goes through each location in stack order blockPatches = _makeComponentPatch(component, (x, y), cold, cornersUp) for element in blockPatches: patches.append(element) @@ -1375,9 +1375,7 @@ def _makeComponentPatch(component, position, cold, cornersUp=False): return [blockPatch] -def plotBlockDiagram( - block, fName, cold, cmapName="RdYlBu", materialList=None, fileFormat="svg" -): +def plotBlockDiagram(block, fName, cold, cmapName="RdYlBu", materialList=None, fileFormat="svg"): """Given a Block with a spatial Grid, plot the diagram of it with all of its components (wire, duct, coolant, etc). diff --git a/armi/utils/tests/test_plotting.py b/armi/utils/tests/test_plotting.py index 0fdb9e1cc..f6795e0ef 100644 --- a/armi/utils/tests/test_plotting.py +++ b/armi/utils/tests/test_plotting.py @@ -18,7 +18,10 @@ import numpy as np +from armi import settings from armi.nuclearDataIO.cccc import isotxs +from armi.reactor import blueprints +from armi.reactor import reactors from armi.reactor.flags import Flags from armi.reactor.tests import test_reactors from armi.tests import ISOAA_PATH, TEST_ROOT @@ -106,14 +109,10 @@ def test_plotBlockFlux(self): def test_plotHexBlock(self): with TemporaryDirectoryChanger(): first_fuel_block = self.r.core.getFirstBlock(Flags.FUEL) - first_fuel_block.autoCreateSpatialGrids() plotting.plotBlockDiagram(first_fuel_block, "blockDiagram23.svg", True) self.assertTrue(os.path.exists("blockDiagram23.svg")) def test_plotCartesianBlock(self): - from armi import settings - from armi.reactor import blueprints, reactors - with TemporaryDirectoryChanger(): cs = settings.Settings( os.path.join(TEST_ROOT, "tutorials", "c5g7-settings.yaml") From 716cd9b113886ef826de691edd6991bdd252cfe5 Mon Sep 17 00:00:00 2001 From: jstilley Date: Tue, 8 Oct 2024 17:22:58 -0700 Subject: [PATCH 8/9] Fixing linting --- armi/utils/plotting.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/armi/utils/plotting.py b/armi/utils/plotting.py index cbc553742..c3b8fcf9c 100644 --- a/armi/utils/plotting.py +++ b/armi/utils/plotting.py @@ -1375,7 +1375,9 @@ def _makeComponentPatch(component, position, cold, cornersUp=False): return [blockPatch] -def plotBlockDiagram(block, fName, cold, cmapName="RdYlBu", materialList=None, fileFormat="svg"): +def plotBlockDiagram( + block, fName, cold, cmapName="RdYlBu", materialList=None, fileFormat="svg" +): """Given a Block with a spatial Grid, plot the diagram of it with all of its components (wire, duct, coolant, etc). From 4a2aa9f8b0f39e7a5ddd8baa961bf3078aa3bd0c Mon Sep 17 00:00:00 2001 From: jstilley Date: Wed, 9 Oct 2024 09:05:10 -0700 Subject: [PATCH 9/9] Reviewing cornersUp example --- armi/utils/plotting.py | 4 ++-- armi/utils/tests/test_plotting.py | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/armi/utils/plotting.py b/armi/utils/plotting.py index c3b8fcf9c..d19c7f8fb 100644 --- a/armi/utils/plotting.py +++ b/armi/utils/plotting.py @@ -1180,7 +1180,7 @@ def _makeBlockPinPatches(block, cold): location = location[0] x, y, _ = location.getLocalCoordinates() if isinstance(comp, Hexagon): - orient = math.pi / 6 if cornersUp else 0 + orient = 0 if cornersUp else math.pi / 6 derivedPatch = matplotlib.patches.RegularPolygon( (x, y), 6, radius=largestPitch / math.sqrt(3), orientation=orient ) @@ -1276,7 +1276,7 @@ def _makeComponentPatch(component, position, cold, cornersUp=False): - (component.getDimension("id", cold=cold) / 2), ) elif isinstance(component, Hexagon): - angle = 0 if cornersUp else 30 + angle = 30 if cornersUp else 0 outerPoints = np.array( hexagon.corners(angle) * component.getDimension("op", cold=cold) ) diff --git a/armi/utils/tests/test_plotting.py b/armi/utils/tests/test_plotting.py index f6795e0ef..0d93bd5e9 100644 --- a/armi/utils/tests/test_plotting.py +++ b/armi/utils/tests/test_plotting.py @@ -54,13 +54,13 @@ def test_plotDepthMap(self): # indirectly tests plot face map fName = plotting.plotBlockDepthMap( self.r.core, param="percentBu", fName="depthMapPlot.png", depthIndex=2 ) - self._checkExists(fName) + self._checkFileExists(fName) def test_plotAssemblyTypes(self): with TemporaryDirectoryChanger(): plotPath = "coreAssemblyTypes1.png" plotting.plotAssemblyTypes(self.r.core.parent.blueprints, plotPath) - self._checkExists(plotPath) + self._checkFileExists(plotPath) if os.path.exists(plotPath): os.remove(plotPath) @@ -72,7 +72,7 @@ def test_plotAssemblyTypes(self): yAxisLabel="y axis", title="title", ) - self._checkExists(plotPath) + self._checkFileExists(plotPath) if os.path.exists(plotPath): os.remove(plotPath) @@ -97,36 +97,36 @@ def test_plotBlockFlux(self): plotting.plotBlockFlux( self.r.core, fName="peak.png", bList=blockList, peak=True ) - self.assertTrue(os.path.exists("peak.png")) + self._checkFileExists("peak.png") plotting.plotBlockFlux( self.r.core, fName="bList2.png", bList=blockList, bList2=blockList, ) - self.assertTrue(os.path.exists("bList2.png")) + self._checkFileExists("bList2.png") def test_plotHexBlock(self): with TemporaryDirectoryChanger(): first_fuel_block = self.r.core.getFirstBlock(Flags.FUEL) plotting.plotBlockDiagram(first_fuel_block, "blockDiagram23.svg", True) - self.assertTrue(os.path.exists("blockDiagram23.svg")) + self._checkFileExists("blockDiagram23.svg") def test_plotCartesianBlock(self): with TemporaryDirectoryChanger(): cs = settings.Settings( os.path.join(TEST_ROOT, "tutorials", "c5g7-settings.yaml") ) - blueprint = blueprints.loadFromCs(cs) _ = reactors.factory(cs, blueprint) for name, bDesign in blueprint.blockDesigns.items(): b = bDesign.construct(cs, blueprint, 0, 1, 1, "AA", {}) plotting.plotBlockDiagram(b, "{}.svg".format(name), True) - self.assertTrue(os.path.exists("uo2.svg")) - self.assertTrue(os.path.exists("mox.svg")) - def _checkExists(self, fName): + self._checkFileExists("uo2.svg") + self._checkFileExists("mox.svg") + + def _checkFileExists(self, fName): self.assertTrue(os.path.exists(fName))