From bb6ba5cb2f59804cc5ba973c1dcac6b269e34601 Mon Sep 17 00:00:00 2001 From: John Stilley <1831479+john-science@users.noreply.github.com> Date: Mon, 4 Mar 2024 11:07:44 -0800 Subject: [PATCH 1/2] Removing unused method HexGrid.allPositionsInThird (#1655) --- armi/reactor/grids/hexagonal.py | 32 +------------------------------- doc/release/0.3.rst | 1 + 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/armi/reactor/grids/hexagonal.py b/armi/reactor/grids/hexagonal.py index a98b88b82..67ba13f02 100644 --- a/armi/reactor/grids/hexagonal.py +++ b/armi/reactor/grids/hexagonal.py @@ -25,7 +25,7 @@ BOUNDARY_60_DEGREES, BOUNDARY_CENTER, ) -from armi.reactor.grids.locations import IndexLocation, IJKType, IJType +from armi.reactor.grids.locations import IJKType, IJType from armi.reactor.grids.structuredGrid import StructuredGrid COS30 = sqrt(3) / 2.0 @@ -469,33 +469,3 @@ def generateSortedHexLocationList(self, nLocs: int): ) return locList[:nLocs] - - # TODO: this is only used by testing and another method that just needs the count of assemblies - # in a ring, not the actual positions - def allPositionsInThird(self, ring, includeEdgeAssems=False): - """ - Returns a list of all the positions in a ring (in the first third). - - Parameters - ---------- - ring : int - The ring to check - includeEdgeAssems : bool, optional - If True, include repeated positions in odd ring numbers. Default: False - - Notes - ----- - Rings start at 1, positions start at 1 - - Returns - ------- - positions : int - """ - positions = [] - for pos in range(1, self.getPositionsInRing(ring) + 1): - i, j = self.getIndicesFromRingAndPos(ring, pos) - loc = IndexLocation(i, j, 0, None) - if self.isInFirstThird(loc, includeEdgeAssems): - positions.append(pos) - - return positions diff --git a/doc/release/0.3.rst b/doc/release/0.3.rst index 4f708df3f..4dd00b665 100644 --- a/doc/release/0.3.rst +++ b/doc/release/0.3.rst @@ -14,6 +14,7 @@ API Changes ----------- #. Renaming ``structuredgrid.py`` to camelCase. (`PR#1650 `_) #. Removing unused argument from ``Block.coords()``. (`PR#1651 `_) +#. Removing unused method ``HexGrid.allPositionsInThird()``. (`PR#1655 `_) #. TBD Bug Fixes From 64351a0a5538e6a150d2c4f9a1183fa6558f5269 Mon Sep 17 00:00:00 2001 From: John Stilley <1831479+john-science@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:34:22 -0800 Subject: [PATCH 2/2] Increasing Code Coverage (#1656) --- armi/bookkeeping/db/tests/test_comparedb3.py | 2 +- armi/cases/suite.py | 8 +- armi/cases/tests/test_cases.py | 81 +++++++++++++++++-- armi/reactor/assemblies.py | 16 ++-- armi/reactor/reactors.py | 35 --------- armi/reactor/tests/test_assemblies.py | 65 ++++++++------- armi/utils/plotting.py | 83 -------------------- doc/release/0.3.rst | 1 + 8 files changed, 124 insertions(+), 167 deletions(-) diff --git a/armi/bookkeeping/db/tests/test_comparedb3.py b/armi/bookkeeping/db/tests/test_comparedb3.py index 0c72a2bc2..f482e6fa2 100644 --- a/armi/bookkeeping/db/tests/test_comparedb3.py +++ b/armi/bookkeeping/db/tests/test_comparedb3.py @@ -94,7 +94,7 @@ def test_diffResultsBasic(self): self.assertEqual(dr.nDiffs(), 10) def test_compareDatabaseDuplicate(self): - """end-to-end test of compareDatabases() on a photocopy database.""" + """End-to-end test of compareDatabases() on a photocopy database.""" # build two super-simple H5 files for testing o, r = test_reactors.loadTestReactor( TEST_ROOT, customSettings={"reloadDBName": "reloadingDB.h5"} diff --git a/armi/cases/suite.py b/armi/cases/suite.py index 75701d2b9..f3928403e 100644 --- a/armi/cases/suite.py +++ b/armi/cases/suite.py @@ -203,9 +203,10 @@ def run(self): """ Run each case, one after the other. - .. warning:: Suite running may not work yet if the cases have interdependencies. - We typically run on a HPC but are still working on a platform - independent way of handling HPCs. + Warning + ------- + Suite running may not work yet if the cases have interdependencies. We typically run on a + HPC but are still working on a platform independent way of handling HPCs. """ for ci, case in enumerate(self): runLog.important(f"Running case {ci+1}/{len(self)}: {case}") @@ -313,6 +314,7 @@ def writeTable(tableResults): userFile, refFile, caseIssues = tableResults[testName] data.append((testName, userFile, refFile, caseIssues)) totalDiffs += caseIssues + print(tabulate.tabulate(data, header, tablefmt=fmt)) print( tabulate.tabulate( diff --git a/armi/cases/tests/test_cases.py b/armi/cases/tests/test_cases.py index 27c0cac0a..0483868fa 100644 --- a/armi/cases/tests/test_cases.py +++ b/armi/cases/tests/test_cases.py @@ -20,6 +20,8 @@ import platform import unittest +import h5py + from armi import cases from armi import context from armi import getApp @@ -27,9 +29,11 @@ from armi import plugins from armi import runLog from armi import settings +from armi.bookkeeping.db.databaseInterface import DatabaseInterface from armi.physics.fuelCycle.settings import CONF_SHUFFLE_LOGIC from armi.reactor import blueprints from armi.reactor import systemLayoutInput +from armi.reactor.tests import test_reactors from armi.tests import ARMI_RUN_PATH from armi.tests import mockRunLogs from armi.tests import TEST_ROOT @@ -307,16 +311,10 @@ def test_checkInputs(self): self.c2.checkInputs() def test_dependenciesWithObscurePaths(self): - """ - Test directory dependence. - - .. tip:: This should be updated to use the Python pathlib - so the tests can work in both Linux and Windows identically. - """ + """Test directory dependence for strangely-written file paths (escape characters).""" checks = [ ("c1.yaml", "c2.yaml", "c1.h5", True), (r"\\case\1\c1.yaml", r"\\case\2\c2.yaml", "c1.h5", False), - # below doesn't work due to some windows path obscurities (r"\\case\1\c1.yaml", r"\\case\2\c2.yaml", r"..\1\c1.h5", False), ] if platform.system() == "Windows": @@ -335,7 +333,7 @@ def test_dependenciesWithObscurePaths(self): r"c2.yaml", r".\c1.h5", True, - ), # py bug in 3.6.4 and 3.7.1 fails here + ), ( r"\\cas\es\1\c1.yaml", r"\\cas\es\2\c2.yaml", @@ -436,6 +434,73 @@ def test_buildCommand(self): self.assertEqual(cmd, 'python -u -m armi run "c1.yaml"') +class TestCaseSuiteComparison(unittest.TestCase): + """CaseSuite.compare() tests.""" + + def setUp(self): + self.td = directoryChangers.TemporaryDirectoryChanger() + self.td.__enter__() + + def tearDown(self): + self.td.__exit__(None, None, None) + + def test_compareNoDiffs(self): + """As a baseline, this test should always reveal zero diffs.""" + # build two super-simple H5 files for testing + o, r = test_reactors.loadTestReactor( + TEST_ROOT, customSettings={"reloadDBName": "reloadingDB.h5"} + ) + + suites = [] + for _i in range(2): + # Build the cases + suite = cases.CaseSuite(settings.Settings()) + + geom = systemLayoutInput.SystemLayoutInput() + geom.readGeomFromStream(io.StringIO(GEOM_INPUT)) + bp = blueprints.Blueprints.load(BLUEPRINT_INPUT) + + c1 = cases.Case(cs=settings.Settings(), geom=geom, bp=bp) + c1.cs.path = "c1.yaml" + suite.add(c1) + + c2 = cases.Case(cs=settings.Settings(), geom=geom, bp=bp) + c2.cs.path = "c2.yaml" + suite.add(c2) + + suites.append(suite) + + # create two DBs, identical but for file names + tmpDir = os.getcwd() + dbs = [] + for i in range(1, 3): + # create the tests DB + dbi = DatabaseInterface(r, o.cs) + dbi.initDB(fName=f"{tmpDir}/c{i}.h5") + db = dbi.database + + # validate the file exists, and force it to be readable again + b = h5py.File(db._fullPath, "r") + self.assertEqual(list(b.keys()), ["inputs"]) + self.assertEqual( + sorted(b["inputs"].keys()), ["blueprints", "geomFile", "settings"] + ) + b.close() + + # append to lists + dbs.append(db) + + # do a comparison that should have no diffs + diff = c1.compare(c2) + self.assertEqual(diff, 0) + + diff = suites[0].compare(suites[1]) + self.assertEqual(diff, 0) + + diff = suites[1].compare(suites[0]) + self.assertEqual(diff, 0) + + class TestExtraInputWriting(unittest.TestCase): """Make sure extra inputs from interfaces are written.""" diff --git a/armi/reactor/assemblies.py b/armi/reactor/assemblies.py index 77eadca41..85f4cfea9 100644 --- a/armi/reactor/assemblies.py +++ b/armi/reactor/assemblies.py @@ -126,8 +126,7 @@ def renameBlocksAccordingToAssemblyNum(self): Notes ----- - You must run armi.reactor.reactors.Reactor.regenAssemblyLists after calling - this. + You must run armi.reactor.reactors.Reactor.regenAssemblyLists after calling this. """ assemNum = self.getNum() for bi, b in enumerate(self): @@ -303,7 +302,9 @@ def getPinPlenumVolumeInCubicMeters(self): ----- If there is no plenum blocks in the assembly, a plenum volume of 0.0 is returned - .. warning:: This is a bit design-specific for pinned assemblies + Warning + ------- + This is a bit design-specific for pinned assemblies """ plenumBlocks = self.getBlocks(Flags.PLENUM) @@ -342,8 +343,9 @@ def doubleResolution(self): ----- Used for mesh sensitivity studies. - .. warning:: This is likely destined for a geometry converter rather than - this instance method. + Warning + ------- + This is likely destined for a geometry converter rather than this instance method. """ newBlockStack = [] topIndex = -1 @@ -412,6 +414,7 @@ def adjustResolution(self, refA): newBlocks -= ( 1 # subtract one because we eliminated the original b completely. ) + self.removeAll() self.spatialGrid = grids.axialUnitGrid(len(newBlockStack)) for b in newBlockStack: @@ -439,7 +442,6 @@ def getAxialMesh(self, centers=False, zeroAtFuel=False): armi.reactor.reactors.Reactor.findAllAxialMeshPoints : gets a global list of all of these, plus finer res. - """ bottom = 0.0 meshVals = [] @@ -509,7 +511,6 @@ def getTotalHeight(self, typeSpec=None): ------- height : float the height in cm - """ h = 0.0 for b in self: @@ -561,7 +562,6 @@ def getElevationBoundariesByBlockType(self, blockType=None): elevation : list of floats Every float in the list is an elevation of a block boundary for the block type specified (has duplicates) - """ elevation, elevationsWithBlockBoundaries = 0.0, [] diff --git a/armi/reactor/reactors.py b/armi/reactor/reactors.py index e18ac04e3..7c10e204e 100644 --- a/armi/reactor/reactors.py +++ b/armi/reactor/reactors.py @@ -2350,41 +2350,6 @@ def getAvgTemp(self, typeSpec, blockList=None, flux2Weight=False): else: raise RuntimeError("no temperature average for {0}".format(typeSpec)) - def getAllNuclidesIn(self, mats): - """ - Find all nuclides that are present in these materials anywhere in the core. - - Parameters - ---------- - mats : iterable or Material - List (or single) of materials to scan the full core for, accumulating a nuclide list - - Returns - ------- - allNucNames : list - All nuclide names in this material anywhere in the reactor - - See Also - -------- - getDominantMaterial : finds the most prevalent material in a certain type of blocks - Block.adjustDensity : modifies nuclides in a block - - Notes - ----- - If you need to know the nuclides in a fuel pin, you can't just use the sample returned - from getDominantMaterial, because it may be a fresh fuel material (U and Zr) even though - there are burned materials elsewhere (with U, Zr, Pu, LFP, etc.). - """ - if not isinstance(mats, list): - # single material passed in - mats = [mats] - names = set(m.name for m in mats) - allNucNames = set() - for c in self.iterComponents(): - if c.material.name in names: - allNucNames.update(c.getNuclides()) - return list(allNucNames) - def growToFullCore(self, cs): """Copies symmetric assemblies to build a full core model out of a 1/3 core model. diff --git a/armi/reactor/tests/test_assemblies.py b/armi/reactor/tests/test_assemblies.py index a2435cdfc..37e9df87b 100644 --- a/armi/reactor/tests/test_assemblies.py +++ b/armi/reactor/tests/test_assemblies.py @@ -13,38 +13,39 @@ # limitations under the License. """Tests assemblies.py.""" -import numpy as np +import math import pathlib import random import unittest + +import numpy as np from numpy.testing import assert_allclose from armi import settings from armi import tests +from armi.physics.neutronics.settings import ( + CONF_LOADING_FILE, + CONF_XS_KERNEL, +) from armi.reactor import assemblies -from armi.reactor import blueprints from armi.reactor import blocks +from armi.reactor import blueprints from armi.reactor import components +from armi.reactor import geometry from armi.reactor import parameters from armi.reactor import reactors -from armi.reactor import geometry from armi.reactor.assemblies import ( copy, Flags, grids, HexAssembly, - math, numpy, runLog, ) +from armi.reactor.tests import test_reactors from armi.tests import TEST_ROOT, mockRunLogs from armi.utils import directoryChangers from armi.utils import textProcessors -from armi.reactor.tests import test_reactors -from armi.physics.neutronics.settings import ( - CONF_LOADING_FILE, - CONF_XS_KERNEL, -) NUM_BLOCKS = 3 @@ -882,7 +883,7 @@ def test_getDominantMaterial(self): self.assertEqual(self.assembly.getDominantMaterial().getName(), ref) def test_iteration(self): - r"""Tests the ability to doubly-loop over assemblies (under development).""" + """Tests the ability to doubly-loop over assemblies (under development).""" a = self.assembly for bi, b in enumerate(a): @@ -917,7 +918,6 @@ def test_getBlocksAndZ(self): def test_getBlocksBetweenElevations(self): # assembly should have 3 blocks of 10 cm in it - blocksAndHeights = self.assembly.getBlocksBetweenElevations(0, 10) self.assertEqual(blocksAndHeights[0], (self.assembly[0], 10.0)) @@ -1135,6 +1135,19 @@ def test_assem_hex_type(self): pitch_comp_type = b.PITCH_COMPONENT_TYPE[0] self.assertEqual(pitch_comp_type.__name__, "Hexagon") + def test_getBIndexFromZIndex(self): + # make sure the axMesh parameters are set in our test block + for b in self.assembly: + b.p.axMesh = 1 + + for zIndex in range(6): + bIndex = self.assembly.getBIndexFromZIndex(zIndex * 0.5) + self.assertEqual(bIndex, math.ceil(zIndex / 2) if zIndex < 5 else -1) + + def test_getElevationBoundariesByBlockType(self): + elevations = self.assembly.getElevationBoundariesByBlockType() + self.assertEqual(elevations, [0.0, 10.0, 10.0, 20.0, 20.0, 30.0]) + class AssemblyInReactor_TestCase(unittest.TestCase): def setUp(self): @@ -1146,9 +1159,8 @@ def test_snapAxialMeshToReferenceConservingMassBasedOnBlockIgniter(self): grid = self.r.core.spatialGrid - ################################ - # examine mass change in igniterFuel - ################################ + # 1. examine mass change in igniterFuel + igniterFuel = self.r.core.childrenByLocator[grid[0, 0, 0]] # gridplate, fuel, fuel, fuel, plenum b = igniterFuel[0] @@ -1174,9 +1186,8 @@ def test_snapAxialMeshToReferenceConservingMassBasedOnBlockIgniter(self): for a in self.r.core.getAssemblies(): a.setBlockMesh(refMesh, conserveMassFlag="auto") - ############################# - # check igniter mass after expansion - ############################# + # 2. check igniter mass after expansion + # gridplate, fuel, fuel, fuel, plenum b = igniterFuel[0] coolantNucs = b.getComponent(Flags.COOLANT).getNuclides() @@ -1207,9 +1218,8 @@ def test_snapAxialMeshToReferenceConservingMassBasedOnBlockIgniter(self): for a in self.r.core.getAssemblies(): a.setBlockMesh(originalMesh, conserveMassFlag="auto") - ############################# - # check igniter mass after shrink to original - ############################# + # 3. check igniter mass after shrink to original + # gridplate, fuel, fuel, fuel, plenum b = igniterFuel[0] coolantNucs = b.getComponent(Flags.COOLANT).getNuclides() @@ -1245,9 +1255,8 @@ def test_snapAxialMeshToReferenceConservingMassBasedOnBlockShield(self): grid = self.r.core.spatialGrid i, j = grid.getIndicesFromRingAndPos(9, 2) - ################################ - # examine mass change in radial shield - ################################ + # 1. examine mass change in radial shield + a = self.r.core.childrenByLocator[grid[i, j, 0]] # gridplate, axial shield, axial shield, axial shield, plenum b = a[0] @@ -1275,9 +1284,8 @@ def test_snapAxialMeshToReferenceConservingMassBasedOnBlockShield(self): for a in self.r.core.getAssemblies(): a.setBlockMesh(refMesh, conserveMassFlag="auto") - ################################ - # examine mass change in radial shield after expansion - ################################ + # 2. examine mass change in radial shield after expansion + # gridplate, axial shield, axial shield, axial shield, plenum b = a[0] coolantNucs = b.getComponent(Flags.COOLANT).getNuclides() @@ -1315,9 +1323,8 @@ def test_snapAxialMeshToReferenceConservingMassBasedOnBlockShield(self): for a in self.r.core.getAssemblies(): a.setBlockMesh(originalMesh, conserveMassFlag="auto") - ################################ - # examine mass change in radial shield after shrink to original - ################################ + # 3. examine mass change in radial shield after shrink to original + # gridplate, axial shield, axial shield, axial shield, plenum b = a[0] coolantNucs = b.getComponent(Flags.COOLANT).getNuclides() diff --git a/armi/utils/plotting.py b/armi/utils/plotting.py index cdfad1219..b915ece16 100644 --- a/armi/utils/plotting.py +++ b/armi/utils/plotting.py @@ -42,7 +42,6 @@ from armi import runLog from armi.bookkeeping import report from armi.materials import custom -from armi.nuclearDataIO.cccc.rtflux import RtfluxData from armi.reactor import grids from armi.reactor.components import Helix, Circle, DerivedShape from armi.reactor.components.basicShapes import Hexagon, Rectangle, Square @@ -1450,88 +1449,6 @@ def plotBlockDiagram( return os.path.abspath(fName) -def plotTriangleFlux( - rtfluxData: RtfluxData, - axialZ, - energyGroup, - hexPitch=math.sqrt(3.0), - hexSideSubdivisions=1, - imgFileExt=".png", -): - """ - Plot region total flux for one core-wide axial slice on triangular/hexagonal geometry. - - .. warning:: This will run on non-triangular meshes but will look wrong. - - Parameters - ---------- - rtfluxData : RtfluxData object - The RTFLUX/ATFLUX data object containing all read file data. - Alternatively, this could be a FIXSRC file object, - but only if FIXSRC.fixSrc is first renamed FIXSRC.triangleFluxes. - axialZ : int - The DIF3D axial node index of the core-wide slice to plot. - energyGroup : int - The energy group index to plot. - hexPitch: float, optional - The flat-to-flat hexagonal assembly pitch in this core. - By default, it is sqrt(3) so that the triangle edge length is 1 if hexSideSubdivisions=1. - hexSideSubdivisions : int, optional - By default, it is 1 so that the triangle edge length is 1 if hexPitch=sqrt(3). - imgFileExt : str, optional - The image file extension. - - Examples - -------- - >>> rtflux = rtflux.RtfluxStream.readBinary("RTFLUX") - >>> plotTriangleFlux(rtflux, axialZ=10, energyGroup=4) - """ - triHeightInCm = hexPitch / 2.0 / hexSideSubdivisions - sideLengthInCm = triHeightInCm / (math.sqrt(3.0) / 2.0) - s2InCm = sideLengthInCm / 2.0 - - vals = rtfluxData.groupFluxes[:, :, axialZ, energyGroup] - patches = [] - colorVals = [] - for i in range(vals.shape[0]): - for j in range(vals.shape[1]): - # use (i+j)%2 for rectangular meshing - flipped = i % 2 - xInCm = s2InCm * (i - j) - yInCm = triHeightInCm * j + sideLengthInCm / 2.0 / math.sqrt(3) * ( - 1 + flipped - ) - - flux = vals[i][j] - - if flux: - triangle = patches.mpatches.RegularPolygon( - (xInCm, yInCm), - 3, - radius=sideLengthInCm / math.sqrt(3), - orientation=math.pi * flipped, - linewidth=0.0, - ) - - patches.append(triangle) - colorVals.append(flux) - - collection = PatchCollection(patches, alpha=1.0, linewidths=(0,), edgecolors="none") - # add color map to this collection ONLY (pins, not ducts) - collection.set_array(numpy.array(colorVals)) - - plt.figure() - ax = plt.gca() - ax.add_collection(collection) - colbar = plt.colorbar(collection) - colbar.set_label("n/s/cm$^3$") - plt.ylabel("cm") - plt.xlabel("cm") - ax.autoscale_view() - plt.savefig("RTFLUX-z" + str(axialZ + 1) + "-g" + str(energyGroup + 1) + imgFileExt) - plt.close() - - def plotNucXs( isotxs, nucNames, xsNames, fName=None, label=None, noShow=False, title=None ): diff --git a/doc/release/0.3.rst b/doc/release/0.3.rst index 4dd00b665..1e2f85162 100644 --- a/doc/release/0.3.rst +++ b/doc/release/0.3.rst @@ -15,6 +15,7 @@ API Changes #. Renaming ``structuredgrid.py`` to camelCase. (`PR#1650 `_) #. Removing unused argument from ``Block.coords()``. (`PR#1651 `_) #. Removing unused method ``HexGrid.allPositionsInThird()``. (`PR#1655 `_) +#. Removed unused methods: ``Reactor.getAllNuclidesIn()``, ``plotTriangleFlux()``. (`PR#1656 `_) #. TBD Bug Fixes