diff --git a/armi/bookkeeping/report/newReportUtils.py b/armi/bookkeeping/report/newReportUtils.py index 98c14bf92..3750925cc 100644 --- a/armi/bookkeeping/report/newReportUtils.py +++ b/armi/bookkeeping/report/newReportUtils.py @@ -628,9 +628,17 @@ def insertCoreAndAssemblyMaps( generateFullCoreMap : bool, default False showBlockAxMesh : bool, default True """ - assemPrototypes = set() + assemPrototypes = [] for aKey in blueprint.assemDesigns.keys(): - assemPrototypes.add(blueprint.constructAssem(cs, name=aKey)) + a = blueprint.constructAssem(cs, name=aKey) + # since we will be plotting cold input heights, we need to make sure that + # that these new assemblies have access to a blueprints somewhere up the + # composite chain. normally this would happen through an assembly's parent + # reactor, but because these newly created assemblies are in the load queue, + # they will not have a parent reactor. to get around this, we just attach + # the blueprints to the assembly directly. + a.blueprints = blueprint + assemPrototypes.append(a) counts = { assemDesign.name: len(r.core.getChildrenOfType(assemDesign.name)) @@ -648,7 +656,7 @@ def insertCoreAndAssemblyMaps( report[DESIGN]["Assembly Designs"] = newReports.Section("Assembly Designs") currentSection = report[DESIGN]["Assembly Designs"] for plotNum, assemBatch in enumerate( - iterables.chunk(list(assemPrototypes), MAX_ASSEMS_PER_ASSEM_PLOT), start=1 + iterables.chunk(assemPrototypes, MAX_ASSEMS_PER_ASSEM_PLOT), start=1 ): assemPlotImage = newReports.Image( imageCaption, @@ -656,11 +664,11 @@ def insertCoreAndAssemblyMaps( ) assemPlotName = os.path.abspath(f"{core.name}AssemblyTypes{plotNum}.png") plotting.plotAssemblyTypes( - blueprint, - assemPlotName, assemBatch, + assemPlotName, maxAssems=MAX_ASSEMS_PER_ASSEM_PLOT, showBlockAxMesh=showBlockAxMesh, + hot=False, ) currentSection.addChildElement(assemPlotImage, assemPlotName) diff --git a/armi/bookkeeping/report/reportingUtils.py b/armi/bookkeeping/report/reportingUtils.py index 9ac13287c..1a58cc14e 100644 --- a/armi/bookkeeping/report/reportingUtils.py +++ b/armi/bookkeeping/report/reportingUtils.py @@ -1062,10 +1062,22 @@ def makeCoreAndAssemblyMaps(r, cs, generateFullCoreMap=False, showBlockAxMesh=Tr generateFullCoreMap : bool, default False showBlockAxMesh : bool, default True """ - assemsInCore = list(r.blueprints.assemblies.values()) + assems = [] + blueprints = r.blueprints + for aKey in blueprints.assemDesigns.keys(): + a = blueprints.constructAssem(cs, name=aKey) + # since we will be plotting cold input heights, we need to make sure that + # that these new assemblies have access to a blueprints somewhere up the + # composite chain. normally this would happen through an assembly's parent + # reactor, but because these newly created assemblies are in the load queue, + # they will not have a parent reactor. to get around this, we just attach + # the blueprints to the assembly directly. + a.blueprints = blueprints + assems.append(a) + core = r.core for plotNum, assemBatch in enumerate( - iterables.chunk(assemsInCore, MAX_ASSEMS_PER_ASSEM_PLOT), start=1 + iterables.chunk(assems, MAX_ASSEMS_PER_ASSEM_PLOT), start=1 ): assemPlotImage = copy(report.ASSEM_TYPES) assemPlotImage.title = assemPlotImage.title + " ({})".format(plotNum) @@ -1073,11 +1085,11 @@ def makeCoreAndAssemblyMaps(r, cs, generateFullCoreMap=False, showBlockAxMesh=Tr report.data.Report.componentWellGroups.insert(-1, assemPlotImage) assemPlotName = os.path.abspath(f"{core.name}AssemblyTypes{plotNum}.png") plotting.plotAssemblyTypes( - core.parent.blueprints, - assemPlotName, assemBatch, + assemPlotName, maxAssems=MAX_ASSEMS_PER_ASSEM_PLOT, showBlockAxMesh=showBlockAxMesh, + hot=False, ) # Create radial core map diff --git a/armi/reactor/converters/uniformMesh.py b/armi/reactor/converters/uniformMesh.py index 8bb949771..234d1a082 100644 --- a/armi/reactor/converters/uniformMesh.py +++ b/armi/reactor/converters/uniformMesh.py @@ -984,9 +984,8 @@ def plotConvertedReactor(self): ): assemPlotName = f"{self.convReactor.core.name}AssemblyTypes{plotNum}-rank{armi.MPI_RANK}.png" plotting.plotAssemblyTypes( - self.convReactor.blueprints, - assemPlotName, assemBatch, + assemPlotName, maxAssems=6, showBlockAxMesh=True, ) diff --git a/armi/utils/plotting.py b/armi/utils/plotting.py index 147e1c20c..84d47c5e4 100644 --- a/armi/utils/plotting.py +++ b/armi/utils/plotting.py @@ -706,28 +706,25 @@ def updatePageDepthColor(self, newVal): def plotAssemblyTypes( - blueprints=None, - fileName=None, assems=None, + fileName=None, maxAssems=None, showBlockAxMesh=True, yAxisLabel=None, title=None, + hot=True, ) -> plt.Figure: """ Generate a plot showing the axial block and enrichment distributions of each assembly type in the core. Parameters ---------- - blueprints: Blueprints - The blueprints to plot assembly types of. (Either this or ``assems`` must be non-None.) + assems: list + list of assembly objects to be plotted. fileName : str or None Base for filename to write, or None for just returning the fig - assems: list - list of assembly objects to be plotted. (Either this or ``blueprints`` must be non-None.) - maxAssems: integer maximum number of assemblies to plot in the assems list. @@ -740,24 +737,14 @@ def plotAssemblyTypes( title: str Optionally, provide a title for the plot. + hot : bool, optional + If True, plot the hot block heights. If False, use cold heights from the inputs. + Returns ------- fig : plt.Figure The figure object created """ - # input validation - if assems is None and blueprints is None: - raise ValueError( - "At least one of these inputs must be non-None: blueprints, assems" - ) - - # handle defaults - if assems is None: - assems = list(blueprints.assemblies.values()) - - if not isinstance(assems, (list, set, tuple)): - assems = [assems] - if maxAssems is not None and not isinstance(maxAssems, int): raise TypeError("Maximum assemblies should be an integer") @@ -792,6 +779,7 @@ def plotAssemblyTypes( xAssemLoc, xAssemEndLoc, showBlockAxMesh, + hot, ) xAxisLabel = re.sub(" ", "\n", assem.getType().upper()) ax.text( @@ -840,6 +828,7 @@ def _plotBlocksInAssembly( xAssemLoc, xAssemEndLoc, showBlockAxMesh, + hot, ): # Set dictionary of pre-defined block types and colors for the plot lightsage = "xkcd:light sage" @@ -865,11 +854,18 @@ def _plotBlocksInAssembly( xTextLoc = xBlockLoc + blockWidth / 20.0 for b in assem: # get block height - try: - blockHeight = b.getInputHeight() - except AttributeError: - runLog.debug(f"No ancestor of {b} has blueprints", single=True) + if hot: blockHeight = b.getHeight() + else: + try: + blockHeight = b.getInputHeight() + except AttributeError: + raise ValueError( + f"Cannot plot cold height for block {b} in assembly {assem} " + "because it does not have access to a blueprints through any " + "of its parents. Either make sure that a blueprints is accessible " + " or plot the hot heights instead." + ) # Get the basic text label for the block try: diff --git a/armi/utils/tests/test_plotting.py b/armi/utils/tests/test_plotting.py index 2c930b91e..90e878993 100644 --- a/armi/utils/tests/test_plotting.py +++ b/armi/utils/tests/test_plotting.py @@ -60,7 +60,9 @@ def test_plotDepthMap(self): # indirectly tests plot face map def test_plotAssemblyTypes(self): with TemporaryDirectoryChanger(): plotPath = "coreAssemblyTypes1.png" - plotting.plotAssemblyTypes(self.r.core.parent.blueprints, plotPath) + plotting.plotAssemblyTypes( + list(self.r.core.parent.blueprints.assemblies.values()), plotPath + ) self._checkFileExists(plotPath) if os.path.exists(plotPath): @@ -68,7 +70,7 @@ def test_plotAssemblyTypes(self): plotPath = "coreAssemblyTypes2.png" plotting.plotAssemblyTypes( - self.r.core.parent.blueprints, + list(self.r.core.parent.blueprints.assemblies.values()), plotPath, yAxisLabel="y axis", title="title", @@ -78,9 +80,6 @@ def test_plotAssemblyTypes(self): if os.path.exists(plotPath): os.remove(plotPath) - with self.assertRaises(ValueError): - plotting.plotAssemblyTypes(None, plotPath, None) - if os.path.exists(plotPath): os.remove(plotPath)