From 0a73f09409630f5915b3df3eddceb5c0e48977be Mon Sep 17 00:00:00 2001 From: jakehader Date: Mon, 12 Dec 2022 18:13:26 -0800 Subject: [PATCH 01/11] Updates to the fission product model and nuclides to allow for users to model fission products explicitly based on which cross section libraries/software will be used to generate multi-group cross sections. This implements a method that looks at the existing nuclides defined in the blueprints `nuclideFlags` and amends this to include all nuclides within regions that are determined to be `Depletable` based on which nuclides the user sets as being `burn=True`. This will not check that the user defines the correct depletable nuclides for this determination, but will only add all the nuclides that are missing to the depletable regions. This is reasonable since the behavior is similar to how it is when the user selects another fission product model option. This change also introduces a mechanism to remove gaseous fission products from the core that can be called upon by a fuel performance or other plugin code. The previous implementation of this modified the fission yields as a surrogate for this removal, but upon further review it is better to update the number densities within the blocks directly compared to modifying the fission yields. By modifying the number densities directly we are explicitly handling both the microscope and macroscopic cross section effects in lattice physics calculations and in system eigenvalue/flux solutions. Finally, the fuel performance parameters were updated to add setters on the `gasReleaseFraction` and the `bondRemoved`. The description suggested that these values should only range from [0, 1] and any value outside of this range is not valid. The setters now check for this and raise a ValueError if these conditions are not met. --- armi/physics/fuelPerformance/parameters.py | 24 +- .../neutronics/crossSectionGroupManager.py | 20 +- .../fissionProductModel.py | 209 ++- .../fissionProductModelSettings.py | 24 +- .../lumpedFissionProduct.py | 343 +--- .../tests/test_fissionProductModel.py | 6 +- .../tests/test_lumpedFissionProduct.py | 102 +- .../globalFlux/globalFluxInterface.py | 2 +- .../latticePhysics/latticePhysicsWriter.py | 16 +- armi/reactor/blueprints/__init__.py | 33 +- armi/resources/referenceFissionProducts.dat | 1607 ++++++++--------- 11 files changed, 1080 insertions(+), 1306 deletions(-) diff --git a/armi/physics/fuelPerformance/parameters.py b/armi/physics/fuelPerformance/parameters.py index 60438be4a..4d0638ab2 100644 --- a/armi/physics/fuelPerformance/parameters.py +++ b/armi/physics/fuelPerformance/parameters.py @@ -27,19 +27,35 @@ def _getFuelPerformanceBlockParams(): pDefs = parameters.ParameterDefinitionCollection() with pDefs.createBuilder(default=0.0, location=ParamLocation.AVERAGE) as pb: + def gasReleaseFraction(self, value): + if value < 0.0 or value > 1.0: + raise ValueError( + f"Cannot set a gas release fraction " + f"of {value} outside of the bounds of [0.0, 1.0]" + ) + self._p_gasReleaseFraction = value + pb.defParam( "gasReleaseFraction", + setter=gasReleaseFraction, units="fraction", - description="Fraction of generated fission gas that no longer exists in the block." - " Should be between 0 and 1, inclusive.", + description="Fraction of generated fission gas that no longer exists in the block", categories=["eq cumulative shift"], ) + def bondRemoved(self, value): + if value < 0.0 or value > 1.0: + raise ValueError( + f"Cannot set a bond removed " + f"of {value} outside of the bounds of [0.0, 1.0]" + ) + self._p_bondRemoved = value + pb.defParam( "bondRemoved", + setter=bondRemoved, units="fraction", - description="Fraction of thermal bond between fuel and clad that has been pushed out. " - "Should be between 0 and 1, inclusive.", + description="Fraction of thermal bond between fuel and clad that has been pushed out.", categories=["eq cumulative shift"], ) diff --git a/armi/physics/neutronics/crossSectionGroupManager.py b/armi/physics/neutronics/crossSectionGroupManager.py index fedda6c07..85c64e490 100644 --- a/armi/physics/neutronics/crossSectionGroupManager.py +++ b/armi/physics/neutronics/crossSectionGroupManager.py @@ -346,15 +346,7 @@ def _getAverageFuelLFP(self): """Compute the average lumped fission products.""" # TODO: make do actual average of LFPs b = self.getCandidateBlocks()[0] - lfpCollection = b.getLumpedFissionProductCollection() - if lfpCollection: - lfpCollectionCopy = lfpCollection.duplicate() - fgRemoved = self._getAverageFissionGasRemoved() - lfpCollectionCopy.setGasRemovedFrac(fgRemoved) - else: - lfpCollectionCopy = lfpCollection - - return lfpCollectionCopy + return b.getLumpedFissionProductCollection() def _getNucTempHelper(self): """All candidate blocks are used in the average.""" @@ -1012,15 +1004,9 @@ def _summarizeGroups(self, blockCollectionsByXsGroup): xsIDGroup = self._getXsIDGroup(xsID) if xsIDGroup == self._REPR_GROUP: reprBlock = self.representativeBlocks.get(xsID) - lfps = reprBlock.getLumpedFissionProductCollection() - if lfps: - fissionGasRemoved = list(lfps.values())[0].getGasRemovedFrac() - else: - fissionGasRemoved = 0.0 runLog.extra( - "XS ID {} contains {:4d} blocks, represented by: {:65s}" - " Fission Gas Removal Fraction: {:.2f}".format( - xsID, len(blocks), reprBlock, fissionGasRemoved + "XS ID {} contains {:4d} blocks, represented by: {:65s}".format( + xsID, len(blocks), reprBlock ) ) elif xsIDGroup == self._NON_REPR_GROUP: diff --git a/armi/physics/neutronics/fissionProductModel/fissionProductModel.py b/armi/physics/neutronics/fissionProductModel/fissionProductModel.py index 877ef137e..5753dda07 100644 --- a/armi/physics/neutronics/fissionProductModel/fissionProductModel.py +++ b/armi/physics/neutronics/fissionProductModel/fissionProductModel.py @@ -42,6 +42,7 @@ from armi import runLog from armi import interfaces from armi.reactor.flags import Flags +from armi.nucDirectory import nuclideBases from armi.physics.neutronics.fissionProductModel import lumpedFissionProduct NUM_FISSION_PRODUCTS_PER_LFP = 2.0 @@ -64,13 +65,20 @@ class FissionProductModel(interfaces.Interface): def __init__(self, r, cs): interfaces.Interface.__init__(self, r, cs) - self._globalLFPs = None self._globalLFPs = lumpedFissionProduct.lumpedFissionProductFactory(self.cs) - self.fissionProductNames = [] + # Boolean that tracks if `setAllBlockLFPs` was called previously. + self._initialized = False + + @property + def _explicitFissionProducts(self): + return self.cs["fpModel"] == "explicitFissionProducts" @property def _useGlobalLFPs(self): - return False if self.cs["makeAllBlockLFPsIndependent"] else True + if self.cs["makeAllBlockLFPsIndependent"] or self._explicitFissionProducts: + return False + else: + return True @property def _fissionProductBlockType(self): @@ -85,64 +93,49 @@ def _fissionProductBlockType(self): not generated when the block is depleted. """ blockType = None if self.getInterface("mcnp") is not None else Flags.FUEL + if blockType is None and self._explicitFissionProducts: + raise ValueError( + f"Expicit fission products model is not compatible with the MCNP interface." + ) return blockType def interactBOL(self): interfaces.Interface.interactBOL(self) self.setAllBlockLFPs() - def setAllBlockLFPs(self, blockType=None, setMaterialsLFP=False): + def setAllBlockLFPs(self): """ - Set ALL the block _lumpedFissionProduct attributes - - Can set them to global or turns on independent block-level LFPs if - requested - - sets block._lumpedFissionProducts to something other than the global. - - Parameters - ---------- - blockType : Flags, optional - this is the type of block that the global lumped fission product is - being applied to. If this is not provided it will get the default - behavior from ``self._fissionProductBlockType``. - - setMaterialsLFP : bool, optional - this is a flag to tell the method whether or not to try to apply - the global lumped fission product to the component and thereby - material -- this is only compatable with - LumpedFissionProductCompatableMaterial - - Examples - -------- - self.setAllBlockLFPs(blockType='fuel') - will apply the global lumped fission product or independent LFPs to only - fuel type blocks - + Sets all the block lumped fission products attributes and adds fission products + to each block if `self._explicitFissionProducts` is set to True. See Also -------- - armi.materials.lumpedFissionProductCompatableMaterial.LumpedFissionProductCompatableMaterial armi.reactor.components.Component.setLumpedFissionProducts - armi.physics.neutronics.fissionProductModel.fissionProductModel.FissionProductModel.setAllBlockLFPs """ - blockType = blockType or self._fissionProductBlockType - for b in self.r.core.getBlocks(blockType, includeAll=True): + for b in self.r.core.getBlocks(self._fissionProductBlockType, includeAll=True): if self._useGlobalLFPs: b.setLumpedFissionProducts(self.getGlobalLumpedFissionProducts()) else: - independentLFPs = self.getGlobalLumpedFissionProducts().duplicate() - b.setLumpedFissionProducts(independentLFPs) - - # use two loops to pass setting the material LFPs - if setMaterialsLFP: - for b in self.r.core.getBlocks(blockType, includeAll=True): - if self._useGlobalLFPs: - b.setChildrenLumpedFissionProducts( - self.getGlobalLumpedFissionProducts() - ) + lfps = self.getGlobalLumpedFissionProducts() + if lfps is None: + b.setLumpedFissionProducts(None) else: independentLFPs = self.getGlobalLumpedFissionProducts().duplicate() - b.setChildrenLumpedFissionProducts(independentLFPs) + b.setLumpedFissionProducts(independentLFPs) + + # Initialize the fission products explicitly on the block component + # that matches the `self._fissionProductBlockType` if it exists. + if self._explicitFissionProducts and not self._initialized: + targetComponent = b.getComponent(self._fissionProductBlockType) + if not targetComponent: + continue + ndens = targetComponent.getNumberDensities() + updatedNDens = {} + for nuc in self.r.blueprints.allNuclidesInProblem: + if nuc in ndens: + continue + updatedNDens[nuc] = 0.0 + targetComponent.updateNumberDensities(updatedNDens) + self._initialized = True def getGlobalLumpedFissionProducts(self): r""" @@ -180,7 +173,7 @@ def interactEveryNode(self, _cycle, _node): def interactDistributeState(self): self.setAllBlockLFPs() - def _getAllFissionProductNames(self): + def getAllFissionProductNames(self): """ Find all fission product names in the problem @@ -205,60 +198,84 @@ def _getAllFissionProductNames(self): if fpName not in fissionProductNames: fissionProductNames.append(fpName) - self.fissionProductNames = fissionProductNames - - def _cacheLFPDensities(self, blockList): - # pass 2: Cache all LFP densities for all blocks (so they aren't read - # for each FP) - runLog.debug(" Caching LFP densities of all blocks") - lfpDensities = {} - for b in blockList: - for lfpName in self._globalLFPs: - lfpDensities[lfpName, b.getName()] = b.getNumberDensity(lfpName) - return lfpDensities + return fissionProductNames - def updateFissionGasRemovalFractions(self): + def removeFissionGasesFromBlocks(self, gasRemovalFractions: dict): """ - Synchronize fission gas removal fractions of all LFP objects with the reactor state. + Removes the fission gases from each of the blocks in the core. - The block parameter ``fgRemoval`` is adjusted by fuel performance - modules and is applied here. - - If the ``makeAllBlockLFPsIndependent`` setting is not activated - (default), the global lump gets the flux-weighted average of all blocks. - Otherwise, each block gets its own fission gas release fraction applied - to its individual LFPs. For MCNP restart cases, it is recommended to - activate the ``makeAllBlockLFPsIndependent`` setting. + Parameters + ---------- + gasRemovalFractions : dict + Dictionary with block objects as the keys and the fraction + of gaseous fission products to remove. Notes ----- - The CrossSectionGroupManager does this for each XSG individually for - lattice physics, but it's important to keep these up to date as well for - anything else that may be interested in fission product information - (e.g. MCNP). - - See Also - -------- - armi.physics.neutronics.crossSectionGroupManager.AverageBlockCollection.getRepresentativeBlock - + The current implementation will update the number density vector + of each of the blocks and the gaseous fission products are not + moved or displaced to another area in the core. """ - runLog.extra("Updating lumped fission product gas removal fractions") - avgGasReleased = 0.0 - totalWeight = 0.0 - for block in self.r.core.getBlocks(Flags.FUEL): - if self._useGlobalLFPs: - # add to average for globals - weight = block.getVolume() * (block.p.flux or 1.0) - avgGasReleased += block.p.gasReleaseFraction * weight - totalWeight += weight - else: - # set individually - block.getLumpedFissionProductCollection().setGasRemovedFrac( - block.p.gasReleaseFraction - ) - - # adjust global lumps if they exist. - if avgGasReleased: - self.getGlobalLumpedFissionProducts().setGasRemovedFrac( - avgGasReleased / totalWeight - ) + + def _getGaseousFissionProductNumberDensities(block, lfp): + """Look into a single lumped fission product object and pull out just the gaseous atom number densities.""" + numberDensities = {} + for nuc in lfp.keys(): + nb = nuclideBases.byName[nuc] + if not lumpedFissionProduct.isGas(nb): + continue + yld = lfp[nuc] + block.getNumberDensity(lfp.name) + numberDensities[nuc] = + + + runLog.info(f"Removing the gaseous fission products from the core.") + if not isinstance(gasRemovalFractions, dict): + raise TypeError(f"The gas removal fractions input is not a dictionary.") + + if self._explicitFissionProducts: + for b in self.r.core.getBlocks(): + if b not in gasRemovalFractions: + continue + updatedNumberDensities = {} + removedFraction = gasRemovalFractions[b] + remainingFraction = 1.0 - removedFraction + for nuc, val in b.getNumberDensities().items(): + nb = nuclideBases.byName[nuc] + if lumpedFissionProduct.isGas(nb): + val = remainingFraction * val + updatedNumberDensities[nuc] = val + b.updateNumberDensities(updatedNumberDensities) + + else: + avgGasReleased = 0.0 + totalWeight = 0.0 + for block in self.r.core.getBlocks(): + lfpCollection = block.getLumpedFissionProductCollection() + # Skip this block if there is no lumped fission product + # collection or this block is not in the dictionary + # of gas removal fractions. + if lfpCollection is None or b not in gasRemovalFractions: + continue + + # If the lumped fission products are global then we are going + # release the average across all the blocks in the core and these + # this data is collected iteratively. + if self._useGlobalLFPs: + weight = block.getVolume() * (block.p.flux or 1.0) + avgGasReleased += block.p.gasReleaseFraction * weight + totalWeight += weight + + # Otherwise, if the lumped fission products are not global + # go ahead of make the change now. + else: + # set individually + block.getLumpedFissionProductCollection().setGasRemovedFrac( + block.p.gasReleaseFraction + ) + + # adjust global lumps if they exist. + if self._useGlobalLFPs and totalWeight: + self.getGlobalLumpedFissionProducts().setGasRemovedFrac( + avgGasReleased / totalWeight + ) \ No newline at end of file diff --git a/armi/physics/neutronics/fissionProductModel/fissionProductModelSettings.py b/armi/physics/neutronics/fissionProductModel/fissionProductModelSettings.py index ce3bbdf10..2b2a614f4 100644 --- a/armi/physics/neutronics/fissionProductModel/fissionProductModelSettings.py +++ b/armi/physics/neutronics/fissionProductModel/fissionProductModelSettings.py @@ -20,6 +20,7 @@ CONF_FP_MODEL = "fpModel" CONF_MAKE_ALL_BLOCK_LFPS_INDEPENDENT = "makeAllBlockLFPsIndependent" CONF_LFP_COMPOSITION_FILE_PATH = "lfpCompositionFilePath" +CONF_FISSION_PRODUCT_LIBRARY_NAME = "fpModelLibrary" def defineSettings(): @@ -28,13 +29,30 @@ def defineSettings(): CONF_FP_MODEL, default="infinitelyDilute", label="Fission Product Model", - description="The fission product model to use in this ARMI run", + description="", options=[ "noFissionProducts", "infinitelyDilute", - "2ndOrder", - "2ndOrderWithTransmutation", "MO99", + "explicitFissionProducts", + ], + ), + setting.Setting( + CONF_FISSION_PRODUCT_LIBRARY_NAME, + default="MC2-3", + label="Fission Product Library", + description=( + f"This setting is used when the `{CONF_FP_MODEL}` setting " + f"is set to `explicitFissionProducts` and is used to configure " + f"all the nuclides that should be modeled within the core. " + f"Setting this is equivalent to adding all nuclides in the " + f"selected code library (i.e., MC2-3) within the blueprints " + f"`nuclideFlags` to be [xs:true, burn:false]. This option acts " + f"as a short-cut so that analysts do not need to change their " + f"inputs when modifying the fission product treatment for calculations." + ), + options=[ + "MC2-3", ], ), setting.Setting( diff --git a/armi/physics/neutronics/fissionProductModel/lumpedFissionProduct.py b/armi/physics/neutronics/fissionProductModel/lumpedFissionProduct.py index d69a0aa93..06591a470 100644 --- a/armi/physics/neutronics/fissionProductModel/lumpedFissionProduct.py +++ b/armi/physics/neutronics/fissionProductModel/lumpedFissionProduct.py @@ -21,14 +21,12 @@ """ -import re +import os -from armi.utils.textProcessors import SCIENTIFIC_PATTERN from armi.nucDirectory import nuclideBases from armi import runLog -from armi.utils import densityTools -from armi.nucDirectory.elements import LANTHANIDE_ELEMENTS, GASEOUS_ELEMENTS +from armi.nucDirectory import elements from .fissionProductModelSettings import CONF_LFP_COMPOSITION_FILE_PATH @@ -70,21 +68,19 @@ def __init__(self, name=None): """ self.name = name self.yld = {} - self.gasRemainingFrac = 1.0 def duplicate(self): """ Make a copy of this w/o using deepcopy """ new = self.__class__(self.name) - new.gasRemainingFrac = self.gasRemainingFrac for key, val in self.yld.items(): new.yld[key] = val return new def __getitem__(self, fissionProduct, default=None): - r""" - Return the FP yield of a particular FP + """ + Return the yield of a particular fission product. This allows the LFP to be accessed via indexing, like this: ``lfp[fp]`` @@ -93,16 +89,9 @@ def __getitem__(self, fissionProduct, default=None): yld : yield of the fission product. Defaults to None. """ yld = self.yld.get(fissionProduct, default) - if yld and isGas(fissionProduct): - yld *= self.gasRemainingFrac return yld def __setitem__(self, key, val): - if self.gasRemainingFrac != 1.0 and isGas(key): - raise RuntimeError( - "Cannot set {0} yield on {1} when gas frac is {2}" - "".format(key, self, self.gasRemainingFrac) - ) self.yld[key] = val def __contains__(self, item): @@ -118,23 +107,11 @@ def values(self): return self.yld.values() def items(self): - """ - make sure gas fraction gets applied - """ for nuc in self.keys(): yield nuc, self[nuc] - def setGasRemovedFrac(self, removedFrac): - """ - Set the fraction of total fission gas that is removed from this LFP. - """ - self.gasRemainingFrac = 1.0 - removedFrac - - def getGasRemovedFrac(self): - return 1.0 - self.gasRemainingFrac - def getTotalYield(self): - r""" + """ Get the fractional yield of all nuclides in this lumped fission product Accounts for any fission gas that may be removed. @@ -160,22 +137,6 @@ def getMassFracs(self): massFracs[nuc] = self.getMassFrac(nuclideBase=nuc) return massFracs - def getNumberFracs(self): - """ - Return a dictionary of number fractions indexed by nuclide. - - Returns - ------- - numberFracs : dict - number fractions (floats) of fission products indexed by nuclide. - """ - - numberFracs = {} - totalNumber = sum(self.yld.values()) - for nuc, yld in self.yld.items(): - numberFracs[nuc] = yld / totalNumber - return numberFracs - def getMassFrac( self, nucName=None, nuclideBase=None, useCache=True, storeCache=True ): @@ -224,39 +185,6 @@ def getExpandedMass(self, mass=1.0): return massVector - def getGasFraction(self): - r""" - get the fraction of gas that is from Xe and Kr gas - - Returns - ------- - gasFrac : float - Fraction of LFP that is gaseous - """ - totalGas = 0 - - # sum up all of the nuclides that are XE or KR - for nuc, val in self.items(): - if isGas(nuc): - totalGas += val - - # normalize the total gas released by the total yield fraction and return - return totalGas / self.getTotalYield() - - def getLanthanideFraction(self): - """Return the fraction of fission products that are lanthanides.""" - - totalLanthanides = 0 - - # sum up all of the nuclides that are XE or KR - for nuc, val in self.items(): - for element in LANTHANIDE_ELEMENTS: - if element in nuc.name: - totalLanthanides += val - - # normalize the total gas released by the total yield fraction and return - return totalLanthanides / self.getTotalYield() - def printDensities(self, lfpDens): """Print densities of nuclides given a LFP density.""" for n in sorted(self.keys()): @@ -293,11 +221,11 @@ def getAllFissionProductNames(self): def getAllFissionProductNuclideBases(self): """Gets names of all fission products in this collection""" - clideBases = set() + nucs = set() for _lfpName, lfp in self.items(): for fp in lfp.keys(): - clideBases.add(fp) - return sorted(clideBases) + nucs.add(fp) + return sorted(nucs) def getNumberDensities(self, objectWithParentDensities=None, densFunc=None): """ @@ -345,133 +273,17 @@ def getMassFrac(self, oldMassFrac=None): return massFrac - def setGasRemovedFrac(self, removedFrac): - """ - Set the fraction of total fission gas that is removed from all LFPs. - """ - for lfp in self.values(): - lfp.setGasRemovedFrac(removedFrac) - - def getGasRemovedFrac(self): - """ - Get the fraction of total fission gas that is removed from all LFPs. - """ - lastVal = -1 - for lfp in self.values(): - myVal = lfp.getGasRemovedFrac() - if lastVal not in (-1, lastVal): - raise RuntimeError( - "Fission gas release fracs in {0} are decoupled" "".format(self) - ) - lastVal = myVal - return lastVal - - -class SingleLumpedFissionProductCollection(LumpedFissionProductCollection): - """ - This is a subclass of LumpedFissionProductCollection to be used when you - want to collapse all the fission products into a single lumped fission product - - There were numerous checks in places to ensure that a collection of - fission products only had 1 lfp and this object consolidates them. - - Notes - ----- - armi.physics.neutronics.fissionProductModel.lumpedFissionProduct.FissionProductDefinitionFile.createSingleLFPFromFile - is a factory for these - - """ - - def __init__(self): - super(SingleLumpedFissionProductCollection, self).__init__() - self.collapsible = True - - def getFirstLfp(self): - return list(self.values())[0] - - def getName(self): - return list(self.keys())[0] - - def updateYieldVector(self, numberDensities=None, massFrac=None, fpFiltered=False): - """update the yield values on the single lumped fission product""" - if massFrac is not None: - numberDensities = densityTools.getNDensFromMasses(1, massFrac) - - if numberDensities is None: - raise ValueError( - "massFrac -- {} -- or numberDensities -- {} -- must be populated".format( - massFrac, numberDensities - ) - ) - self._updateYieldVectorFromNumberDensities( - numberDensities, fpFiltered=fpFiltered - ) - - def _updateYieldVectorFromNumberDensities(self, numberDensities, fpFiltered=False): - """ - This method updates the yield distribution of the first lfp to reflect - whatever is on the massFrac vector - - Parameters - ---------- - numberDensities : dict - This is a .p.massFrac format mass fraction vector indexed by - nuclide name - - fpFiltered : bool - This is a flag to let this method know whether it needs to filter - the mass fraction vector for fission products - - """ - lfp = self.getFirstLfp() - lfpNumberFrac = lfp.getNumberFracs() - - if fpFiltered: - fpNumberDensities = numberDensities - else: - fpNumberDensities = {} - # filter massFracs for only fission products - lfpNumberDensity = numberDensities.get(self.getName(), 0) - for nucName in sorted(self.getAllFissionProductNames()): - nb = nuclideBases.byName[nucName] - fpNumberDensities[nb] = numberDensities.get( - nucName, 0 - ) + lfpNumberDensity * lfpNumberFrac.get(nb, 0) - - totalFPNumberDensity = sum(fpNumberDensities.values()) - if totalFPNumberDensity: - # check to see that you want to update the yields AND that there is - # a distribution of fission products -- at BOL this has a zero - # division bc there are no fission products. - for nb in lfp.keys(): - lfp[nb] = ( - fpNumberDensities.get(nb, 0) / totalFPNumberDensity - ) * 2.0 # part of ARMI task T331 - else: - runLog.debug( - "fpMassFrac vector should be populated -- not updating the yield vector" - ) - # update the weight on the nuclide base object - # This is a GLOBAL operation, which is a bit problematic if it - # is being changed and should be upgraded accordingly. - nb = nuclideBases.byName[lfp.name] - nb.weight = ( - 2 - * sum([yld * nb.weight for nb, yld in lfp.yld.items()]) - / sum([yld for yld in self.getFirstLfp().values()]) - ) - class FissionProductDefinitionFile: """ Reads a file that has definitions of one or more LFPs in it to produce LFPs - The format for this file is effectively input lines from a MC2-2 file:: + The format for this file is as follows:: - 13 LFP35 GE73 5 5.9000E-06 - 13 LFP35 GE74 5 1.4000E-05 - 13 LFP35 GE76 5 1.6000E-04 - 13 LFP35 AS75 5 8.9000E-05 + LFP35 GE73 5.9000E-06 + LFP35 GE74 1.4000E-05 + LFP35 GE76 1.6000E-04 + LFP35 AS75 8.9000E-05 and so on @@ -483,10 +295,6 @@ class FissionProductDefinitionFile: The path to this file name is specified by the """ - fpPat = re.compile( - r"13\s+([A-Z]+\d+)[_]{0,1}[0-9]{0,1}\s+(......)\s+(" + SCIENTIFIC_PATTERN + ")" - ) - def __init__(self, stream): self.stream = stream @@ -513,16 +321,6 @@ def createSingleLFPFromFile(self, name): lfp = self._readOneLFP(lfpLines[0]) # only one LFP expected. Use it. return lfp - def createSingleLFPCollectionFromFile(self, name): - """ - Creates a LFPCollection with only one LFP from the file - """ - lfps = SingleLumpedFissionProductCollection() - lfpLines = self._splitIntoIndividualLFPLines(name) - lfp = self._readOneLFP(lfpLines[0]) # only one LFP expected. Use it. - lfps[lfp.name] = lfp - return lfps - def _splitIntoIndividualLFPLines(self, lfpName=None): """ The lfp file can contain one or more LFPs. This splits them. @@ -544,7 +342,7 @@ def _splitIntoIndividualLFPLines(self, lfpName=None): thisLFPLines = [] lastName = None for line in lines: - name = line.split()[1] + name = line.split()[0] if "DUMP" in name or (lfpName and lfpName not in name): continue if lastName and name != lastName: @@ -562,17 +360,11 @@ def _readOneLFP(self, linesOfOneLFP): lfp = LumpedFissionProduct() totalYield = 0.0 for line in linesOfOneLFP: - match = re.search(self.fpPat, line) - if not match: - raise ValueError( - "Invalid LFP data file {0}. Line is invalid:\n{1}".format( - self.fName, line - ) - ) - parent = match.group(1) - nucLibId = match.group(2) - nuc = nuclideBases.byMccId[nucLibId] - yld = float(match.group(3)) + data = line.split() + parent = data[0] + nucLibId = data[1] + nuc = nuclideBases.byName[nucLibId] + yld = float(data[2]) lfp.yld[nuc] = yld totalYield += yld @@ -581,12 +373,14 @@ def _readOneLFP(self, linesOfOneLFP): "Loaded {0} {1} nuclides for a total yield of {2}" "".format(len(lfp.yld), lfp.name, totalYield) ) - return lfp def lumpedFissionProductFactory(cs): """Build lumped fission products.""" + if cs["fpModel"] == "explicitFissionProducts": + return None + if cs["fpModel"] == "MO99": runLog.warning( "Activating MO99-fission product model. All FPs are treated a MO99!" @@ -594,8 +388,12 @@ def lumpedFissionProductFactory(cs): return _buildMo99LumpedFissionProduct() lfpPath = cs[CONF_LFP_COMPOSITION_FILE_PATH] - if not lfpPath: - return None + if not lfpPath or not os.path.exists(lfpPath): + raise ValueError( + f"The fission product reference file does " + f"not exist or is not a valid path. Path provided: {lfpPath}" + ) + runLog.extra(f"Loading global lumped fission products (LFPs) from {lfpPath}") with open(lfpPath) as lfpStream: lfpFile = FissionProductDefinitionFile(lfpStream) @@ -603,6 +401,39 @@ def lumpedFissionProductFactory(cs): return lfps +def _buildExplictFissionProducts(cs, modeledNuclides): + """ + Build a LFP collection that is a single fission product for each + additional nuclide not already initialized in the nuclide flags. + """ + lfpCollections = LumpedFissionProductCollection() + allNuclideBases = getAllNuclideBasesByLibrary(cs) + modeledNuclideBases = [nuclideBases.byName[nuc] for nuc in modeledNuclides] + fissionProductNuclideBases = set(allNuclideBases).difference( + set(modeledNuclideBases) + ) + for nb in fissionProductNuclideBases: + addedFissionProduct = LumpedFissionProduct(nb.name) + addedFissionProduct[nb] = 1.0 + lfpCollections[nb.name] = addedFissionProduct + return lfpCollections + + +def getAllNuclideBasesByLibrary(cs): + """Return a list of nuclide bases that are available for a given `fpModelLibrary`.""" + nbs = [] + if cs["fpModel"] == "explicitFissionProducts": + if cs["fpModelLibrary"] == "MC2-3": + nbs = nuclideBases.byMcc3Id.values() + else: + raise ValueError( + f"An option to handle the `fpModelLibrary` " + f"set to {cs['fpModelLibrary']} has not been " + f"implemented." + ) + return nbs + + def _buildMo99LumpedFissionProduct(): """ Build a dummy MO-99 LFP collection. @@ -615,8 +446,8 @@ def _buildMo99LumpedFissionProduct(): for lfp in nuclideBases.where( lambda nb: isinstance(nb, nuclideBases.LumpNuclideBase) ): - # Not all lump nuclide bases defined are fission products, so ensure that only fission products are considered. - if not "FP" in lfp.name: + # Not all lump nuclides bases defined are fission products, so ensure that only fission products are considered. + if not ("FP" in lfp.name or "REGN" in lfp.name): continue mo99FP = LumpedFissionProduct(lfp.name) mo99FP[mo99] = 2.0 @@ -677,53 +508,9 @@ def expandFissionProducts(massFrac, lumpedFissionProducts): return newMassFrac -def collapseFissionProducts( - massFracs, lumpedFissionProducts, updateLumpedFissionProduct=False -): - """ - collapses fission products into a single lumped fission product - - Parameters - ---------- - massFracs : dict - - lumpedFissionProducts - LumpedFissionProductCollection (acts like a dict) - result of .getGlobalLumpedFissionProducts - - Returns - ------- - newMassFracs : dict - - Notes - ----- - collapsing only works if there is a 'single lumped fission product collection' -- otherwise its confusing to - determine how much of what isotope goes to which lumped fission products - """ - - assert isinstance(lumpedFissionProducts, SingleLumpedFissionProductCollection) - lfp = lumpedFissionProducts.getFirstLfp() - newMassFracs = {} - lumpedFissionProductsMassFracs = {} - - for nb in lfp.keys(): - lumpedFissionProductsMassFracs[nb.name] = massFracs.get(nb.name, 0) - - newMassFracs[lumpedFissionProducts.getName()] = sum( - lumpedFissionProductsMassFracs.values() - ) - for nucName in massFracs.keys(): - if nucName not in lumpedFissionProductsMassFracs.keys(): - newMassFracs[nucName] = massFracs[nucName] - - if updateLumpedFissionProduct: - lumpedFissionProducts.updateYieldVector(massFrac=lumpedFissionProductsMassFracs) - - return newMassFracs - - def isGas(nuc): """True if nuclide is considered a gas.""" - for elementName in GASEOUS_ELEMENTS: - if elementName in nuc.name: + for element in elements.getElementsByChemicalPhase(elements.ChemicalPhase.GAS): + if element.symbol in nuc.name: return True return False diff --git a/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py b/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py index c6e73c065..215e6e470 100644 --- a/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py +++ b/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py @@ -45,9 +45,9 @@ def test_loadGlobalLFPsFromFile(self): def test_getAllFissionProductNames(self): # pylint: disable = protected-access - self.fpModel._getAllFissionProductNames() - self.assertGreater(len(self.fpModel.fissionProductNames), 5) - self.assertIn("XE135", self.fpModel.fissionProductNames) + fissionProductNames = self.fpModel.getAllFissionProductNames() + self.assertGreater(len(fissionProductNames), 5) + self.assertIn("XE135", fissionProductNames) if __name__ == "__main__": diff --git a/armi/physics/neutronics/fissionProductModel/tests/test_lumpedFissionProduct.py b/armi/physics/neutronics/fissionProductModel/tests/test_lumpedFissionProduct.py index ffa2f038e..0b2af5ef2 100644 --- a/armi/physics/neutronics/fissionProductModel/tests/test_lumpedFissionProduct.py +++ b/armi/physics/neutronics/fissionProductModel/tests/test_lumpedFissionProduct.py @@ -27,16 +27,16 @@ from armi.reactor.flags import Flags from armi.nucDirectory import nuclideBases -LFP_TEXT = """ 13 LFP35 GE73 5 5.9000E-06 - 13 LFP35 GE74 5 1.4000E-05 - 13 LFP35 GE76 5 1.6000E-04 - 13 LFP35 AS75 5 8.9000E-05 - 13 LFP35 KR85 5 8.9000E-05 - 13 LFP35 MO99 5 8.9000E-05 - 13 LFP35 SM1505 8.9000E-05 - 13 LFP35 XE1355 8.9000E-05 - 13 LFP39 XE1355 8.9000E-05 - 13 LFP38 XE1355 8.9000E-05 +LFP_TEXT = """LFP35 GE73 5.9000E-06 +LFP35 GE74 1.4000E-05 +LFP35 GE76 1.6000E-04 +LFP35 AS75 8.9000E-05 +LFP35 KR85 8.9000E-05 +LFP35 MO99 8.9000E-05 +LFP35 SM150 8.9000E-05 +LFP35 XE135 8.9000E-05 +LFP39 XE135 8.9000E-05 +LFP38 XE135 8.9000E-05 """ @@ -98,15 +98,6 @@ def setUp(self): io.StringIO(LFP_TEXT) ) - def test_setGasRemovedFrac(self): - """Test of the set gas removal fraction""" - lfp = self.fpd.createSingleLFPFromFile("LFP38") - xe135 = nuclideBases.fromName("XE135") - gas1 = lfp[xe135] - lfp.setGasRemovedFrac(0.25) - gas2 = lfp[xe135] - self.assertAlmostEqual(gas1 * 0.75, gas2) - def test_getYield(self): """Test of the yield of a fission product""" xe135 = nuclideBases.fromName("XE135") @@ -116,37 +107,17 @@ def test_getYield(self): self.assertEqual(val3, 3) self.assertIsNone(lfp[5]) - def test_getNumberFracs(self): - xe135 = nuclideBases.fromName("XE135") - lfp = self.fpd.createSingleLFPFromFile("LFP38") - numberFracs = lfp.getNumberFracs() - self.assertEqual(numberFracs.get(xe135), 1.0) - def test_getExpandedMass(self): xe135 = nuclideBases.fromName("XE135") lfp = self.fpd.createSingleLFPFromFile("LFP38") massVector = lfp.getExpandedMass(mass=0.99) self.assertEqual(massVector.get(xe135), 0.99) - def test_getGasFraction(self): - """Test of the get gas removal fraction""" - lfp = self.fpd.createSingleLFPFromFile("LFP35") - frac = lfp.getGasFraction() - self.assertGreater(frac, 0.0) - self.assertLess(frac, 1.0) - def test_printDensities(self): _ = nuclideBases.fromName("XE135") lfp = self.fpd.createSingleLFPFromFile("LFP38") lfp.printDensities(10.0) - def test_getLanthanideFraction(self): - """Test of the lanthanide fraction function""" - lfp = self.fpd.createSingleLFPFromFile("LFP35") - frac = lfp.getLanthanideFraction() - self.assertGreater(frac, 0.0) - self.assertLess(frac, 1.0) - class TestLumpedFissionProductCollection(unittest.TestCase): """Test of the fission product collection""" @@ -233,59 +204,6 @@ def test_getAllFissionProductNames(self): self.assertAlmostEqual(self.lfps["LFP35"].getTotalYield(), 2.0) -class TestExpandCollapse(unittest.TestCase): - """Test of the ability of the fission product file to expand from the LFPs""" - - def test_expand(self): - - fpd = lumpedFissionProduct.FissionProductDefinitionFile(io.StringIO(LFP_TEXT)) - lfps = fpd.createSingleLFPCollectionFromFile("LFP35") - - massFrac = { - "U238": 24482008.501781337, - "LFP35": 0.0, - "CL35": 1617.0794376133247, - "CL37": 17091083.486970097, - "U235": 3390057.9136671578, - "NA23": 367662.29994516558, - } - refMassFrac = massFrac.copy() - del refMassFrac["LFP35"] - testMassFrac = lumpedFissionProduct.expandFissionProducts(massFrac, {}) - - for nucName, mass in refMassFrac.items(): - normalizedMass = (testMassFrac[nucName] - mass) / mass - self.assertAlmostEqual(normalizedMass, 0, 6) - - refMassFrac = lfps.getFirstLfp().getMassFracs() - massFrac = {lfps.getFirstLfp().name: 1} - newMassFrac = lumpedFissionProduct.expandFissionProducts(massFrac, lfps) - - for nb, mass in refMassFrac.items(): - normalizedMass = (newMassFrac[nb.name] - mass) / mass - self.assertAlmostEqual(normalizedMass, 0, 6) - - def test_collapse(self): - - fpd = lumpedFissionProduct.FissionProductDefinitionFile(io.StringIO(LFP_TEXT)) - lfps = fpd.createSingleLFPCollectionFromFile("LFP35") - - burnup = 0.01 # fima - - # make 1% burnup fuel - refMassFracs = {"U235": 1 - burnup} - - lfp = lfps.getFirstLfp() - for nb, mFrac in lfp.getMassFracs().items(): - refMassFracs[nb.name] = burnup * mFrac - - newMassFracs = lumpedFissionProduct.collapseFissionProducts(refMassFracs, lfps) - - self.assertAlmostEqual(newMassFracs["LFP35"], burnup, 6) - lfps.updateYieldVector(massFrac=newMassFracs) - self.assertAlmostEqual(lfps["LFP35"].getTotalYield(), 2.0) - - if __name__ == "__main__": # import sys;sys.argv = ['', 'Test.testName'] unittest.main() diff --git a/armi/physics/neutronics/globalFlux/globalFluxInterface.py b/armi/physics/neutronics/globalFlux/globalFluxInterface.py index a1835cd5c..43b5870a4 100644 --- a/armi/physics/neutronics/globalFlux/globalFluxInterface.py +++ b/armi/physics/neutronics/globalFlux/globalFluxInterface.py @@ -353,7 +353,7 @@ def fromReactor(self, reactor: reactors.Reactor): self.symmetry = reactor.core.symmetry cycleNodeStamp = f"{reactor.p.cycle:03d}{reactor.p.timeNode:03d}" - if self.savePhysicsFilesList is not None: + if self.savePhysicsFilesList: self.savePhysicsFiles = cycleNodeStamp in self.savePhysicsFilesList else: self.savePhysicsFiles = False diff --git a/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py b/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py index d33d99008..815c225a9 100644 --- a/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py +++ b/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py @@ -108,6 +108,7 @@ def __init__( self.modelFissionProducts = ( blockNeedsFPs and self.cs["fpModel"] != "noFissionProducts" ) + self.explicitFissionProducts = self.cs["fpModel"] == "explicitFissionProducts" self.diluteFissionProducts = ( blockNeedsFPs and self.cs["fpModel"] == "infinitelyDilute" ) @@ -214,11 +215,18 @@ def _getAllNuclidesByCategory(self, component=None): ) objNuclides = subjectObject.getNuclides() - numDensities = subjectObject.getNuclideNumberDensities( - self.r.blueprints.allNuclidesInProblem + # If the explicit fission product model is enabled then the number densities + # on the components will already contain all the nuclides required to be + # modeled by the lattice physics writer. Otherwise, assume that `allNuclidesInProblem` + # should be modeled. + nuclides = ( + sorted(objNuclides) + if self.explicitFissionProducts + else self.r.blueprints.allNuclidesInProblem ) + numDensities = subjectObject.getNuclideNumberDensities(nuclides) - for nucName, dens in zip(self.r.blueprints.allNuclidesInProblem, numDensities): + for nucName, dens in zip(nuclides, numDensities): nuc = nuclideBases.byName[nucName] if isinstance(nuc, nuclideBases.LumpNuclideBase): continue # skip LFPs here but add individual FPs below. @@ -371,7 +379,7 @@ def _getDetailedFPDensities(self): # now, go through the list and make sure that there aren't any values less than the # minimumNuclideDensity; we need to keep trace amounts of nuclides in the problem for fpName, fpDens in dfpDensitiesByName.items(): - fp = nuclideBases.fromName(fpName) + fp = nuclideBases.byName[fpName] dfpDensities[fp] = max(fpDens, self.minimumNuclideDensity) return dfpDensities diff --git a/armi/reactor/blueprints/__init__.py b/armi/reactor/blueprints/__init__.py index f3a3e1bd0..6076f6007 100644 --- a/armi/reactor/blueprints/__init__.py +++ b/armi/reactor/blueprints/__init__.py @@ -91,6 +91,8 @@ from armi.reactor import systemLayoutInput from armi.scripts import migration from armi.utils import textProcessors +from armi.physics.neutronics.fissionProductModel import lumpedFissionProduct +from armi.nucDirectory import elements # NOTE: using non-ARMI-standard imports because these are all a part of this package, # and using the module imports would make the attribute definitions extremely long @@ -342,6 +344,33 @@ def _assignTypeNums(self): if bDesign not in self.blockDesigns: self.blockDesigns.add(bDesign) + def _autoUpdateNuclideFlags(self, cs): + """ + This method is responsible for examining the fission product model treatment + that is selected by the user and adding a set of nuclides to the `nuclideFlags` + list. + Notes + ----- + The reason for adding this method is that when switching between fission product + modeling treatments it can be time-consuming to manually adjust the `nuclideFlags` + inputs. This is specifically the case with the fission product model is set to + `explicitFissionProducts`. + """ + nbs = lumpedFissionProduct.getAllNuclideBasesByLibrary(cs) + if nbs: + runLog.info( + f"Adding explicit fission products to the nuclide flags based on the " + f"fission product model set to `{cs['fpModel']}`." + ) + for nb in nbs: + nuc = nb.name + if nuc in self.nuclideFlags or elements.byZ[nb.z] in self.nuclideFlags: + continue + nuclideFlag = isotopicOptions.NuclideFlag( + nuc, burn=False, xs=True, expandTo=[] + ) + self.nuclideFlags[nuc] = nuclideFlag + def _resolveNuclides(self, cs): """ Process elements and determine how to expand them to natural isotopics. @@ -364,6 +393,8 @@ def _resolveNuclides(self, cs): if self.nuclideFlags is None: self.nuclideFlags = isotopicOptions.genDefaultNucFlags() + self._autoUpdateNuclideFlags(cs) + self.elementsToExpand = [] for nucFlag in self.nuclideFlags: # this returns any nuclides that are flagged specifically for expansion by input @@ -454,7 +485,7 @@ def _resolveNuclides(self, cs): ) # Inform user which nuclides are truncating the burn chain. - if undefBurnChainActiveNuclides: + if undefBurnChainActiveNuclides and nuclideBases.burnChainImposed: runLog.info( tabulate.tabulate( [ diff --git a/armi/resources/referenceFissionProducts.dat b/armi/resources/referenceFissionProducts.dat index 39c155056..e5ff7e473 100644 --- a/armi/resources/referenceFissionProducts.dat +++ b/armi/resources/referenceFissionProducts.dat @@ -1,807 +1,800 @@ -13 LFP35 GE76_7 1.5000E-04 -13 LFP35 AS75_7 7.0000E-05 -13 LFP35 SE77_7 2.9000E-04 -13 LFP35 SE78_7 5.6000E-04 -13 LFP35 SE79_7 9.1000E-04 -13 LFP35 SE80_7 1.5100E-03 -13 LFP35 SE82_7 3.9000E-03 -13 LFP35 BR81_7 2.4600E-03 -13 LFP35 BR82_7 0.0000E+00 -13 LFP35 KR82_7 6.0000E-05 -13 LFP35 KR83_7 6.0100E-03 -13 LFP35 KR84_7 1.0840E-02 -13 LFP35 KR85_7 1.3800E-02 -13 LFP35 KR86_7 1.9280E-02 -13 LFP35 RB85_7 2.9000E-04 -13 LFP35 RB86_7 1.0000E-05 -13 LFP35 RB87_7 2.5480E-02 -13 LFP35 SR86_7 4.0000E-05 -13 LFP35 SR88_7 3.6410E-02 -13 LFP35 SR89_7 1.2780E-02 -13 LFP35 SR90_7 5.0750E-02 -13 LFP35 Y89__7 3.1560E-02 -13 LFP35 Y90__7 1.0000E-05 -13 LFP35 Y91__7 1.7630E-02 -13 LFP35 ZR90_7 4.3000E-04 -13 LFP35 ZR91_7 3.5320E-02 -13 LFP35 ZR92_7 5.6390E-02 -13 LFP35 ZR93_7 5.9640E-02 -13 LFP35 ZR94_7 6.4490E-02 -13 LFP35 ZR95_7 2.3150E-02 -13 LFP35 ZR96_7 6.5530E-02 -13 LFP35 NB95_7 1.1460E-02 -13 LFP35 MO95_7 2.9250E-02 -13 LFP35 MO96_7 6.0000E-04 -13 LFP35 MO97_7 5.8480E-02 -13 LFP35 MO98_7 6.1360E-02 -13 LFP35 MO99_7 9.1000E-04 -13 LFP35 MO1007 6.3550E-02 -13 LFP35 TC99_7 5.3490E-02 -13 LFP35 RU1007 1.5200E-03 -13 LFP35 RU1017 5.3270E-02 -13 LFP35 RU1027 4.8160E-02 -13 LFP35 RU1037 7.9600E-03 -13 LFP35 RU1047 2.3660E-02 -13 LFP35 RU1067 3.6500E-03 -13 LFP35 RH1037 2.5090E-02 -13 LFP35 RH1057 1.3000E-04 -13 LFP35 PD1047 6.7000E-04 -13 LFP35 PD1057 1.4260E-02 -13 LFP35 PD1067 1.4900E-03 -13 LFP35 PD1077 1.7200E-03 -13 LFP35 PD1087 7.8000E-04 -13 LFP35 PD1107 4.5000E-04 -13 LFP35 AG1097 5.4000E-04 -13 LFP35 AG1117 1.0000E-05 -13 LFP35 CD1107 1.0000E-05 -13 LFP35 CD1117 2.7000E-04 -13 LFP35 CD1127 3.9000E-04 -13 LFP35 CD1137 3.4000E-04 -13 LFP35 CD1147 3.4000E-04 -13 LFP35 CD1157 0.0000E+00 -13 LFP35 CD15M7 1.0000E-05 -13 LFP35 CD1167 3.6000E-04 -13 LFP35 IN1157 2.0000E-04 -13 LFP35 SN1157 1.0000E-05 -13 LFP35 SN1167 0.0000E+00 -13 LFP35 SN1177 3.7000E-04 -13 LFP35 SN1187 3.9000E-04 -13 LFP35 SN1197 4.2000E-04 -13 LFP35 SN1207 4.4000E-04 -13 LFP35 SN1227 4.8000E-04 -13 LFP35 SN1237 3.0000E-04 -13 LFP35 SN1247 6.0000E-04 -13 LFP35 SN1257 4.0000E-05 -13 LFP35 SN1267 1.0400E-03 -13 LFP35 SB1217 4.5000E-04 -13 LFP35 SB1227 0.0000E+00 -13 LFP35 SB1237 2.4000E-04 -13 LFP35 SB1247 0.0000E+00 -13 LFP35 SB1257 6.3000E-04 -13 LFP35 SB1267 1.0000E-05 -13 LFP35 SB1277 5.0000E-05 -13 LFP35 TE1227 1.0000E-05 -13 LFP35 TE1237 0.0000E+00 -13 LFP35 TE1247 0.0000E+00 -13 LFP35 TE1257 5.0000E-05 -13 LFP35 TE1267 6.0000E-05 -13 LFP35 TE27M7 1.7000E-04 -13 LFP35 TE1287 3.2100E-03 -13 LFP35 TE29M7 1.0800E-03 -13 LFP35 TE1307 1.4390E-02 -13 LFP35 TE1327 8.9000E-04 -13 LFP35 I127_7 1.7400E-03 -13 LFP35 I129_7 4.4200E-03 -13 LFP35 I131_7 1.5300E-03 -13 LFP35 XE1287 5.0000E-05 -13 LFP35 XE1297 0.0000E+00 -13 LFP35 XE1307 8.0000E-05 -13 LFP35 XE1317 3.0630E-02 -13 LFP35 XE1327 4.7610E-02 -13 LFP35 XE1337 2.0600E-03 -13 LFP35 XE1347 7.1550E-02 -13 LFP35 XE1367 5.9930E-02 -13 LFP35 CS1337 6.2930E-02 -13 LFP35 CS1347 1.6900E-03 -13 LFP35 CS1357 6.3930E-02 -13 LFP35 CS1367 4.0000E-05 -13 LFP35 CS1377 6.2280E-02 -13 LFP35 BA1347 1.4000E-04 -13 LFP35 BA1357 0.0000E+00 -13 LFP35 BA1367 2.4000E-04 -13 LFP35 BA1377 4.7000E-04 -13 LFP35 BA1387 6.6920E-02 -13 LFP35 BA1407 4.3300E-03 -13 LFP35 LA1397 7.0870E-02 -13 LFP35 LA1407 5.7000E-04 -13 LFP35 CE1407 5.3220E-02 -13 LFP35 CE1417 1.1780E-02 -13 LFP35 CE1427 5.8780E-02 -13 LFP35 CE1437 4.8000E-04 -13 LFP35 CE1447 3.8490E-02 -13 LFP35 PR1417 4.8540E-02 -13 LFP35 PR1437 4.6700E-03 -13 LFP35 ND1427 2.8000E-04 -13 LFP35 ND1437 5.2700E-02 -13 LFP35 ND1447 1.3010E-02 -13 LFP35 ND1457 3.7780E-02 -13 LFP35 ND1467 3.0160E-02 -13 LFP35 ND1477 1.3300E-03 -13 LFP35 ND1487 1.6910E-02 -13 LFP35 ND1507 7.1500E-03 -13 LFP35 PM1477 1.6510E-02 -13 LFP35 PM1487 4.0000E-05 -13 LFP35 PM48M7 2.0000E-04 -13 LFP35 PM1497 1.5000E-04 -13 LFP35 SM1477 1.3500E-03 -13 LFP35 SM1487 8.6000E-04 -13 LFP35 SM1497 1.0170E-02 -13 LFP35 SM1507 7.6000E-04 -13 LFP35 SM1517 4.0000E-03 -13 LFP35 SM1527 3.5000E-03 -13 LFP35 SM1537 2.0000E-05 -13 LFP35 SM1547 9.7000E-04 -13 LFP35 EU1537 1.8200E-03 -13 LFP35 EU1547 2.0000E-04 -13 LFP35 EU1557 3.4000E-04 -13 LFP35 EU1567 2.0000E-05 -13 LFP35 GD1547 0.0000E+00 -13 LFP35 GD1557 1.0000E-05 -13 LFP35 GD1567 1.7000E-04 -13 LFP35 GD1577 7.0000E-05 -13 LFP35 GD1587 6.0000E-05 -13 LFP35 GD1607 1.0000E-05 -13 LFP35 TB1597 3.0000E-05 -13 LFP35 TB1607 0.0000E+00 -13 LFP35 TB1617 0.0000E+00 -13 LFP35 DY1607 0.0000E+00 -13 LFP35 DY1617 0.0000E+00 -13 LFP35 DY1627 0.0000E+00 -13 LFP35 DY1637 0.0000E+00 -13 LFP35 DY1647 0.0000E+00 -13 LFP38 GE76_7 1.0000E-05 -13 LFP38 AS75_7 0.0000E+00 -13 LFP38 SE77_7 4.0000E-05 -13 LFP38 SE78_7 1.4000E-04 -13 LFP38 SE79_7 4.0000E-04 -13 LFP38 SE80_7 8.7000E-04 -13 LFP38 SE82_7 2.5000E-03 -13 LFP38 BR81_7 1.5700E-03 -13 LFP38 BR82_7 0.0000E+00 -13 LFP38 KR82_7 3.0000E-05 -13 LFP38 KR83_7 3.9000E-03 -13 LFP38 KR84_7 5.5900E-03 -13 LFP38 KR85_7 7.1500E-03 -13 LFP38 KR86_7 1.3820E-02 -13 LFP38 RB85_7 1.5000E-04 -13 LFP38 RB86_7 0.0000E+00 -13 LFP38 RB87_7 1.7000E-02 -13 LFP38 SR86_7 0.0000E+00 -13 LFP38 SR88_7 2.1020E-02 -13 LFP38 SR89_7 9.5600E-03 -13 LFP38 SR90_7 3.1150E-02 -13 LFP38 Y89__7 2.2020E-02 -13 LFP38 Y90__7 1.0000E-05 -13 LFP38 Y91__7 1.3320E-02 -13 LFP38 ZR90_7 2.6000E-04 -13 LFP38 ZR91_7 2.4990E-02 -13 LFP38 ZR92_7 4.3120E-02 -13 LFP38 ZR93_7 4.7000E-02 -13 LFP38 ZR94_7 5.1070E-02 -13 LFP38 ZR95_7 2.0630E-02 -13 LFP38 ZR96_7 5.7440E-02 -13 LFP38 NB95_7 9.9100E-03 -13 LFP38 MO95_7 2.4170E-02 -13 LFP38 MO96_7 2.5000E-04 -13 LFP38 MO97_7 5.8040E-02 -13 LFP38 MO98_7 6.1270E-02 -13 LFP38 MO99_7 1.1200E-03 -13 LFP38 MO1007 6.1510E-02 -13 LFP38 TC99_7 6.0070E-02 -13 LFP38 RU1007 1.6500E-03 -13 LFP38 RU1017 5.9440E-02 -13 LFP38 RU1027 6.3610E-02 -13 LFP38 RU1037 1.4790E-02 -13 LFP38 RU1047 5.7020E-02 -13 LFP38 RU1067 2.3370E-02 -13 LFP38 RH1037 4.3460E-02 -13 LFP38 RH1057 3.4000E-04 -13 LFP38 PD1047 1.1200E-03 -13 LFP38 PD1057 3.3480E-02 -13 LFP38 PD1067 6.7900E-03 -13 LFP38 PD1077 1.2340E-02 -13 LFP38 PD1087 6.8400E-03 -13 LFP38 PD1107 1.3900E-03 -13 LFP38 AG1097 2.0100E-03 -13 LFP38 AG1117 4.0000E-05 -13 LFP38 CD1107 5.0000E-05 -13 LFP38 CD1117 7.3000E-04 -13 LFP38 CD1127 7.1000E-04 -13 LFP38 CD1137 5.5000E-04 -13 LFP38 CD1147 4.6000E-04 -13 LFP38 CD1157 1.0000E-05 -13 LFP38 CD15M7 1.0000E-05 -13 LFP38 CD1167 3.5000E-04 -13 LFP38 IN1157 3.7000E-04 -13 LFP38 SN1157 2.0000E-05 -13 LFP38 SN1167 1.0000E-05 -13 LFP38 SN1177 3.4000E-04 -13 LFP38 SN1187 3.4000E-04 -13 LFP38 SN1197 3.4000E-04 -13 LFP38 SN1207 3.4000E-04 -13 LFP38 SN1227 3.7000E-04 -13 LFP38 SN1237 2.2000E-04 -13 LFP38 SN1247 4.2000E-04 -13 LFP38 SN1257 5.0000E-05 -13 LFP38 SN1267 1.0000E-03 -13 LFP38 SB1217 3.5000E-04 -13 LFP38 SB1227 0.0000E+00 -13 LFP38 SB1237 1.7000E-04 -13 LFP38 SB1247 0.0000E+00 -13 LFP38 SB1257 6.6000E-04 -13 LFP38 SB1267 0.0000E+00 -13 LFP38 SB1277 4.0000E-05 -13 LFP38 TE1227 1.0000E-05 -13 LFP38 TE1237 0.0000E+00 -13 LFP38 TE1247 0.0000E+00 -13 LFP38 TE1257 5.0000E-05 -13 LFP38 TE1267 1.0000E-05 -13 LFP38 TE27M7 1.3000E-04 -13 LFP38 TE1287 5.0600E-03 -13 LFP38 TE29M7 1.8900E-03 -13 LFP38 TE1307 1.9590E-02 -13 LFP38 TE1327 9.5000E-04 -13 LFP38 I127_7 1.3200E-03 -13 LFP38 I129_7 7.1300E-03 -13 LFP38 I131_7 1.8500E-03 -13 LFP38 XE1287 3.0000E-05 -13 LFP38 XE1297 0.0000E+00 -13 LFP38 XE1307 1.2000E-04 -13 LFP38 XE1317 3.3910E-02 -13 LFP38 XE1327 4.5460E-02 -13 LFP38 XE1337 1.8400E-03 -13 LFP38 XE1347 6.1720E-02 -13 LFP38 XE1367 6.2920E-02 -13 LFP38 CS1337 5.1520E-02 -13 LFP38 CS1347 1.2000E-03 -13 LFP38 CS1357 5.6270E-02 -13 LFP38 CS1367 3.0000E-05 -13 LFP38 CS1377 6.9490E-02 -13 LFP38 BA1347 8.0000E-05 -13 LFP38 BA1357 0.0000E+00 -13 LFP38 BA1367 1.6000E-04 -13 LFP38 BA1377 5.1000E-04 -13 LFP38 BA1387 6.4770E-02 -13 LFP38 BA1407 4.9300E-03 -13 LFP38 LA1397 6.0830E-02 -13 LFP38 LA1407 6.5000E-04 -13 LFP38 CE1407 5.5060E-02 -13 LFP38 CE1417 1.1240E-02 -13 LFP38 CE1427 5.0540E-02 -13 LFP38 CE1437 4.2000E-04 -13 LFP38 CE1447 3.4240E-02 -13 LFP38 PR1417 4.2770E-02 -13 LFP38 PR1437 4.0300E-03 -13 LFP38 ND1427 2.4000E-04 -13 LFP38 ND1437 4.1630E-02 -13 LFP38 ND1447 1.1040E-02 -13 LFP38 ND1457 4.0860E-02 -13 LFP38 ND1467 3.8970E-02 -13 LFP38 ND1477 1.8600E-03 -13 LFP38 ND1487 2.3590E-02 -13 LFP38 ND1507 1.4640E-02 -13 LFP38 PM1477 2.1310E-02 -13 LFP38 PM1487 5.0000E-05 -13 LFP38 PM48M7 2.5000E-04 -13 LFP38 PM1497 2.5000E-04 -13 LFP38 SM1477 1.6800E-03 -13 LFP38 SM1487 1.0400E-03 -13 LFP38 SM1497 1.6200E-02 -13 LFP38 SM1507 1.1700E-03 -13 LFP38 SM1517 9.2800E-03 -13 LFP38 SM1527 7.5100E-03 -13 LFP38 SM1537 6.0000E-05 -13 LFP38 SM1547 2.1300E-03 -13 LFP38 EU1537 3.9000E-03 -13 LFP38 EU1547 4.2000E-04 -13 LFP38 EU1557 1.0500E-03 -13 LFP38 EU1567 9.0000E-05 -13 LFP38 GD1547 1.0000E-05 -13 LFP38 GD1557 4.0000E-05 -13 LFP38 GD1567 7.1000E-04 -13 LFP38 GD1577 2.7000E-04 -13 LFP38 GD1587 2.1000E-04 -13 LFP38 GD1607 3.0000E-05 -13 LFP38 TB1597 8.0000E-05 -13 LFP38 TB1607 0.0000E+00 -13 LFP38 TB1617 0.0000E+00 -13 LFP38 DY1607 0.0000E+00 -13 LFP38 DY1617 1.0000E-05 -13 LFP38 DY1627 0.0000E+00 -13 LFP38 DY1637 0.0000E+00 -13 LFP38 DY1647 0.0000E+00 -13 LFP39 GE76_7 1.1000E-04 -13 LFP39 AS75_7 0.0000E+00 -13 LFP39 SE77_7 1.3000E-04 -13 LFP39 SE78_7 3.5000E-04 -13 LFP39 SE79_7 5.3000E-04 -13 LFP39 SE80_7 9.0000E-04 -13 LFP39 SE82_7 2.3500E-03 -13 LFP39 BR81_7 1.3700E-03 -13 LFP39 BR82_7 0.0000E+00 -13 LFP39 KR82_7 6.0000E-05 -13 LFP39 KR83_7 3.4800E-03 -13 LFP39 KR84_7 5.6500E-03 -13 LFP39 KR85_7 6.5500E-03 -13 LFP39 KR86_7 8.7800E-03 -13 LFP39 RB85_7 1.4000E-04 -13 LFP39 RB86_7 0.0000E+00 -13 LFP39 RB87_7 1.1500E-02 -13 LFP39 SR86_7 1.0000E-05 -13 LFP39 SR88_7 1.4210E-02 -13 LFP39 SR89_7 5.4300E-03 -13 LFP39 SR90_7 1.8750E-02 -13 LFP39 Y89__7 1.2850E-02 -13 LFP39 Y90__7 1.0000E-05 -13 LFP39 Y91__7 8.6400E-03 -13 LFP39 ZR90_7 1.6000E-04 -13 LFP39 ZR91_7 1.6600E-02 -13 LFP39 ZR92_7 3.0610E-02 -13 LFP39 ZR93_7 3.7910E-02 -13 LFP39 ZR94_7 4.2740E-02 -13 LFP39 ZR95_7 1.7570E-02 -13 LFP39 ZR96_7 4.9390E-02 -13 LFP39 NB95_7 8.5400E-03 -13 LFP39 MO95_7 2.1180E-02 -13 LFP39 MO96_7 2.5000E-04 -13 LFP39 MO97_7 5.2000E-02 -13 LFP39 MO98_7 5.6500E-02 -13 LFP39 MO99_7 9.9000E-04 -13 LFP39 MO1007 6.4390E-02 -13 LFP39 TC99_7 5.5000E-02 -13 LFP39 RU1007 1.5300E-03 -13 LFP39 RU1017 6.3870E-02 -13 LFP39 RU1027 6.8340E-02 -13 LFP39 RU1037 1.5920E-02 -13 LFP39 RU1047 6.4990E-02 -13 LFP39 RU1067 3.3780E-02 -13 LFP39 RH1037 4.8100E-02 -13 LFP39 RH1057 4.8000E-04 -13 LFP39 PD1047 1.2600E-03 -13 LFP39 PD1057 4.8370E-02 -13 LFP39 PD1067 1.1050E-02 -13 LFP39 PD1077 3.0830E-02 -13 LFP39 PD1087 2.4150E-02 -13 LFP39 PD1107 9.2300E-03 -13 LFP39 AG1097 1.6320E-02 -13 LFP39 AG1117 2.1000E-04 -13 LFP39 CD1107 4.2000E-04 -13 LFP39 CD1117 4.1000E-03 -13 LFP39 CD1127 1.3500E-03 -13 LFP39 CD1137 9.1000E-04 -13 LFP39 CD1147 1.0000E-03 -13 LFP39 CD1157 1.0000E-05 -13 LFP39 CD15M7 8.0000E-05 -13 LFP39 CD1167 6.4000E-04 -13 LFP39 IN1157 8.9000E-04 -13 LFP39 SN1157 4.0000E-05 -13 LFP39 SN1167 2.0000E-05 -13 LFP39 SN1177 6.4000E-04 -13 LFP39 SN1187 6.5000E-04 -13 LFP39 SN1197 6.6000E-04 -13 LFP39 SN1207 6.7000E-04 -13 LFP39 SN1227 6.9000E-04 -13 LFP39 SN1237 3.9000E-04 -13 LFP39 SN1247 1.2000E-03 -13 LFP39 SN1257 1.1000E-04 -13 LFP39 SN1267 3.0300E-03 -13 LFP39 SB1217 6.6000E-04 -13 LFP39 SB1227 0.0000E+00 -13 LFP39 SB1237 3.0000E-04 -13 LFP39 SB1247 0.0000E+00 -13 LFP39 SB1257 1.6300E-03 -13 LFP39 SB1267 1.0000E-05 -13 LFP39 SB1277 1.2000E-04 -13 LFP39 TE1227 2.0000E-05 -13 LFP39 TE1237 0.0000E+00 -13 LFP39 TE1247 0.0000E+00 -13 LFP39 TE1257 1.2000E-04 -13 LFP39 TE1267 8.0000E-05 -13 LFP39 TE27M7 4.5000E-04 -13 LFP39 TE1287 7.9000E-03 -13 LFP39 TE29M7 2.3000E-03 -13 LFP39 TE1307 1.9560E-02 -13 LFP39 TE1327 1.0400E-03 -13 LFP39 I127_7 4.4600E-03 -13 LFP39 I129_7 8.9700E-03 -13 LFP39 I131_7 2.1800E-03 -13 LFP39 XE1287 1.2000E-04 -13 LFP39 XE1297 0.0000E+00 -13 LFP39 XE1307 1.8000E-04 -13 LFP39 XE1317 4.1390E-02 -13 LFP39 XE1327 5.2730E-02 -13 LFP39 XE1337 2.2300E-03 -13 LFP39 XE1347 7.2290E-02 -13 LFP39 XE1367 6.7760E-02 -13 LFP39 CS1337 6.4410E-02 -13 LFP39 CS1347 1.5300E-03 -13 LFP39 CS1357 7.4240E-02 -13 LFP39 CS1367 9.0000E-05 -13 LFP39 CS1377 6.3420E-02 -13 LFP39 BA1347 1.1000E-04 -13 LFP39 BA1357 0.0000E+00 -13 LFP39 BA1367 8.2000E-04 -13 LFP39 BA1377 5.7000E-04 -13 LFP39 BA1387 4.9360E-02 -13 LFP39 BA1407 4.1400E-03 -13 LFP39 LA1397 6.0530E-02 -13 LFP39 LA1407 5.4000E-04 -13 LFP39 CE1407 4.7870E-02 -13 LFP39 CE1417 1.1760E-02 -13 LFP39 CE1427 4.9240E-02 -13 LFP39 CE1437 3.9000E-04 -13 LFP39 CE1447 2.6420E-02 -13 LFP39 PR1417 4.6110E-02 -13 LFP39 PR1437 3.7200E-03 -13 LFP39 ND1427 2.6000E-04 -13 LFP39 ND1437 3.9700E-02 -13 LFP39 ND1447 8.7600E-03 -13 LFP39 ND1457 3.0100E-02 -13 LFP39 ND1467 2.5570E-02 -13 LFP39 ND1477 1.4100E-03 -13 LFP39 ND1487 1.7150E-02 -13 LFP39 ND1507 1.0390E-02 -13 LFP39 PM1477 1.6640E-02 -13 LFP39 PM1487 4.0000E-05 -13 LFP39 PM48M7 1.9000E-04 -13 LFP39 PM1497 1.9000E-04 -13 LFP39 SM1477 1.3300E-03 -13 LFP39 SM1487 8.3000E-04 -13 LFP39 SM1497 1.2650E-02 -13 LFP39 SM1507 9.3000E-04 -13 LFP39 SM1517 7.6100E-03 -13 LFP39 SM1527 7.5900E-03 -13 LFP39 SM1537 7.0000E-05 -13 LFP39 SM1547 3.2100E-03 -13 LFP39 EU1537 4.6300E-03 -13 LFP39 EU1547 5.1000E-04 -13 LFP39 EU1557 2.1400E-03 -13 LFP39 EU1567 2.0000E-04 -13 LFP39 GD1547 1.0000E-05 -13 LFP39 GD1557 9.0000E-05 -13 LFP39 GD1567 1.6600E-03 -13 LFP39 GD1577 9.3000E-04 -13 LFP39 GD1587 8.9000E-04 -13 LFP39 GD1607 3.3000E-04 -13 LFP39 TB1597 4.3000E-04 -13 LFP39 TB1607 2.0000E-05 -13 LFP39 TB1617 1.0000E-05 -13 LFP39 DY1607 1.0000E-05 -13 LFP39 DY1617 2.0000E-04 -13 LFP39 DY1627 2.0000E-05 -13 LFP39 DY1637 0.0000E+00 -13 LFP39 DY1647 0.0000E+00 -13 LFP40 GE76_7 1.0000E-05 -13 LFP40 AS75_7 0.0000E+00 -13 LFP40 SE77_7 1.4000E-04 -13 LFP40 SE78_7 2.9000E-04 -13 LFP40 SE79_7 5.3000E-04 -13 LFP40 SE80_7 9.1000E-04 -13 LFP40 SE82_7 2.0900E-03 -13 LFP40 BR81_7 1.4800E-03 -13 LFP40 BR82_7 0.0000E+00 -13 LFP40 KR82_7 3.0000E-05 -13 LFP40 KR83_7 3.1200E-03 -13 LFP40 KR84_7 4.5800E-03 -13 LFP40 KR85_7 5.8900E-03 -13 LFP40 KR86_7 7.9300E-03 -13 LFP40 RB85_7 1.2000E-04 -13 LFP40 RB86_7 0.0000E+00 -13 LFP40 RB87_7 1.0190E-02 -13 LFP40 SR86_7 0.0000E+00 -13 LFP40 SR88_7 1.2810E-02 -13 LFP40 SR89_7 4.9900E-03 -13 LFP40 SR90_7 1.9640E-02 -13 LFP40 Y89__7 1.1100E-02 -13 LFP40 Y90__7 1.0000E-05 -13 LFP40 Y91__7 8.4700E-03 -13 LFP40 ZR90_7 1.6000E-04 -13 LFP40 ZR91_7 1.5370E-02 -13 LFP40 ZR92_7 2.9300E-02 -13 LFP40 ZR93_7 3.5310E-02 -13 LFP40 ZR94_7 4.2320E-02 -13 LFP40 ZR95_7 1.7730E-02 -13 LFP40 ZR96_7 5.0270E-02 -13 LFP40 NB95_7 8.3900E-03 -13 LFP40 MO95_7 1.9980E-02 -13 LFP40 MO96_7 2.3000E-04 -13 LFP40 MO97_7 5.2800E-02 -13 LFP40 MO98_7 5.5680E-02 -13 LFP40 MO99_7 1.0800E-03 -13 LFP40 MO1007 6.0410E-02 -13 LFP40 TC99_7 5.5520E-02 -13 LFP40 RU1007 1.4900E-03 -13 LFP40 RU1017 5.8480E-02 -13 LFP40 RU1027 6.1650E-02 -13 LFP40 RU1037 1.5490E-02 -13 LFP40 RU1047 5.7910E-02 -13 LFP40 RU1067 4.1310E-02 -13 LFP40 RH1037 4.3910E-02 -13 LFP40 RH1057 5.5000E-04 -13 LFP40 PD1047 1.1200E-03 -13 LFP40 PD1057 5.2300E-02 -13 LFP40 PD1067 1.1480E-02 -13 LFP40 PD1077 4.0780E-02 -13 LFP40 PD1087 3.3210E-02 -13 LFP40 PD1107 1.2000E-02 -13 LFP40 AG1097 2.1390E-02 -13 LFP40 AG1117 3.0000E-04 -13 LFP40 CD1107 5.2000E-04 -13 LFP40 CD1117 5.5800E-03 -13 LFP40 CD1127 2.8800E-03 -13 LFP40 CD1137 1.5000E-03 -13 LFP40 CD1147 1.0200E-03 -13 LFP40 CD1157 1.0000E-05 -13 LFP40 CD15M7 8.0000E-05 -13 LFP40 CD1167 8.5000E-04 -13 LFP40 IN1157 8.6000E-04 -13 LFP40 SN1157 4.0000E-05 -13 LFP40 SN1167 2.0000E-05 -13 LFP40 SN1177 8.5000E-04 -13 LFP40 SN1187 8.0000E-04 -13 LFP40 SN1197 8.0000E-04 -13 LFP40 SN1207 8.5000E-04 -13 LFP40 SN1227 9.5000E-04 -13 LFP40 SN1237 6.3000E-04 -13 LFP40 SN1247 1.2000E-03 -13 LFP40 SN1257 1.0000E-04 -13 LFP40 SN1267 3.1600E-03 -13 LFP40 SB1217 8.3000E-04 -13 LFP40 SB1227 0.0000E+00 -13 LFP40 SB1237 4.6000E-04 -13 LFP40 SB1247 0.0000E+00 -13 LFP40 SB1257 1.3800E-03 -13 LFP40 SB1267 1.0000E-05 -13 LFP40 SB1277 1.0000E-04 -13 LFP40 TE1227 2.0000E-05 -13 LFP40 TE1237 0.0000E+00 -13 LFP40 TE1247 0.0000E+00 -13 LFP40 TE1257 9.0000E-05 -13 LFP40 TE1267 5.0000E-05 -13 LFP40 TE27M7 3.3000E-04 -13 LFP40 TE1287 6.1800E-03 -13 LFP40 TE29M7 2.1500E-03 -13 LFP40 TE1307 1.8890E-02 -13 LFP40 TE1327 1.0400E-03 -13 LFP40 I127_7 3.1800E-03 -13 LFP40 I129_7 7.8300E-03 -13 LFP40 I131_7 1.8400E-03 -13 LFP40 XE1287 8.0000E-05 -13 LFP40 XE1297 0.0000E+00 -13 LFP40 XE1307 1.3000E-04 -13 LFP40 XE1317 3.2240E-02 -13 LFP40 XE1327 4.8230E-02 -13 LFP40 XE1337 1.9900E-03 -13 LFP40 XE1347 6.2700E-02 -13 LFP40 XE1367 7.1210E-02 -13 LFP40 CS1337 5.3530E-02 -13 LFP40 CS1347 1.2300E-03 -13 LFP40 CS1357 6.7720E-02 -13 LFP40 CS1367 7.0000E-05 -13 LFP40 CS1377 6.9480E-02 -13 LFP40 BA1347 8.0000E-05 -13 LFP40 BA1357 0.0000E+00 -13 LFP40 BA1367 5.6000E-04 -13 LFP40 BA1377 5.0000E-04 -13 LFP40 BA1387 6.4650E-02 -13 LFP40 BA1407 4.5600E-03 -13 LFP40 LA1397 5.8020E-02 -13 LFP40 LA1407 6.0000E-04 -13 LFP40 CE1407 4.8680E-02 -13 LFP40 CE1417 1.0270E-02 -13 LFP40 CE1427 5.3020E-02 -13 LFP40 CE1437 4.7000E-04 -13 LFP40 CE1447 3.0570E-02 -13 LFP40 PR1417 3.7570E-02 -13 LFP40 PR1437 4.5200E-03 -13 LFP40 ND1427 2.1000E-04 -13 LFP40 ND1437 4.4640E-02 -13 LFP40 ND1447 9.7500E-03 -13 LFP40 ND1457 3.2680E-02 -13 LFP40 ND1467 2.8470E-02 -13 LFP40 ND1477 1.6100E-03 -13 LFP40 ND1487 1.8140E-02 -13 LFP40 ND1507 1.1570E-02 -13 LFP40 PM1477 1.7680E-02 -13 LFP40 PM1487 4.0000E-05 -13 LFP40 PM48M7 2.0000E-04 -13 LFP40 PM1497 2.3000E-04 -13 LFP40 SM1477 1.3700E-03 -13 LFP40 SM1487 8.4000E-04 -13 LFP40 SM1497 1.4040E-02 -13 LFP40 SM1507 9.9000E-04 -13 LFP40 SM1517 8.5100E-03 -13 LFP40 SM1527 8.5000E-03 -13 LFP40 SM1537 8.0000E-05 -13 LFP40 SM1547 4.0500E-03 -13 LFP40 EU1537 5.0800E-03 -13 LFP40 EU1547 5.4000E-04 -13 LFP40 EU1557 2.6700E-03 -13 LFP40 EU1567 2.7000E-04 -13 LFP40 GD1547 1.0000E-05 -13 LFP40 GD1557 1.1000E-04 -13 LFP40 GD1567 2.1500E-03 -13 LFP40 GD1577 1.2200E-03 -13 LFP40 GD1587 1.1100E-03 -13 LFP40 GD1607 3.3000E-04 -13 LFP40 TB1597 5.3000E-04 -13 LFP40 TB1607 2.0000E-05 -13 LFP40 TB1617 2.0000E-05 -13 LFP40 DY1607 2.0000E-05 -13 LFP40 DY1617 2.9000E-04 -13 LFP40 DY1627 3.0000E-05 -13 LFP40 DY1637 0.0000E+00 -13 LFP40 DY1647 0.0000E+00 -13 LFP41 GE76_7 0.0000E+00 -13 LFP41 AS75_7 0.0000E+00 -13 LFP41 SE77_7 1.0000E-04 -13 LFP41 SE78_7 1.9000E-04 -13 LFP41 SE79_7 3.7000E-04 -13 LFP41 SE80_7 6.8000E-04 -13 LFP41 SE82_7 1.7100E-03 -13 LFP41 BR81_7 1.0900E-03 -13 LFP41 BR82_7 0.0000E+00 -13 LFP41 KR82_7 2.0000E-05 -13 LFP41 KR83_7 2.4500E-03 -13 LFP41 KR84_7 3.7700E-03 -13 LFP41 KR85_7 5.0100E-03 -13 LFP41 KR86_7 6.7400E-03 -13 LFP41 RB85_7 1.0000E-04 -13 LFP41 RB86_7 0.0000E+00 -13 LFP41 RB87_7 8.8400E-03 -13 LFP41 SR86_7 0.0000E+00 -13 LFP41 SR88_7 1.1350E-02 -13 LFP41 SR89_7 4.2400E-03 -13 LFP41 SR90_7 1.7520E-02 -13 LFP41 Y89__7 9.6000E-03 -13 LFP41 Y90__7 0.0000E+00 -13 LFP41 Y91__7 7.4600E-03 -13 LFP41 ZR90_7 1.4000E-04 -13 LFP41 ZR91_7 1.3760E-02 -13 LFP41 ZR92_7 2.5790E-02 -13 LFP41 ZR93_7 3.1530E-02 -13 LFP41 ZR94_7 3.7730E-02 -13 LFP41 ZR95_7 1.7010E-02 -13 LFP41 ZR96_7 4.7370E-02 -13 LFP41 NB95_7 8.1100E-03 -13 LFP41 MO95_7 1.9550E-02 -13 LFP41 MO96_7 2.0000E-04 -13 LFP41 MO97_7 5.0800E-02 -13 LFP41 MO98_7 5.4950E-02 -13 LFP41 MO99_7 1.0100E-03 -13 LFP41 MO1007 5.9510E-02 -13 LFP41 TC99_7 5.2940E-02 -13 LFP41 RU1007 1.4400E-03 -13 LFP41 RU1017 5.8760E-02 -13 LFP41 RU1027 6.0990E-02 -13 LFP41 RU1037 1.5260E-02 -13 LFP41 RU1047 5.9000E-02 -13 LFP41 RU1067 4.2670E-02 -13 LFP41 RH1037 4.4040E-02 -13 LFP41 RH1057 5.6000E-04 -13 LFP41 PD1047 1.1300E-03 -13 LFP41 PD1057 5.4300E-02 -13 LFP41 PD1067 1.2000E-02 -13 LFP41 PD1077 4.1100E-02 -13 LFP41 PD1087 3.9120E-02 -13 LFP41 PD1107 1.6910E-02 -13 LFP41 AG1097 2.7580E-02 -13 LFP41 AG1117 4.5000E-04 -13 LFP41 CD1107 6.7000E-04 -13 LFP41 CD1117 8.5400E-03 -13 LFP41 CD1127 4.3300E-03 -13 LFP41 CD1137 2.2200E-03 -13 LFP41 CD1147 1.0300E-03 -13 LFP41 CD1157 1.0000E-05 -13 LFP41 CD15M7 7.0000E-05 -13 LFP41 CD1167 1.0000E-03 -13 LFP41 IN1157 9.7000E-04 -13 LFP41 SN1157 5.0000E-05 -13 LFP41 SN1167 2.0000E-05 -13 LFP41 SN1177 9.0000E-04 -13 LFP41 SN1187 8.7000E-04 -13 LFP41 SN1197 8.6000E-04 -13 LFP41 SN1207 8.5000E-04 -13 LFP41 SN1227 9.3000E-04 -13 LFP41 SN1237 5.7000E-04 -13 LFP41 SN1247 1.1100E-03 -13 LFP41 SN1257 7.0000E-05 -13 LFP41 SN1267 2.4000E-03 -13 LFP41 SB1217 8.8000E-04 -13 LFP41 SB1227 0.0000E+00 -13 LFP41 SB1237 4.3000E-04 -13 LFP41 SB1247 0.0000E+00 -13 LFP41 SB1257 9.5000E-04 -13 LFP41 SB1267 0.0000E+00 -13 LFP41 SB1277 8.0000E-05 -13 LFP41 TE1227 2.0000E-05 -13 LFP41 TE1237 0.0000E+00 -13 LFP41 TE1247 0.0000E+00 -13 LFP41 TE1257 7.0000E-05 -13 LFP41 TE1267 2.0000E-05 -13 LFP41 TE27M7 2.8000E-04 -13 LFP41 TE1287 5.2000E-03 -13 LFP41 TE29M7 1.7900E-03 -13 LFP41 TE1307 1.5340E-02 -13 LFP41 TE1327 9.4000E-04 -13 LFP41 I127_7 2.7700E-03 -13 LFP41 I129_7 6.6400E-03 -13 LFP41 I131_7 1.5000E-03 -13 LFP41 XE1287 7.0000E-05 -13 LFP41 XE1297 0.0000E+00 -13 LFP41 XE1307 1.1000E-04 -13 LFP41 XE1317 2.6830E-02 -13 LFP41 XE1327 4.4050E-02 -13 LFP41 XE1337 1.9100E-03 -13 LFP41 XE1347 6.1020E-02 -13 LFP41 XE1367 7.1120E-02 -13 LFP41 CS1337 5.2310E-02 -13 LFP41 CS1347 1.2100E-03 -13 LFP41 CS1357 6.7050E-02 -13 LFP41 CS1367 5.0000E-05 -13 LFP41 CS1377 7.1810E-02 -13 LFP41 BA1347 8.0000E-05 -13 LFP41 BA1357 0.0000E+00 -13 LFP41 BA1367 3.1000E-04 -13 LFP41 BA1377 5.2000E-04 -13 LFP41 BA1387 6.7380E-02 -13 LFP41 BA1407 4.6100E-03 -13 LFP41 LA1397 6.0930E-02 -13 LFP41 LA1407 6.1000E-04 -13 LFP41 CE1407 5.0310E-02 -13 LFP41 CE1417 1.0740E-02 -13 LFP41 CE1427 4.9900E-02 -13 LFP41 CE1437 5.0000E-04 -13 LFP41 CE1447 3.3360E-02 -13 LFP41 PR1417 4.0060E-02 -13 LFP41 PR1437 4.8100E-03 -13 LFP41 ND1427 2.2000E-04 -13 LFP41 ND1437 4.8540E-02 -13 LFP41 ND1447 1.0750E-02 -13 LFP41 ND1457 3.4770E-02 -13 LFP41 ND1467 3.0510E-02 -13 LFP41 ND1477 1.7600E-03 -13 LFP41 ND1487 1.9290E-02 -13 LFP41 ND1507 1.2900E-02 -13 LFP41 PM1477 1.9790E-02 -13 LFP41 PM1487 5.0000E-05 -13 LFP41 PM48M7 2.3000E-04 -13 LFP41 PM1497 2.4000E-04 -13 LFP41 SM1477 1.5500E-03 -13 LFP41 SM1487 9.6000E-04 -13 LFP41 SM1497 1.5200E-02 -13 LFP41 SM1507 1.0900E-03 -13 LFP41 SM1517 9.1100E-03 -13 LFP41 SM1527 9.3000E-03 -13 LFP41 SM1537 9.0000E-05 -13 LFP41 SM1547 4.4700E-03 -13 LFP41 EU1537 5.7400E-03 -13 LFP41 EU1547 6.1000E-04 -13 LFP41 EU1557 2.9500E-03 -13 LFP41 EU1567 3.0000E-04 -13 LFP41 GD1547 1.0000E-05 -13 LFP41 GD1557 1.2000E-04 -13 LFP41 GD1567 2.4600E-03 -13 LFP41 GD1577 1.3900E-03 -13 LFP41 GD1587 1.3500E-03 -13 LFP41 GD1607 3.9000E-04 -13 LFP41 TB1597 6.2000E-04 -13 LFP41 TB1607 2.0000E-05 -13 LFP41 TB1617 1.0000E-05 -13 LFP41 DY1607 2.0000E-05 -13 LFP41 DY1617 2.0000E-04 -13 LFP41 DY1627 2.0000E-05 -13 LFP41 DY1637 0.0000E+00 -13 LFP41 DY1647 0.0000E+00 -13 DUMP1 DUMMY1 1.0000E-06 -13 DUMP2 DUMMY1 1.0000E-06 \ No newline at end of file +LFP35 GE76 1.500000E-04 +LFP35 AS75 7.000000E-05 +LFP35 SE77 2.900000E-04 +LFP35 SE78 5.600000E-04 +LFP35 SE79 9.100000E-04 +LFP35 SE80 1.510000E-03 +LFP35 SE82 3.900000E-03 +LFP35 BR81 2.460000E-03 +LFP35 BR82 0.000000E+00 +LFP35 KR82 6.000000E-05 +LFP35 KR83 6.010000E-03 +LFP35 KR84 1.084000E-02 +LFP35 KR85 1.380000E-02 +LFP35 KR86 1.928000E-02 +LFP35 RB85 2.900000E-04 +LFP35 RB86 1.000000E-05 +LFP35 RB87 2.548000E-02 +LFP35 SR86 4.000000E-05 +LFP35 SR88 3.641000E-02 +LFP35 SR89 1.278000E-02 +LFP35 SR90 5.075000E-02 +LFP35 Y89 3.156000E-02 +LFP35 Y91 1.763000E-02 +LFP35 ZR90 4.300000E-04 +LFP35 ZR91 3.532000E-02 +LFP35 ZR92 5.639000E-02 +LFP35 ZR93 5.964000E-02 +LFP35 ZR94 6.449000E-02 +LFP35 ZR95 2.315000E-02 +LFP35 ZR96 6.553000E-02 +LFP35 NB95 1.146000E-02 +LFP35 MO95 2.925000E-02 +LFP35 MO96 6.000000E-04 +LFP35 MO97 5.848000E-02 +LFP35 MO98 6.136000E-02 +LFP35 MO99 9.100000E-04 +LFP35 MO100 6.355000E-02 +LFP35 TC99 5.349000E-02 +LFP35 RU100 1.520000E-03 +LFP35 RU101 5.327000E-02 +LFP35 RU102 4.816000E-02 +LFP35 RU103 7.960000E-03 +LFP35 RU104 2.366000E-02 +LFP35 RU106 3.650000E-03 +LFP35 RH103 2.509000E-02 +LFP35 RH105 1.300000E-04 +LFP35 PD104 6.700000E-04 +LFP35 PD105 1.426000E-02 +LFP35 PD106 1.490000E-03 +LFP35 PD107 1.720000E-03 +LFP35 PD108 7.800000E-04 +LFP35 PD110 4.500000E-04 +LFP35 AG109 5.400000E-04 +LFP35 AG111 1.000000E-05 +LFP35 CD110 1.000000E-05 +LFP35 CD111 2.700000E-04 +LFP35 CD112 3.900000E-04 +LFP35 CD113 3.400000E-04 +LFP35 CD114 3.400000E-04 +LFP35 CD115 0.000000E+00 +LFP35 CD115M 1.000000E-05 +LFP35 CD116 3.600000E-04 +LFP35 IN115 2.000000E-04 +LFP35 SN115 1.000000E-05 +LFP35 SN116 0.000000E+00 +LFP35 SN117 3.700000E-04 +LFP35 SN118 3.900000E-04 +LFP35 SN119 4.200000E-04 +LFP35 SN120 4.400000E-04 +LFP35 SN122 4.800000E-04 +LFP35 SN123 3.000000E-04 +LFP35 SN124 6.000000E-04 +LFP35 SN125 4.000000E-05 +LFP35 SN126 1.040000E-03 +LFP35 SB121 4.500000E-04 +LFP35 SB122 0.000000E+00 +LFP35 SB123 2.400000E-04 +LFP35 SB124 0.000000E+00 +LFP35 SB125 6.300000E-04 +LFP35 SB126 1.000000E-05 +LFP35 SB127 5.000000E-05 +LFP35 TE122 1.000000E-05 +LFP35 TE123 0.000000E+00 +LFP35 TE124 0.000000E+00 +LFP35 TE125 5.000000E-05 +LFP35 TE126 6.000000E-05 +LFP35 TE127M 1.700000E-04 +LFP35 TE128 3.210000E-03 +LFP35 TE129M 1.080000E-03 +LFP35 TE130 1.439000E-02 +LFP35 TE132 8.900000E-04 +LFP35 I127 1.740000E-03 +LFP35 I129 4.420000E-03 +LFP35 I131 1.530000E-03 +LFP35 XE128 5.000000E-05 +LFP35 XE129 0.000000E+00 +LFP35 XE130 8.000000E-05 +LFP35 XE131 3.063000E-02 +LFP35 XE132 4.761000E-02 +LFP35 XE133 2.060000E-03 +LFP35 XE134 7.155000E-02 +LFP35 XE136 5.993000E-02 +LFP35 CS133 6.293000E-02 +LFP35 CS134 1.690000E-03 +LFP35 CS135 6.393000E-02 +LFP35 CS136 4.000000E-05 +LFP35 CS137 6.228000E-02 +LFP35 BA134 1.400000E-04 +LFP35 BA135 0.000000E+00 +LFP35 BA136 2.400000E-04 +LFP35 BA137 4.700000E-04 +LFP35 BA138 6.692000E-02 +LFP35 BA140 4.330000E-03 +LFP35 LA139 7.087000E-02 +LFP35 LA140 5.700000E-04 +LFP35 CE140 5.322000E-02 +LFP35 CE141 1.178000E-02 +LFP35 CE142 5.878000E-02 +LFP35 CE143 4.800000E-04 +LFP35 CE144 3.849000E-02 +LFP35 PR141 4.854000E-02 +LFP35 PR143 4.670000E-03 +LFP35 ND142 2.800000E-04 +LFP35 ND143 5.270000E-02 +LFP35 ND144 1.301000E-02 +LFP35 ND145 3.778000E-02 +LFP35 ND146 3.016000E-02 +LFP35 ND147 1.330000E-03 +LFP35 ND148 1.691000E-02 +LFP35 ND150 7.150000E-03 +LFP35 PM147 1.651000E-02 +LFP35 PM148 4.000000E-05 +LFP35 PM148M 2.000000E-04 +LFP35 PM149 1.500000E-04 +LFP35 SM147 1.350000E-03 +LFP35 SM148 8.600000E-04 +LFP35 SM149 1.017000E-02 +LFP35 SM150 7.600000E-04 +LFP35 SM151 4.000000E-03 +LFP35 SM152 3.500000E-03 +LFP35 SM153 2.000000E-05 +LFP35 SM154 9.700000E-04 +LFP35 EU153 1.820000E-03 +LFP35 EU154 2.000000E-04 +LFP35 EU155 3.400000E-04 +LFP35 EU156 2.000000E-05 +LFP35 GD154 0.000000E+00 +LFP35 GD155 1.000000E-05 +LFP35 GD156 1.700000E-04 +LFP35 GD157 7.000000E-05 +LFP35 GD158 6.000000E-05 +LFP35 GD160 1.000000E-05 +LFP35 TB159 3.000000E-05 +LFP35 TB160 0.000000E+00 +LFP35 TB161 0.000000E+00 +LFP35 DY160 0.000000E+00 +LFP35 DY161 0.000000E+00 +LFP35 DY162 0.000000E+00 +LFP35 DY163 0.000000E+00 +LFP35 DY164 0.000000E+00 +LFP38 GE76 1.000000E-05 +LFP38 AS75 0.000000E+00 +LFP38 SE77 4.000000E-05 +LFP38 SE78 1.400000E-04 +LFP38 SE79 4.000000E-04 +LFP38 SE80 8.700000E-04 +LFP38 SE82 2.500000E-03 +LFP38 BR81 1.570000E-03 +LFP38 BR82 0.000000E+00 +LFP38 KR82 3.000000E-05 +LFP38 KR83 3.900000E-03 +LFP38 KR84 5.590000E-03 +LFP38 KR85 7.150000E-03 +LFP38 KR86 1.382000E-02 +LFP38 RB85 1.500000E-04 +LFP38 RB86 0.000000E+00 +LFP38 RB87 1.700000E-02 +LFP38 SR86 0.000000E+00 +LFP38 SR88 2.102000E-02 +LFP38 SR89 9.560000E-03 +LFP38 SR90 3.115000E-02 +LFP38 Y89 2.202000E-02 +LFP38 Y91 1.332000E-02 +LFP38 ZR90 2.600000E-04 +LFP38 ZR91 2.499000E-02 +LFP38 ZR92 4.312000E-02 +LFP38 ZR93 4.700000E-02 +LFP38 ZR94 5.107000E-02 +LFP38 ZR95 2.063000E-02 +LFP38 ZR96 5.744000E-02 +LFP38 NB95 9.910000E-03 +LFP38 MO95 2.417000E-02 +LFP38 MO96 2.500000E-04 +LFP38 MO97 5.804000E-02 +LFP38 MO98 6.127000E-02 +LFP38 MO99 1.120000E-03 +LFP38 MO100 6.151000E-02 +LFP38 TC99 6.007000E-02 +LFP38 RU100 1.650000E-03 +LFP38 RU101 5.944000E-02 +LFP38 RU102 6.361000E-02 +LFP38 RU103 1.479000E-02 +LFP38 RU104 5.702000E-02 +LFP38 RU106 2.337000E-02 +LFP38 RH103 4.346000E-02 +LFP38 RH105 3.400000E-04 +LFP38 PD104 1.120000E-03 +LFP38 PD105 3.348000E-02 +LFP38 PD106 6.790000E-03 +LFP38 PD107 1.234000E-02 +LFP38 PD108 6.840000E-03 +LFP38 PD110 1.390000E-03 +LFP38 AG109 2.010000E-03 +LFP38 AG111 4.000000E-05 +LFP38 CD110 5.000000E-05 +LFP38 CD111 7.300000E-04 +LFP38 CD112 7.100000E-04 +LFP38 CD113 5.500000E-04 +LFP38 CD114 4.600000E-04 +LFP38 CD115 1.000000E-05 +LFP38 CD115M 1.000000E-05 +LFP38 CD116 3.500000E-04 +LFP38 IN115 3.700000E-04 +LFP38 SN115 2.000000E-05 +LFP38 SN116 1.000000E-05 +LFP38 SN117 3.400000E-04 +LFP38 SN118 3.400000E-04 +LFP38 SN119 3.400000E-04 +LFP38 SN120 3.400000E-04 +LFP38 SN122 3.700000E-04 +LFP38 SN123 2.200000E-04 +LFP38 SN124 4.200000E-04 +LFP38 SN125 5.000000E-05 +LFP38 SN126 1.000000E-03 +LFP38 SB121 3.500000E-04 +LFP38 SB122 0.000000E+00 +LFP38 SB123 1.700000E-04 +LFP38 SB124 0.000000E+00 +LFP38 SB125 6.600000E-04 +LFP38 SB126 0.000000E+00 +LFP38 SB127 4.000000E-05 +LFP38 TE122 1.000000E-05 +LFP38 TE123 0.000000E+00 +LFP38 TE124 0.000000E+00 +LFP38 TE125 5.000000E-05 +LFP38 TE126 1.000000E-05 +LFP38 TE127M 1.300000E-04 +LFP38 TE128 5.060000E-03 +LFP38 TE129M 1.890000E-03 +LFP38 TE130 1.959000E-02 +LFP38 TE132 9.500000E-04 +LFP38 I127 1.320000E-03 +LFP38 I129 7.130000E-03 +LFP38 I131 1.850000E-03 +LFP38 XE128 3.000000E-05 +LFP38 XE129 0.000000E+00 +LFP38 XE130 1.200000E-04 +LFP38 XE131 3.391000E-02 +LFP38 XE132 4.546000E-02 +LFP38 XE133 1.840000E-03 +LFP38 XE134 6.172000E-02 +LFP38 XE136 6.292000E-02 +LFP38 CS133 5.152000E-02 +LFP38 CS134 1.200000E-03 +LFP38 CS135 5.627000E-02 +LFP38 CS136 3.000000E-05 +LFP38 CS137 6.949000E-02 +LFP38 BA134 8.000000E-05 +LFP38 BA135 0.000000E+00 +LFP38 BA136 1.600000E-04 +LFP38 BA137 5.100000E-04 +LFP38 BA138 6.477000E-02 +LFP38 BA140 4.930000E-03 +LFP38 LA139 6.083000E-02 +LFP38 LA140 6.500000E-04 +LFP38 CE140 5.506000E-02 +LFP38 CE141 1.124000E-02 +LFP38 CE142 5.054000E-02 +LFP38 CE143 4.200000E-04 +LFP38 CE144 3.424000E-02 +LFP38 PR141 4.277000E-02 +LFP38 PR143 4.030000E-03 +LFP38 ND142 2.400000E-04 +LFP38 ND143 4.163000E-02 +LFP38 ND144 1.104000E-02 +LFP38 ND145 4.086000E-02 +LFP38 ND146 3.897000E-02 +LFP38 ND147 1.860000E-03 +LFP38 ND148 2.359000E-02 +LFP38 ND150 1.464000E-02 +LFP38 PM147 2.131000E-02 +LFP38 PM148 5.000000E-05 +LFP38 PM148M 2.500000E-04 +LFP38 PM149 2.500000E-04 +LFP38 SM147 1.680000E-03 +LFP38 SM148 1.040000E-03 +LFP38 SM149 1.620000E-02 +LFP38 SM150 1.170000E-03 +LFP38 SM151 9.280000E-03 +LFP38 SM152 7.510000E-03 +LFP38 SM153 6.000000E-05 +LFP38 SM154 2.130000E-03 +LFP38 EU153 3.900000E-03 +LFP38 EU154 4.200000E-04 +LFP38 EU155 1.050000E-03 +LFP38 EU156 9.000000E-05 +LFP38 GD154 1.000000E-05 +LFP38 GD155 4.000000E-05 +LFP38 GD156 7.100000E-04 +LFP38 GD157 2.700000E-04 +LFP38 GD158 2.100000E-04 +LFP38 GD160 3.000000E-05 +LFP38 TB159 8.000000E-05 +LFP38 TB160 0.000000E+00 +LFP38 TB161 0.000000E+00 +LFP38 DY160 0.000000E+00 +LFP38 DY161 1.000000E-05 +LFP38 DY162 0.000000E+00 +LFP38 DY163 0.000000E+00 +LFP38 DY164 0.000000E+00 +LFP39 GE76 1.100000E-04 +LFP39 AS75 0.000000E+00 +LFP39 SE77 1.300000E-04 +LFP39 SE78 3.500000E-04 +LFP39 SE79 5.300000E-04 +LFP39 SE80 9.000000E-04 +LFP39 SE82 2.350000E-03 +LFP39 BR81 1.370000E-03 +LFP39 BR82 0.000000E+00 +LFP39 KR82 6.000000E-05 +LFP39 KR83 3.480000E-03 +LFP39 KR84 5.650000E-03 +LFP39 KR85 6.550000E-03 +LFP39 KR86 8.780000E-03 +LFP39 RB85 1.400000E-04 +LFP39 RB86 0.000000E+00 +LFP39 RB87 1.150000E-02 +LFP39 SR86 1.000000E-05 +LFP39 SR88 1.421000E-02 +LFP39 SR89 5.430000E-03 +LFP39 SR90 1.875000E-02 +LFP39 Y89 1.285000E-02 +LFP39 Y91 8.640000E-03 +LFP39 ZR90 1.600000E-04 +LFP39 ZR91 1.660000E-02 +LFP39 ZR92 3.061000E-02 +LFP39 ZR93 3.791000E-02 +LFP39 ZR94 4.274000E-02 +LFP39 ZR95 1.757000E-02 +LFP39 ZR96 4.939000E-02 +LFP39 NB95 8.540000E-03 +LFP39 MO95 2.118000E-02 +LFP39 MO96 2.500000E-04 +LFP39 MO97 5.200000E-02 +LFP39 MO98 5.650000E-02 +LFP39 MO99 9.900000E-04 +LFP39 MO100 6.439000E-02 +LFP39 TC99 5.500000E-02 +LFP39 RU100 1.530000E-03 +LFP39 RU101 6.387000E-02 +LFP39 RU102 6.834000E-02 +LFP39 RU103 1.592000E-02 +LFP39 RU104 6.499000E-02 +LFP39 RU106 3.378000E-02 +LFP39 RH103 4.810000E-02 +LFP39 RH105 4.800000E-04 +LFP39 PD104 1.260000E-03 +LFP39 PD105 4.837000E-02 +LFP39 PD106 1.105000E-02 +LFP39 PD107 3.083000E-02 +LFP39 PD108 2.415000E-02 +LFP39 PD110 9.230000E-03 +LFP39 AG109 1.632000E-02 +LFP39 AG111 2.100000E-04 +LFP39 CD110 4.200000E-04 +LFP39 CD111 4.100000E-03 +LFP39 CD112 1.350000E-03 +LFP39 CD113 9.100000E-04 +LFP39 CD114 1.000000E-03 +LFP39 CD115 1.000000E-05 +LFP39 CD115M 8.000000E-05 +LFP39 CD116 6.400000E-04 +LFP39 IN115 8.900000E-04 +LFP39 SN115 4.000000E-05 +LFP39 SN116 2.000000E-05 +LFP39 SN117 6.400000E-04 +LFP39 SN118 6.500000E-04 +LFP39 SN119 6.600000E-04 +LFP39 SN120 6.700000E-04 +LFP39 SN122 6.900000E-04 +LFP39 SN123 3.900000E-04 +LFP39 SN124 1.200000E-03 +LFP39 SN125 1.100000E-04 +LFP39 SN126 3.030000E-03 +LFP39 SB121 6.600000E-04 +LFP39 SB122 0.000000E+00 +LFP39 SB123 3.000000E-04 +LFP39 SB124 0.000000E+00 +LFP39 SB125 1.630000E-03 +LFP39 SB126 1.000000E-05 +LFP39 SB127 1.200000E-04 +LFP39 TE122 2.000000E-05 +LFP39 TE123 0.000000E+00 +LFP39 TE124 0.000000E+00 +LFP39 TE125 1.200000E-04 +LFP39 TE126 8.000000E-05 +LFP39 TE127M 4.500000E-04 +LFP39 TE128 7.900000E-03 +LFP39 TE129M 2.300000E-03 +LFP39 TE130 1.956000E-02 +LFP39 TE132 1.040000E-03 +LFP39 I127 4.460000E-03 +LFP39 I129 8.970000E-03 +LFP39 I131 2.180000E-03 +LFP39 XE128 1.200000E-04 +LFP39 XE129 0.000000E+00 +LFP39 XE130 1.800000E-04 +LFP39 XE131 4.139000E-02 +LFP39 XE132 5.273000E-02 +LFP39 XE133 2.230000E-03 +LFP39 XE134 7.229000E-02 +LFP39 XE136 6.776000E-02 +LFP39 CS133 6.441000E-02 +LFP39 CS134 1.530000E-03 +LFP39 CS135 7.424000E-02 +LFP39 CS136 9.000000E-05 +LFP39 CS137 6.342000E-02 +LFP39 BA134 1.100000E-04 +LFP39 BA135 0.000000E+00 +LFP39 BA136 8.200000E-04 +LFP39 BA137 5.700000E-04 +LFP39 BA138 4.936000E-02 +LFP39 BA140 4.140000E-03 +LFP39 LA139 6.053000E-02 +LFP39 LA140 5.400000E-04 +LFP39 CE140 4.787000E-02 +LFP39 CE141 1.176000E-02 +LFP39 CE142 4.924000E-02 +LFP39 CE143 3.900000E-04 +LFP39 CE144 2.642000E-02 +LFP39 PR141 4.611000E-02 +LFP39 PR143 3.720000E-03 +LFP39 ND142 2.600000E-04 +LFP39 ND143 3.970000E-02 +LFP39 ND144 8.760000E-03 +LFP39 ND145 3.010000E-02 +LFP39 ND146 2.557000E-02 +LFP39 ND147 1.410000E-03 +LFP39 ND148 1.715000E-02 +LFP39 ND150 1.039000E-02 +LFP39 PM147 1.664000E-02 +LFP39 PM148 4.000000E-05 +LFP39 PM148M 1.900000E-04 +LFP39 PM149 1.900000E-04 +LFP39 SM147 1.330000E-03 +LFP39 SM148 8.300000E-04 +LFP39 SM149 1.265000E-02 +LFP39 SM150 9.300000E-04 +LFP39 SM151 7.610000E-03 +LFP39 SM152 7.590000E-03 +LFP39 SM153 7.000000E-05 +LFP39 SM154 3.210000E-03 +LFP39 EU153 4.630000E-03 +LFP39 EU154 5.100000E-04 +LFP39 EU155 2.140000E-03 +LFP39 EU156 2.000000E-04 +LFP39 GD154 1.000000E-05 +LFP39 GD155 9.000000E-05 +LFP39 GD156 1.660000E-03 +LFP39 GD157 9.300000E-04 +LFP39 GD158 8.900000E-04 +LFP39 GD160 3.300000E-04 +LFP39 TB159 4.300000E-04 +LFP39 TB160 2.000000E-05 +LFP39 TB161 1.000000E-05 +LFP39 DY160 1.000000E-05 +LFP39 DY161 2.000000E-04 +LFP39 DY162 2.000000E-05 +LFP39 DY163 0.000000E+00 +LFP39 DY164 0.000000E+00 +LFP40 GE76 1.000000E-05 +LFP40 AS75 0.000000E+00 +LFP40 SE77 1.400000E-04 +LFP40 SE78 2.900000E-04 +LFP40 SE79 5.300000E-04 +LFP40 SE80 9.100000E-04 +LFP40 SE82 2.090000E-03 +LFP40 BR81 1.480000E-03 +LFP40 BR82 0.000000E+00 +LFP40 KR82 3.000000E-05 +LFP40 KR83 3.120000E-03 +LFP40 KR84 4.580000E-03 +LFP40 KR85 5.890000E-03 +LFP40 KR86 7.930000E-03 +LFP40 RB85 1.200000E-04 +LFP40 RB86 0.000000E+00 +LFP40 RB87 1.019000E-02 +LFP40 SR86 0.000000E+00 +LFP40 SR88 1.281000E-02 +LFP40 SR89 4.990000E-03 +LFP40 SR90 1.964000E-02 +LFP40 Y89 1.110000E-02 +LFP40 Y91 8.470000E-03 +LFP40 ZR90 1.600000E-04 +LFP40 ZR91 1.537000E-02 +LFP40 ZR92 2.930000E-02 +LFP40 ZR93 3.531000E-02 +LFP40 ZR94 4.232000E-02 +LFP40 ZR95 1.773000E-02 +LFP40 ZR96 5.027000E-02 +LFP40 NB95 8.390000E-03 +LFP40 MO95 1.998000E-02 +LFP40 MO96 2.300000E-04 +LFP40 MO97 5.280000E-02 +LFP40 MO98 5.568000E-02 +LFP40 MO99 1.080000E-03 +LFP40 MO100 6.041000E-02 +LFP40 TC99 5.552000E-02 +LFP40 RU100 1.490000E-03 +LFP40 RU101 5.848000E-02 +LFP40 RU102 6.165000E-02 +LFP40 RU103 1.549000E-02 +LFP40 RU104 5.791000E-02 +LFP40 RU106 4.131000E-02 +LFP40 RH103 4.391000E-02 +LFP40 RH105 5.500000E-04 +LFP40 PD104 1.120000E-03 +LFP40 PD105 5.230000E-02 +LFP40 PD106 1.148000E-02 +LFP40 PD107 4.078000E-02 +LFP40 PD108 3.321000E-02 +LFP40 PD110 1.200000E-02 +LFP40 AG109 2.139000E-02 +LFP40 AG111 3.000000E-04 +LFP40 CD110 5.200000E-04 +LFP40 CD111 5.580000E-03 +LFP40 CD112 2.880000E-03 +LFP40 CD113 1.500000E-03 +LFP40 CD114 1.020000E-03 +LFP40 CD115 1.000000E-05 +LFP40 CD115M 8.000000E-05 +LFP40 CD116 8.500000E-04 +LFP40 IN115 8.600000E-04 +LFP40 SN115 4.000000E-05 +LFP40 SN116 2.000000E-05 +LFP40 SN117 8.500000E-04 +LFP40 SN118 8.000000E-04 +LFP40 SN119 8.000000E-04 +LFP40 SN120 8.500000E-04 +LFP40 SN122 9.500000E-04 +LFP40 SN123 6.300000E-04 +LFP40 SN124 1.200000E-03 +LFP40 SN125 1.000000E-04 +LFP40 SN126 3.160000E-03 +LFP40 SB121 8.300000E-04 +LFP40 SB122 0.000000E+00 +LFP40 SB123 4.600000E-04 +LFP40 SB124 0.000000E+00 +LFP40 SB125 1.380000E-03 +LFP40 SB126 1.000000E-05 +LFP40 SB127 1.000000E-04 +LFP40 TE122 2.000000E-05 +LFP40 TE123 0.000000E+00 +LFP40 TE124 0.000000E+00 +LFP40 TE125 9.000000E-05 +LFP40 TE126 5.000000E-05 +LFP40 TE127M 3.300000E-04 +LFP40 TE128 6.180000E-03 +LFP40 TE129M 2.150000E-03 +LFP40 TE130 1.889000E-02 +LFP40 TE132 1.040000E-03 +LFP40 I127 3.180000E-03 +LFP40 I129 7.830000E-03 +LFP40 I131 1.840000E-03 +LFP40 XE128 8.000000E-05 +LFP40 XE129 0.000000E+00 +LFP40 XE130 1.300000E-04 +LFP40 XE131 3.224000E-02 +LFP40 XE132 4.823000E-02 +LFP40 XE133 1.990000E-03 +LFP40 XE134 6.270000E-02 +LFP40 XE136 7.121000E-02 +LFP40 CS133 5.353000E-02 +LFP40 CS134 1.230000E-03 +LFP40 CS135 6.772000E-02 +LFP40 CS136 7.000000E-05 +LFP40 CS137 6.948000E-02 +LFP40 BA134 8.000000E-05 +LFP40 BA135 0.000000E+00 +LFP40 BA136 5.600000E-04 +LFP40 BA137 5.000000E-04 +LFP40 BA138 6.465000E-02 +LFP40 BA140 4.560000E-03 +LFP40 LA139 5.802000E-02 +LFP40 LA140 6.000000E-04 +LFP40 CE140 4.868000E-02 +LFP40 CE141 1.027000E-02 +LFP40 CE142 5.302000E-02 +LFP40 CE143 4.700000E-04 +LFP40 CE144 3.057000E-02 +LFP40 PR141 3.757000E-02 +LFP40 PR143 4.520000E-03 +LFP40 ND142 2.100000E-04 +LFP40 ND143 4.464000E-02 +LFP40 ND144 9.750000E-03 +LFP40 ND145 3.268000E-02 +LFP40 ND146 2.847000E-02 +LFP40 ND147 1.610000E-03 +LFP40 ND148 1.814000E-02 +LFP40 ND150 1.157000E-02 +LFP40 PM147 1.768000E-02 +LFP40 PM148 4.000000E-05 +LFP40 PM148M 2.000000E-04 +LFP40 PM149 2.300000E-04 +LFP40 SM147 1.370000E-03 +LFP40 SM148 8.400000E-04 +LFP40 SM149 1.404000E-02 +LFP40 SM150 9.900000E-04 +LFP40 SM151 8.510000E-03 +LFP40 SM152 8.500000E-03 +LFP40 SM153 8.000000E-05 +LFP40 SM154 4.050000E-03 +LFP40 EU153 5.080000E-03 +LFP40 EU154 5.400000E-04 +LFP40 EU155 2.670000E-03 +LFP40 EU156 2.700000E-04 +LFP40 GD154 1.000000E-05 +LFP40 GD155 1.100000E-04 +LFP40 GD156 2.150000E-03 +LFP40 GD157 1.220000E-03 +LFP40 GD158 1.110000E-03 +LFP40 GD160 3.300000E-04 +LFP40 TB159 5.300000E-04 +LFP40 TB160 2.000000E-05 +LFP40 TB161 2.000000E-05 +LFP40 DY160 2.000000E-05 +LFP40 DY161 2.900000E-04 +LFP40 DY162 3.000000E-05 +LFP40 DY163 0.000000E+00 +LFP40 DY164 0.000000E+00 +LFP41 GE76 0.000000E+00 +LFP41 AS75 0.000000E+00 +LFP41 SE77 1.000000E-04 +LFP41 SE78 1.900000E-04 +LFP41 SE79 3.700000E-04 +LFP41 SE80 6.800000E-04 +LFP41 SE82 1.710000E-03 +LFP41 BR81 1.090000E-03 +LFP41 BR82 0.000000E+00 +LFP41 KR82 2.000000E-05 +LFP41 KR83 2.450000E-03 +LFP41 KR84 3.770000E-03 +LFP41 KR85 5.010000E-03 +LFP41 KR86 6.740000E-03 +LFP41 RB85 1.000000E-04 +LFP41 RB86 0.000000E+00 +LFP41 RB87 8.840000E-03 +LFP41 SR86 0.000000E+00 +LFP41 SR88 1.135000E-02 +LFP41 SR89 4.240000E-03 +LFP41 SR90 1.752000E-02 +LFP41 Y89 9.600000E-03 +LFP41 Y91 7.460000E-03 +LFP41 ZR90 1.400000E-04 +LFP41 ZR91 1.376000E-02 +LFP41 ZR92 2.579000E-02 +LFP41 ZR93 3.153000E-02 +LFP41 ZR94 3.773000E-02 +LFP41 ZR95 1.701000E-02 +LFP41 ZR96 4.737000E-02 +LFP41 NB95 8.110000E-03 +LFP41 MO95 1.955000E-02 +LFP41 MO96 2.000000E-04 +LFP41 MO97 5.080000E-02 +LFP41 MO98 5.495000E-02 +LFP41 MO99 1.010000E-03 +LFP41 MO100 5.951000E-02 +LFP41 TC99 5.294000E-02 +LFP41 RU100 1.440000E-03 +LFP41 RU101 5.876000E-02 +LFP41 RU102 6.099000E-02 +LFP41 RU103 1.526000E-02 +LFP41 RU104 5.900000E-02 +LFP41 RU106 4.267000E-02 +LFP41 RH103 4.404000E-02 +LFP41 RH105 5.600000E-04 +LFP41 PD104 1.130000E-03 +LFP41 PD105 5.430000E-02 +LFP41 PD106 1.200000E-02 +LFP41 PD107 4.110000E-02 +LFP41 PD108 3.912000E-02 +LFP41 PD110 1.691000E-02 +LFP41 AG109 2.758000E-02 +LFP41 AG111 4.500000E-04 +LFP41 CD110 6.700000E-04 +LFP41 CD111 8.540000E-03 +LFP41 CD112 4.330000E-03 +LFP41 CD113 2.220000E-03 +LFP41 CD114 1.030000E-03 +LFP41 CD115 1.000000E-05 +LFP41 CD115M 7.000000E-05 +LFP41 CD116 1.000000E-03 +LFP41 IN115 9.700000E-04 +LFP41 SN115 5.000000E-05 +LFP41 SN116 2.000000E-05 +LFP41 SN117 9.000000E-04 +LFP41 SN118 8.700000E-04 +LFP41 SN119 8.600000E-04 +LFP41 SN120 8.500000E-04 +LFP41 SN122 9.300000E-04 +LFP41 SN123 5.700000E-04 +LFP41 SN124 1.110000E-03 +LFP41 SN125 7.000000E-05 +LFP41 SN126 2.400000E-03 +LFP41 SB121 8.800000E-04 +LFP41 SB122 0.000000E+00 +LFP41 SB123 4.300000E-04 +LFP41 SB124 0.000000E+00 +LFP41 SB125 9.500000E-04 +LFP41 SB126 0.000000E+00 +LFP41 SB127 8.000000E-05 +LFP41 TE122 2.000000E-05 +LFP41 TE123 0.000000E+00 +LFP41 TE124 0.000000E+00 +LFP41 TE125 7.000000E-05 +LFP41 TE126 2.000000E-05 +LFP41 TE127M 2.800000E-04 +LFP41 TE128 5.200000E-03 +LFP41 TE129M 1.790000E-03 +LFP41 TE130 1.534000E-02 +LFP41 TE132 9.400000E-04 +LFP41 I127 2.770000E-03 +LFP41 I129 6.640000E-03 +LFP41 I131 1.500000E-03 +LFP41 XE128 7.000000E-05 +LFP41 XE129 0.000000E+00 +LFP41 XE130 1.100000E-04 +LFP41 XE131 2.683000E-02 +LFP41 XE132 4.405000E-02 +LFP41 XE133 1.910000E-03 +LFP41 XE134 6.102000E-02 +LFP41 XE136 7.112000E-02 +LFP41 CS133 5.231000E-02 +LFP41 CS134 1.210000E-03 +LFP41 CS135 6.705000E-02 +LFP41 CS136 5.000000E-05 +LFP41 CS137 7.181000E-02 +LFP41 BA134 8.000000E-05 +LFP41 BA135 0.000000E+00 +LFP41 BA136 3.100000E-04 +LFP41 BA137 5.200000E-04 +LFP41 BA138 6.738000E-02 +LFP41 BA140 4.610000E-03 +LFP41 LA139 6.093000E-02 +LFP41 LA140 6.100000E-04 +LFP41 CE140 5.031000E-02 +LFP41 CE141 1.074000E-02 +LFP41 CE142 4.990000E-02 +LFP41 CE143 5.000000E-04 +LFP41 CE144 3.336000E-02 +LFP41 PR141 4.006000E-02 +LFP41 PR143 4.810000E-03 +LFP41 ND142 2.200000E-04 +LFP41 ND143 4.854000E-02 +LFP41 ND144 1.075000E-02 +LFP41 ND145 3.477000E-02 +LFP41 ND146 3.051000E-02 +LFP41 ND147 1.760000E-03 +LFP41 ND148 1.929000E-02 +LFP41 ND150 1.290000E-02 +LFP41 PM147 1.979000E-02 +LFP41 PM148 5.000000E-05 +LFP41 PM148M 2.300000E-04 +LFP41 PM149 2.400000E-04 +LFP41 SM147 1.550000E-03 +LFP41 SM148 9.600000E-04 +LFP41 SM149 1.520000E-02 +LFP41 SM150 1.090000E-03 +LFP41 SM151 9.110000E-03 +LFP41 SM152 9.300000E-03 +LFP41 SM153 9.000000E-05 +LFP41 SM154 4.470000E-03 +LFP41 EU153 5.740000E-03 +LFP41 EU154 6.100000E-04 +LFP41 EU155 2.950000E-03 +LFP41 EU156 3.000000E-04 +LFP41 GD154 1.000000E-05 +LFP41 GD155 1.200000E-04 +LFP41 GD156 2.460000E-03 +LFP41 GD157 1.390000E-03 +LFP41 GD158 1.350000E-03 +LFP41 GD160 3.900000E-04 +LFP41 TB159 6.200000E-04 +LFP41 TB160 2.000000E-05 +LFP41 TB161 1.000000E-05 +LFP41 DY160 2.000000E-05 +LFP41 DY161 2.000000E-04 +LFP41 DY162 2.000000E-05 +LFP41 DY163 0.000000E+00 +LFP41 DY164 0.000000E+00 \ No newline at end of file From 05f46aad1ccc2d8e0d0344f2347bcfadc721db5d Mon Sep 17 00:00:00 2001 From: jakehader Date: Mon, 12 Dec 2022 20:00:54 -0800 Subject: [PATCH 02/11] WIP - adding testing --- .../fissionProductModel.py | 58 ++++++++++++------- .../tests/test_fissionProductModel.py | 7 +++ doc/release/0.2.rst | 2 + 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/armi/physics/neutronics/fissionProductModel/fissionProductModel.py b/armi/physics/neutronics/fissionProductModel/fissionProductModel.py index 5753dda07..28a19bc1e 100644 --- a/armi/physics/neutronics/fissionProductModel/fissionProductModel.py +++ b/armi/physics/neutronics/fissionProductModel/fissionProductModel.py @@ -225,8 +225,9 @@ def _getGaseousFissionProductNumberDensities(block, lfp): if not lumpedFissionProduct.isGas(nb): continue yld = lfp[nuc] - block.getNumberDensity(lfp.name) - numberDensities[nuc] = + ndens = block.getNumberDensity(lfp.name) + numberDensities[nuc] = ndens * yld + return numberDensities runLog.info(f"Removing the gaseous fission products from the core.") @@ -248,8 +249,8 @@ def _getGaseousFissionProductNumberDensities(block, lfp): b.updateNumberDensities(updatedNumberDensities) else: - avgGasReleased = 0.0 - totalWeight = 0.0 + avgGasReleased = {} + totalWeight = {} for block in self.r.core.getBlocks(): lfpCollection = block.getLumpedFissionProductCollection() # Skip this block if there is no lumped fission product @@ -258,24 +259,39 @@ def _getGaseousFissionProductNumberDensities(block, lfp): if lfpCollection is None or b not in gasRemovalFractions: continue - # If the lumped fission products are global then we are going - # release the average across all the blocks in the core and these - # this data is collected iteratively. - if self._useGlobalLFPs: - weight = block.getVolume() * (block.p.flux or 1.0) - avgGasReleased += block.p.gasReleaseFraction * weight - totalWeight += weight + + numberDensities = block.getNumberDensities() + for lfp in lfpCollection: + ndens = _getGaseousFissionProductNumberDensities(block, lfp) + removedFraction = gasRemovalFractions[b] - # Otherwise, if the lumped fission products are not global - # go ahead of make the change now. - else: - # set individually - block.getLumpedFissionProductCollection().setGasRemovedFrac( - block.p.gasReleaseFraction - ) + # If the lumped fission products are global then we are going + # release the average across all the blocks in the core and these + # this data is collected iteratively. + if self._useGlobalLFPs: + avgGasReleased[lfp] += sum(ndens.values()) * removedFraction + totalWeight[lfp] += block.getVolume() * (block.p.flux or 1.0) + + # Otherwise, if the lumped fission products are not global + # go ahead of make the change now. + else: + updatedLFPNumberDensity = numberDensities[lfp.name] - sum(ndens.values()) * removedFraction + numberDensities.update({lfp.name: updatedLFPNumberDensity}) + block.setNumberDensities(numberDensities) # adjust global lumps if they exist. if self._useGlobalLFPs and totalWeight: - self.getGlobalLumpedFissionProducts().setGasRemovedFrac( - avgGasReleased / totalWeight - ) \ No newline at end of file + for b in self.r.core.getBlocks(): + lfpCollection = block.getLumpedFissionProductCollection() + # Skip this block if there is no lumped fission product + # collection or this block is not in the dictionary + # of gas removal fractions. + if lfpCollection is None or b not in gasRemovalFractions: + continue + + for lfp in lfpCollection: + updatedLFPNumberDensity = numberDensities[lfp.name] - (avgGasReleased[lfp] / totalWeight[lfp]) + numberDensities.update({lfp.name: updatedLFPNumberDensity}) + block.setNumberDensities(numberDensities) + + \ No newline at end of file diff --git a/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py b/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py index 215e6e470..36ab2bdde 100644 --- a/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py +++ b/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py @@ -48,6 +48,13 @@ def test_getAllFissionProductNames(self): fissionProductNames = self.fpModel.getAllFissionProductNames() self.assertGreater(len(fissionProductNames), 5) self.assertIn("XE135", fissionProductNames) + + def test_removeGaseousFissionProductsLFP(self): + gasRemovalFractions = {} + for b in self.r.core.getBlocks(): + + + self.fpModel.removeFissionGasesFromBlocks() if __name__ == "__main__": diff --git a/doc/release/0.2.rst b/doc/release/0.2.rst index 35013f57a..e63360fe5 100644 --- a/doc/release/0.2.rst +++ b/doc/release/0.2.rst @@ -15,11 +15,13 @@ What's new in ARMI #. Split algorithms specific to hex assemblies out of ``FuelHandler``. (`PR#962 `_) #. Add ability to load from a db using negative node index #. Add group structures for 21- and 94-groups used in photon transport +#. An explicit fission product modeling option was added in `PR#1022 `_. Bug fixes --------- #. Fixed bug in ``referenceBlockAxialMesh`` and ``axialMesh`` during process loading. (`PR#980 `) #. Removed Barriers in temp directory changers and output cache to avoid deadlocks in MPI cases +#. A bug was fixed in `PR#1022 `_ where the gaseous fission products were not being removed from the core directly, but instead the fission yields within the lumped fission products were being adjusted. ARMI v0.2.5 From aa2c0eceb6238b5ba7011f02282a143364e05c5c Mon Sep 17 00:00:00 2001 From: jakehader Date: Wed, 14 Dec 2022 11:20:57 -0800 Subject: [PATCH 03/11] Working on testing. --- .../fissionProductModel.py | 2 ++ .../tests/test_fissionProductModel.py | 36 +++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/armi/physics/neutronics/fissionProductModel/fissionProductModel.py b/armi/physics/neutronics/fissionProductModel/fissionProductModel.py index 28a19bc1e..5a6d73320 100644 --- a/armi/physics/neutronics/fissionProductModel/fissionProductModel.py +++ b/armi/physics/neutronics/fissionProductModel/fissionProductModel.py @@ -264,6 +264,8 @@ def _getGaseousFissionProductNumberDensities(block, lfp): for lfp in lfpCollection: ndens = _getGaseousFissionProductNumberDensities(block, lfp) removedFraction = gasRemovalFractions[b] + if removedFraction < 0.0 or removedFraction > 1.0: + raise ValueError(f"The gaseous removal fraction in {block} is not within [0.0, 1.0].") # If the lumped fission products are global then we are going # release the average across all the blocks in the core and these diff --git a/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py b/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py index 36ab2bdde..4e56db9a5 100644 --- a/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py +++ b/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py @@ -22,6 +22,12 @@ from armi.physics.neutronics.fissionProductModel.tests import test_lumpedFissionProduct +def _getLumpedFissionProductNumberDensities(b): + """Returns the number densities for each lumped fission product in a block.""" + nDens = {} + for lfp in b.getLumpedFissionProductCollection(): + nDens[lfp] = b.getNumberDensity(lfp.name) + return nDens class TestFissionProductModel(unittest.TestCase): """ @@ -38,23 +44,47 @@ def setUp(self): self.fpModel.setAllBlockLFPs() def test_loadGlobalLFPsFromFile(self): - # pylint: disable = protected-access + """Tests that loading lumped fission products from a file.""" self.assertEqual(len(self.fpModel._globalLFPs), 3) lfps = self.fpModel.getGlobalLumpedFissionProducts() self.assertIn("LFP39", lfps) def test_getAllFissionProductNames(self): - # pylint: disable = protected-access + """Tests retrieval of the fission product names within all the lumped fission products of the core.""" fissionProductNames = self.fpModel.getAllFissionProductNames() self.assertGreater(len(fissionProductNames), 5) self.assertIn("XE135", fissionProductNames) def test_removeGaseousFissionProductsLFP(self): + """Tests removal of gaseous fission products globally in the core.""" gasRemovalFractions = {} + previousBlockFissionProductNumberDensities = {} + updatedBlockFissionProductNumberDensities = {} for b in self.r.core.getBlocks(): + lfpCollection = b.getLumpedFissionProductCollection() + if lfpCollection is None: + continue + previousBlockFissionProductNumberDensities[b] = _getLumpedFissionProductNumberDensities(b) + gasRemovalFractions = {b: 0.1} + + self.fpModel.removeFissionGasesFromBlocks(gasRemovalFractions) + for b in self.r.core.getBlocks(): + lfpCollection = b.getLumpedFissionProductCollection() + if lfpCollection is None: + continue + + updatedBlockFissionProductNumberDensities[b] = _getLumpedFissionProductNumberDensities(b) + for lfp in lfpCollection: + old = previousBlockFissionProductNumberDensities[b][lfp] + new = updatedBlockFissionProductNumberDensities[b][lfp] + self.assertAlmostEqual(new, old * (1.0 - gasRemovalFractions[b])) + + + + def test_removeGaseousFissionProductsLFPFailure(self): + """Tests failure when the gaseous removal fractions are out of range.""" - self.fpModel.removeFissionGasesFromBlocks() if __name__ == "__main__": From 545f00237e896885ff359bc36c3f0349beac5ccc Mon Sep 17 00:00:00 2001 From: jakehader Date: Tue, 20 Dec 2022 18:17:38 -0800 Subject: [PATCH 04/11] Finish fission product model updates for explicit fission product model treatment with fission gas removal implemented --- armi/cases/case.py | 52 +-- armi/operators/settingsValidation.py | 12 +- armi/physics/neutronics/__init__.py | 7 +- .../fissionProductModel/__init__.py | 4 +- .../fissionProductModel.py | 164 +++++--- .../fissionProductModelSettings.py | 69 +++- .../lumpedFissionProduct.py | 69 ++-- .../tests/test_fissionProductModel.py | 367 ++++++++++++++++-- .../tests/test_lumpedFissionProduct.py | 39 +- .../tests/test_cross_section_manager.py | 6 - armi/reactor/tests/test_assemblies.py | 2 +- armi/resources/referenceFissionProducts.dat | 40 +- armi/settings/caseSettings.py | 8 +- armi/settings/fwSettings/globalSettings.py | 1 - 14 files changed, 645 insertions(+), 195 deletions(-) diff --git a/armi/cases/case.py b/armi/cases/case.py index 35b7f2197..20d72324c 100644 --- a/armi/cases/case.py +++ b/armi/cases/case.py @@ -540,7 +540,7 @@ def _initBurnChain(self): """ if not self.cs["initializeBurnChain"]: runLog.info( - "Skipping burn-chain initialization due disabling of the `initializeBurnChain` setting." + "Skipping burn-chain initialization since `initializeBurnChain` setting is disabled." ) return @@ -563,38 +563,38 @@ def checkInputs(self): bool True if the inputs are all good, False otherwise """ + runLog.header("=========== Settings Validation Checks ===========") with DirectoryChanger(self.cs.inputDirectory, dumpOnException=False): operatorClass = operators.getOperatorClassFromSettings(self.cs) inspector = operatorClass.inspector(self.cs) inspectorIssues = [query for query in inspector.queries if query] - if context.CURRENT_MODE == context.Mode.INTERACTIVE: - # if interactive, ask user to deal with settings issues - inspector.run() - else: - # when not interactive, just print out the info in the stdout - queryData = [] - for i, query in enumerate(inspectorIssues, start=1): - queryData.append( - ( - i, - textwrap.fill( - query.statement, width=50, break_long_words=False - ), - textwrap.fill( - query.question, width=50, break_long_words=False - ), - ) + + # Write out the settings validation issues that will be prompted for + # resolution if in an interactive session or forced to be resolved + # otherwise. + queryData = [] + for i, query in enumerate(inspectorIssues, start=1): + queryData.append( + ( + i, + textwrap.fill( + query.statement, width=50, break_long_words=False + ), + textwrap.fill(query.question, width=50, break_long_words=False), ) + ) - if queryData and context.MPI_RANK == 0: - runLog.header("=========== Settings Input Queries ===========") - runLog.info( - tabulate.tabulate( - queryData, - headers=["Number", "Statement", "Question"], - tablefmt="armi", - ) + if queryData and context.MPI_RANK == 0: + runLog.info( + tabulate.tabulate( + queryData, + headers=["Number", "Statement", "Question"], + tablefmt="armi", ) + ) + if context.CURRENT_MODE == context.Mode.INTERACTIVE: + # if interactive, ask user to deal with settings issues + inspector.run() return not any(inspectorIssues) diff --git a/armi/operators/settingsValidation.py b/armi/operators/settingsValidation.py index 92f307091..c3ea0129a 100644 --- a/armi/operators/settingsValidation.py +++ b/armi/operators/settingsValidation.py @@ -32,7 +32,6 @@ from armi.reactor import systemLayoutInput from armi.physics import neutronics from armi.utils import directoryChangers -from armi.settings.fwSettings import globalSettings from armi.settings.settingsIO import ( prompt, RunLogPromptCancel, @@ -67,7 +66,6 @@ def __init__(self, condition, statement, question, correction): self.correction = correction # True if the query is `passed` and does not result in an immediate failure self._passed = False - self._corrected = False self.autoResolved = True def __repr__(self): @@ -107,9 +105,7 @@ def resolve(self): ) if make_correction: self.correction() - self._corrected = True - else: - self._passed = True + self._passed = True except RunLogPromptCancel as ki: raise KeyboardInterrupt from ki else: @@ -191,7 +187,6 @@ def run(self, cs=None): # the following attribute changes will alter what the queries investigate when # resolved - correctionsMade = False self.cs = cs or self.cs runLog.debug("{} executing queries.".format(self.__class__.__name__)) if not any(self.queries): @@ -203,8 +198,6 @@ def run(self, cs=None): else: for query in self.queries: query.resolve() - if query._corrected: # pylint: disable=protected-access - correctionsMade = True issues = [ query for query in self.queries @@ -221,8 +214,6 @@ def run(self, cs=None): ) runLog.debug("{} has finished querying.".format(self.__class__.__name__)) - return correctionsMade - def addQuery(self, condition, statement, question, correction): """Convenience method, query must be resolved, else run fails""" if not callable(correction): @@ -262,6 +253,7 @@ def _assignCS(self, key, value): """Lambda assignment workaround""" # this type of assignment works, but be mindful of # scoping when trying different methods + runLog.extra(f"Updating setting `{key}` to `{value}`") self.cs[key] = value def _raise(self): # pylint: disable=no-self-use diff --git a/armi/physics/neutronics/__init__.py b/armi/physics/neutronics/__init__.py index ef089bc46..b35434eb9 100644 --- a/armi/physics/neutronics/__init__.py +++ b/armi/physics/neutronics/__init__.py @@ -102,8 +102,13 @@ def defineSettings(): def defineSettingsValidators(inspector): """Implementation of settings inspections for neutronics settings.""" from armi.physics.neutronics.settings import getNeutronicsSettingValidators + from armi.physics.neutronics.fissionProductModel.fissionProductModelSettings import ( + getFissionProductModelSettingValidators, + ) - return getNeutronicsSettingValidators(inspector) + settingsValidators = getNeutronicsSettingValidators(inspector) + settingsValidators.extend(getFissionProductModelSettingValidators(inspector)) + return settingsValidators @staticmethod @plugins.HOOKIMPL diff --git a/armi/physics/neutronics/fissionProductModel/__init__.py b/armi/physics/neutronics/fissionProductModel/__init__.py index fe62f66ca..b4428c630 100644 --- a/armi/physics/neutronics/fissionProductModel/__init__.py +++ b/armi/physics/neutronics/fissionProductModel/__init__.py @@ -17,8 +17,8 @@ """ import os -from armi import ROOT +from armi.context import RES REFERENCE_LUMPED_FISSION_PRODUCT_FILE = os.path.join( - ROOT, "resources", "referenceFissionProducts.dat" + RES, "referenceFissionProducts.dat" ) diff --git a/armi/physics/neutronics/fissionProductModel/fissionProductModel.py b/armi/physics/neutronics/fissionProductModel/fissionProductModel.py index 5a6d73320..b690f2778 100644 --- a/armi/physics/neutronics/fissionProductModel/fissionProductModel.py +++ b/armi/physics/neutronics/fissionProductModel/fissionProductModel.py @@ -38,6 +38,8 @@ lfp35.keys() """ +import collections + from armi import runLog from armi import interfaces @@ -95,7 +97,8 @@ def _fissionProductBlockType(self): blockType = None if self.getInterface("mcnp") is not None else Flags.FUEL if blockType is None and self._explicitFissionProducts: raise ValueError( - f"Expicit fission products model is not compatible with the MCNP interface." + f"The explicit fission products model is not compatible with the MCNP interface. " + f"Select another `fpModel` option." ) return blockType @@ -167,9 +170,6 @@ def interactBOC(self, cycle=None): """ self.setAllBlockLFPs() - def interactEveryNode(self, _cycle, _node): - self.updateFissionGasRemovalFractions() - def interactDistributeState(self): self.setAllBlockLFPs() @@ -183,7 +183,7 @@ def getAllFissionProductNames(self): sets fissionProductNames, a list of nuclide names of all the fission products """ - runLog.debug(" Gathering all possible FPs") + runLog.debug("Gathering all possible fission products that are modeled.") fissionProductNames = [] lfpCollections = [] # get all possible lfp collections (global + block-level) @@ -216,84 +216,128 @@ def removeFissionGasesFromBlocks(self, gasRemovalFractions: dict): of each of the blocks and the gaseous fission products are not moved or displaced to another area in the core. """ - - def _getGaseousFissionProductNumberDensities(block, lfp): - """Look into a single lumped fission product object and pull out just the gaseous atom number densities.""" + if not self.cs["fgRemoval"]: + runLog.info( + "Skipping removal of gaseous fission products since the `fgRemoval` setting is disabled." + ) + return + + runLog.info(f"Removing the gaseous fission products from the core.") + if not isinstance(gasRemovalFractions, dict): + raise TypeError(f"The gas removal fractions input is not a dictionary.") + + # Check that the gas removal fractions supplied are within [0.0, 1.0] and cap them + # at these limits otherwise. + updatedGasRemovalFractions = {} + for b, frac in gasRemovalFractions.items(): + if frac < 0.0: + runLog.warning( + f"The fission gas removal fraction is less than zero for {b}. Setting to zero." + ) + updatedGasRemovalFractions[b] = 0.0 + elif frac > 1.0: + runLog.warning( + f"The fission gas removal fraction is greater than one for {b}. Setting to one." + ) + updatedGasRemovalFractions[b] = 1.0 + else: + updatedGasRemovalFractions[b] = frac + gasRemovalFractions.update(updatedGasRemovalFractions) + + def _getGaseousFissionProductNumberDensities(b, lfp): + """Look into a single lumped fission product object and pull out the gaseous atom number densities.""" numberDensities = {} - for nuc in lfp.keys(): - nb = nuclideBases.byName[nuc] + for nb in lfp.keys(): if not lumpedFissionProduct.isGas(nb): continue - yld = lfp[nuc] - ndens = block.getNumberDensity(lfp.name) - numberDensities[nuc] = ndens * yld + yld = lfp[nb] + ndens = b.getNumberDensity(lfp.name) + numberDensities[nb.name] = ndens * yld return numberDensities - - - runLog.info(f"Removing the gaseous fission products from the core.") - if not isinstance(gasRemovalFractions, dict): - raise TypeError(f"The gas removal fractions input is not a dictionary.") - if self._explicitFissionProducts: - for b in self.r.core.getBlocks(): + def _removeFissionGasesExplicitFissionProductModeling( + core, gasRemovalFractions + ): + """ + This is called when `explicitFissionProducts` are selected as the fission product model. + + Notes + ----- + The input provided has the amount of gas to be removed by each block in the core. The + gas that remains in a block is first calculated and then the number density of the + gaseous nuclides is multiplied by the fraction that remains and the update number + density vector for the block is updated. + """ + for b in core.getBlocks(): if b not in gasRemovalFractions: continue - updatedNumberDensities = {} removedFraction = gasRemovalFractions[b] remainingFraction = 1.0 - removedFraction + updatedNumberDensities = {} for nuc, val in b.getNumberDensities().items(): nb = nuclideBases.byName[nuc] - if lumpedFissionProduct.isGas(nb): - val = remainingFraction * val - updatedNumberDensities[nuc] = val + updatedNumberDensities[nuc] = ( + remainingFraction * val + if lumpedFissionProduct.isGas(nb) + else val + ) b.updateNumberDensities(updatedNumberDensities) - - else: - avgGasReleased = {} - totalWeight = {} - for block in self.r.core.getBlocks(): - lfpCollection = block.getLumpedFissionProductCollection() - # Skip this block if there is no lumped fission product - # collection or this block is not in the dictionary - # of gas removal fractions. + + def _removeFissionGasesLumpedFissionProductModeling(core, gasRemovalFractions): + weightedGasRemoved = collections.defaultdict(float) + totalWeight = collections.defaultdict(float) + for b in core.getBlocks(): + lfpCollection = b.getLumpedFissionProductCollection() if lfpCollection is None or b not in gasRemovalFractions: continue - - - numberDensities = block.getNumberDensities() - for lfp in lfpCollection: - ndens = _getGaseousFissionProductNumberDensities(block, lfp) + + numberDensities = b.getNumberDensities() + for lfp in lfpCollection.values(): + ndens = _getGaseousFissionProductNumberDensities(b, lfp) removedFraction = gasRemovalFractions[b] - if removedFraction < 0.0 or removedFraction > 1.0: - raise ValueError(f"The gaseous removal fraction in {block} is not within [0.0, 1.0].") - + remainingFraction = 1.0 - removedFraction + # If the lumped fission products are global then we are going - # release the average across all the blocks in the core and these + # release the average across all the blocks in the core and # this data is collected iteratively. - if self._useGlobalLFPs: - avgGasReleased[lfp] += sum(ndens.values()) * removedFraction - totalWeight[lfp] += block.getVolume() * (block.p.flux or 1.0) - + if self._useGlobalLFPs: + weight = b.getVolume() * (b.p.flux or 1.0) + weightedGasRemoved[lfp] += ( + sum(ndens.values()) * removedFraction * weight + ) + totalWeight[lfp] += weight + # Otherwise, if the lumped fission products are not global # go ahead of make the change now. else: - updatedLFPNumberDensity = numberDensities[lfp.name] - sum(ndens.values()) * removedFraction + updatedLFPNumberDensity = ( + numberDensities[lfp.name] * remainingFraction + ) numberDensities.update({lfp.name: updatedLFPNumberDensity}) - block.setNumberDensities(numberDensities) - - # adjust global lumps if they exist. + b.setNumberDensities(numberDensities) + + # Apply the global updates to the fission products uniformly across the core. if self._useGlobalLFPs and totalWeight: for b in self.r.core.getBlocks(): - lfpCollection = block.getLumpedFissionProductCollection() - # Skip this block if there is no lumped fission product - # collection or this block is not in the dictionary - # of gas removal fractions. + lfpCollection = b.getLumpedFissionProductCollection() if lfpCollection is None or b not in gasRemovalFractions: continue - - for lfp in lfpCollection: - updatedLFPNumberDensity = numberDensities[lfp.name] - (avgGasReleased[lfp] / totalWeight[lfp]) + + for lfp in lfpCollection.values(): + # The updated number density is calculated as the current number density subtracting + # the amount of gaseous fission products that are being removed. + updatedLFPNumberDensity = b.getNumberDensity(lfp.name) - ( + weightedGasRemoved[lfp] / totalWeight[lfp] + ) numberDensities.update({lfp.name: updatedLFPNumberDensity}) - block.setNumberDensities(numberDensities) - - \ No newline at end of file + b.setNumberDensities(numberDensities) + + if self._explicitFissionProducts: + _removeFissionGasesExplicitFissionProductModeling( + self.r.core, gasRemovalFractions + ) + + else: + _removeFissionGasesLumpedFissionProductModeling( + self.r.core, gasRemovalFractions + ) diff --git a/armi/physics/neutronics/fissionProductModel/fissionProductModelSettings.py b/armi/physics/neutronics/fissionProductModel/fissionProductModelSettings.py index 2b2a614f4..e755d2d1b 100644 --- a/armi/physics/neutronics/fissionProductModel/fissionProductModelSettings.py +++ b/armi/physics/neutronics/fissionProductModel/fissionProductModelSettings.py @@ -39,7 +39,7 @@ def defineSettings(): ), setting.Setting( CONF_FISSION_PRODUCT_LIBRARY_NAME, - default="MC2-3", + default="", label="Fission Product Library", description=( f"This setting is used when the `{CONF_FP_MODEL}` setting " @@ -52,6 +52,7 @@ def defineSettings(): f"inputs when modifying the fission product treatment for calculations." ), options=[ + "", "MC2-3", ], ), @@ -72,3 +73,69 @@ def defineSettings(): ), ] return settings + + +def getFissionProductModelSettingValidators(inspector): + """The standard helper method, to provide validators to the fission product model.""" + + # Import the Query class here to avoid circular imports. + from armi.operators.settingsValidation import Query + + queries = [] + + queries.append( + Query( + lambda: inspector.cs["fpModel"] != "explicitFissionProducts" + and not bool(inspector.cs["initializeBurnChain"]), + ( + f"The burn chain is not being initialized and the fission product model is not set to `explicitFissionProducts`. " + f"This will likely fail." + ), + (f"Would you like to set the `fpModel` to `explicitFissionProducts`?"), + lambda: inspector._assignCS("fpModel", "explicitFissionProducts"), + ) + ) + + queries.append( + Query( + lambda: inspector.cs["fpModel"] != "explicitFissionProducts" + and inspector.cs["fpModelLibrary"] != "", + ( + f"The explicit fission product model is disabled and the fission product model library is set. This will have no " + f"impact on the results, but it is best to disable the `fpModelLibrary` option." + ), + (f"Would you like to do this?"), + lambda: inspector._assignCS("fpModelLibrary", ""), + ) + ) + + queries.append( + Query( + lambda: inspector.cs["fpModel"] == "explicitFissionProducts" + and bool(inspector.cs["initializeBurnChain"]), + ( + f"The explicit fission product model is enabled, but initializing the burn chain is also enabled. This will " + f"likely fail." + ), + (f"Would you like to disable the burn chain initialization?"), + lambda: inspector._assignCS("initializeBurnChain", False), + ) + ) + + queries.append( + Query( + lambda: inspector.cs["fpModel"] == "explicitFissionProducts" + and inspector.cs["fpModelLibrary"] == "", + ( + f"The explicit fission product model is enabled and the fission product model library is disabled. This will result " + f"in a failure. Note that the fission product model library will determine which nuclides to add to the depletable regions of the core " + f"that are not already included in the blueprints `nuclideFlags`." + ), + ( + f"Would you like to set the `fpModelLibrary` option to be equal to the default implementation of MC2-3?." + ), + lambda: inspector._assignCS("fpModelLibrary", "MC2-3"), + ) + ) + + return queries diff --git a/armi/physics/neutronics/fissionProductModel/lumpedFissionProduct.py b/armi/physics/neutronics/fissionProductModel/lumpedFissionProduct.py index 9e2676ca6..d3d36f8cf 100644 --- a/armi/physics/neutronics/fissionProductModel/lumpedFissionProduct.py +++ b/armi/physics/neutronics/fissionProductModel/lumpedFissionProduct.py @@ -77,7 +77,7 @@ def duplicate(self): new.yld[key] = val return new - def __getitem__(self, fissionProduct, default=None): + def __getitem__(self, fissionProduct): """ Return the yield of a particular fission product. @@ -85,19 +85,27 @@ def __getitem__(self, fissionProduct, default=None): Returns ------- - yld : yield of the fission product. Defaults to None. + yld : yield of the fission product. """ - yld = self.yld.get(fissionProduct, default) - return yld + return self.yld.get(fissionProduct, 0.0) def __setitem__(self, key, val): + if val < 0.0: + raise ValueError( + f"Cannot set the yield of {key} in {self} to be less than zero as this is non-physical." + ) + if val > 2.0: + raise ValueError( + f"Cannot set the yield of {key} in {self} to be greater than two as this is non-physical." + ) + self.yld[key] = val def __contains__(self, item): return item in self.yld def __repr__(self): - return "".format(self.name) + return f"" def keys(self): return self.yld.keys() @@ -109,6 +117,15 @@ def items(self): for nuc in self.keys(): yield nuc, self[nuc] + def getGaseousYieldFraction(self): + """Return the yield fraction of the gaseous nuclides.""" + yld = 0.0 + for nuc in self.keys(): + if not isGas(nuc): + continue + yld += self[nuc] + return yld + def getTotalYield(self): """ Get the fractional yield of all nuclides in this lumped fission product @@ -149,9 +166,7 @@ def getMassFrac( massFracDenom = self.getMassFracDenom(useCache=useCache, storeCache=storeCache) if not nuclideBase: nuclideBase = nuclideBases.byName[nucName] - return self.__getitem__(nuclideBase, default=0) * ( - nuclideBase.weight / massFracDenom - ) + return self.__getitem__(nuclideBase) * (nuclideBase.weight / massFracDenom) def getMassFracDenom(self, useCache=True, storeCache=True): """ @@ -185,7 +200,19 @@ def getExpandedMass(self, mass=1.0): return massVector def printDensities(self, lfpDens): - """Print densities of nuclides given a LFP density.""" + """ + Print number densities of nuclides within the lumped fission product. + + Parameters + ---------- + lfpDens : float + Number density (atom/b-cm) of the lumped fission product + + Notes + ----- + This multiplies the provided number density for the lumped fission + product by the yield of each nuclide. + """ for n in sorted(self.keys()): runLog.info("{0:6s} {1:.7E}".format(n.name, lfpDens * self[n])) @@ -198,7 +225,6 @@ class LumpedFissionProductCollection(dict): """ def __init__(self): - super(LumpedFissionProductCollection, self).__init__() self.collapsible = False def duplicate(self): @@ -378,10 +404,11 @@ def _readOneLFP(self, linesOfOneLFP): def lumpedFissionProductFactory(cs): """Build lumped fission products.""" if cs["fpModel"] == "explicitFissionProducts": + runLog.info("Fission products will be modeled explicitly.") return None if cs["fpModel"] == "MO99": - runLog.warning( + runLog.info( "Activating MO99-fission product model. All FPs are treated a MO99!" ) return _buildMo99LumpedFissionProduct() @@ -400,24 +427,6 @@ def lumpedFissionProductFactory(cs): return lfps -def _buildExplictFissionProducts(cs, modeledNuclides): - """ - Build a LFP collection that is a single fission product for each - additional nuclide not already initialized in the nuclide flags. - """ - lfpCollections = LumpedFissionProductCollection() - allNuclideBases = getAllNuclideBasesByLibrary(cs) - modeledNuclideBases = [nuclideBases.byName[nuc] for nuc in modeledNuclides] - fissionProductNuclideBases = set(allNuclideBases).difference( - set(modeledNuclideBases) - ) - for nb in fissionProductNuclideBases: - addedFissionProduct = LumpedFissionProduct(nb.name) - addedFissionProduct[nb] = 1.0 - lfpCollections[nb.name] = addedFissionProduct - return lfpCollections - - def getAllNuclideBasesByLibrary(cs): """Return a list of nuclide bases that are available for a given `fpModelLibrary`.""" nbs = [] @@ -427,7 +436,7 @@ def getAllNuclideBasesByLibrary(cs): else: raise ValueError( f"An option to handle the `fpModelLibrary` " - f"set to {cs['fpModelLibrary']} has not been " + f"set to `{cs['fpModelLibrary']}` has not been " f"implemented." ) return nbs diff --git a/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py b/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py index 4e56db9a5..14243011a 100644 --- a/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py +++ b/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py @@ -11,37 +11,50 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """ Test the fission product module to ensure all FP are available. """ import unittest +from ordered_set import OrderedSet +from armi import nuclideBases +from armi.physics.neutronics.fissionProductModel import lumpedFissionProduct from armi.physics.neutronics.fissionProductModel import fissionProductModel from armi.reactor.tests.test_reactors import buildOperatorOfEmptyHexBlocks from armi.physics.neutronics.fissionProductModel.tests import test_lumpedFissionProduct + def _getLumpedFissionProductNumberDensities(b): """Returns the number densities for each lumped fission product in a block.""" nDens = {} - for lfp in b.getLumpedFissionProductCollection(): - nDens[lfp] = b.getNumberDensity(lfp.name) + for lfpName, lfp in b.getLumpedFissionProductCollection().items(): + nDens[lfp] = b.getNumberDensity(lfpName) return nDens -class TestFissionProductModel(unittest.TestCase): + +class TestFissionProductModelLumpedFissionProducts(unittest.TestCase): """ - Test for the fission product model, ensures LFP model contains appropriate FPs. + Tests the fission product model interface behavior when lumped fission products are enabled. + + Notes + ----- + This loads the global fission products from a file stream. """ def setUp(self): o = buildOperatorOfEmptyHexBlocks() - self.fpModel = fissionProductModel.FissionProductModel(o.r, o.cs) o.removeAllInterfaces() + self.fpModel = fissionProductModel.FissionProductModel(o.r, o.cs) o.addInterface(self.fpModel) + + # Load the fission products from a file stream. dummyLFPs = test_lumpedFissionProduct.getDummyLFPFile() self.fpModel.setGlobalLumpedFissionProducts(dummyLFPs.createLFPsFromFile()) + + # Set up the global LFPs and check that they are setup. self.fpModel.setAllBlockLFPs() + self.assertTrue(self.fpModel._useGlobalLFPs) def test_loadGlobalLFPsFromFile(self): """Tests that loading lumped fission products from a file.""" @@ -54,37 +67,341 @@ def test_getAllFissionProductNames(self): fissionProductNames = self.fpModel.getAllFissionProductNames() self.assertGreater(len(fissionProductNames), 5) self.assertIn("XE135", fissionProductNames) - - def test_removeGaseousFissionProductsLFP(self): - """Tests removal of gaseous fission products globally in the core.""" + + +class TestFissionProductModelNoGasRemoval(unittest.TestCase): + """Tests that no gaseous fission products are removed when the `fgRemoval` settings is disabled.""" + + def setUp(self): + o = buildOperatorOfEmptyHexBlocks() + o.removeAllInterfaces() + o.cs["fgRemoval"] = False + + self.core = o.r.core + self.fpModel = fissionProductModel.FissionProductModel(o.r, o.cs) + o.addInterface(self.fpModel) + + # Set up the global LFPs and check that they are setup. + self.fpModel.setAllBlockLFPs() + self.assertTrue(self.fpModel._useGlobalLFPs) + + # Setup the blocks to have the same lumped fission product densities. + for b in self.core.getBlocks(): + if b.getLumpedFissionProductCollection() is None: + continue + for lfp in b.getLumpedFissionProductCollection(): + updatedNumberDensities = b.getNumberDensities() + updatedNumberDensities[lfp] = 1e5 + b.setNumberDensities(updatedNumberDensities) + + def test_removeAllGaseousFissionProductsLFP(self): + """ + Same as ``TestFissionProductModelGasRemovalLumpedFissionProducts.test_removeAllGaseousFissionProductsLFP`` + but the `fgRemoval` setting is disabled so there is expected to be no change. + """ + gasRemovalFractions = {} + previousBlockFissionProductNumberDensities = {} + updatedBlockFissionProductNumberDensities = {} + + # Get the initial fission product number densities and setup the gas removal fractions. + for b in self.core.getBlocks(): + lfpCollection = b.getLumpedFissionProductCollection() + if lfpCollection is None: + continue + + previousBlockFissionProductNumberDensities[ + b + ] = _getLumpedFissionProductNumberDensities(b) + gasRemovalFractions = {b: 1.0} + + # Remove the fission gases + self.fpModel.removeFissionGasesFromBlocks(gasRemovalFractions) + + # Check that the fission gases were removed correctly. + for b in self.core.getBlocks(): + lfpCollection = b.getLumpedFissionProductCollection() + if lfpCollection is None: + continue + + updatedBlockFissionProductNumberDensities[ + b + ] = _getLumpedFissionProductNumberDensities(b) + for lfp in lfpCollection.values(): + old = previousBlockFissionProductNumberDensities[b][lfp] + new = updatedBlockFissionProductNumberDensities[b][lfp] + self.assertAlmostEqual(new, old) + + +class TestFissionProductModelGasRemovalLumpedFissionProducts(unittest.TestCase): + """ + Tests the fission product model interface behavior when lumped fission products are enabled. + + Notes + ----- + This loads the global fission products from the default file. + """ + + def setUp(self): + o = buildOperatorOfEmptyHexBlocks() + o.removeAllInterfaces() + o.cs["fgRemoval"] = True + + self.core = o.r.core + self.fpModel = fissionProductModel.FissionProductModel(o.r, o.cs) + o.addInterface(self.fpModel) + + # Set up the global LFPs and check that they are setup. + self.fpModel.setAllBlockLFPs() + self.assertTrue(self.fpModel._useGlobalLFPs) + + # Setup the blocks to have the same lumped fission product densities. + for b in self.core.getBlocks(): + if b.getLumpedFissionProductCollection() is None: + continue + for lfp in b.getLumpedFissionProductCollection(): + updatedNumberDensities = b.getNumberDensities() + updatedNumberDensities[lfp] = 1e5 + b.setNumberDensities(updatedNumberDensities) + + def test_removeZeroGaseousFissionProductsLFP(self): + """Tests removal of gaseous fission products globally in the core with a removal fraction of zero.""" + gasRemovalFractions = {} + previousBlockFissionProductNumberDensities = {} + updatedBlockFissionProductNumberDensities = {} + + # Get the initial fission product number densities and setup the gas removal fractions. + for b in self.core.getBlocks(): + lfpCollection = b.getLumpedFissionProductCollection() + if lfpCollection is None: + continue + + previousBlockFissionProductNumberDensities[ + b + ] = _getLumpedFissionProductNumberDensities(b) + gasRemovalFractions = {b: 0.0} + + # Remove the fission gases + self.fpModel.removeFissionGasesFromBlocks(gasRemovalFractions) + + # Check that the fission gases were removed correctly. + for b in self.core.getBlocks(): + lfpCollection = b.getLumpedFissionProductCollection() + if lfpCollection is None: + continue + + updatedBlockFissionProductNumberDensities[ + b + ] = _getLumpedFissionProductNumberDensities(b) + for lfp in lfpCollection.values(): + old = previousBlockFissionProductNumberDensities[b][lfp] + new = updatedBlockFissionProductNumberDensities[b][lfp] + + # The expected result should be the previous number densities for the lumped fission + # product within the block that is subtracted by the gas removal fraction multiplied + # by the fraction of gas atoms produced from fission. + result = old - ( + old * gasRemovalFractions[b] * lfp.getGaseousYieldFraction() + ) + self.assertAlmostEqual(new, result) + self.assertEqual(new, old) + + def test_removeHalfGaseousFissionProductsLFP(self): + """Tests removal of gaseous fission products globally in the core with a removal fraction of 0.5.""" gasRemovalFractions = {} previousBlockFissionProductNumberDensities = {} updatedBlockFissionProductNumberDensities = {} - for b in self.r.core.getBlocks(): + + # Get the initial fission product number densities and setup the gas removal fractions. + for b in self.core.getBlocks(): lfpCollection = b.getLumpedFissionProductCollection() if lfpCollection is None: continue - - previousBlockFissionProductNumberDensities[b] = _getLumpedFissionProductNumberDensities(b) - gasRemovalFractions = {b: 0.1} - + + previousBlockFissionProductNumberDensities[ + b + ] = _getLumpedFissionProductNumberDensities(b) + gasRemovalFractions = {b: 0.5} + + # Remove the fission gases self.fpModel.removeFissionGasesFromBlocks(gasRemovalFractions) - for b in self.r.core.getBlocks(): + + # Check that the fission gases were removed correctly. + for b in self.core.getBlocks(): lfpCollection = b.getLumpedFissionProductCollection() if lfpCollection is None: continue - - updatedBlockFissionProductNumberDensities[b] = _getLumpedFissionProductNumberDensities(b) - for lfp in lfpCollection: + + updatedBlockFissionProductNumberDensities[ + b + ] = _getLumpedFissionProductNumberDensities(b) + for lfp in lfpCollection.values(): old = previousBlockFissionProductNumberDensities[b][lfp] new = updatedBlockFissionProductNumberDensities[b][lfp] - self.assertAlmostEqual(new, old * (1.0 - gasRemovalFractions[b])) - - - - def test_removeGaseousFissionProductsLFPFailure(self): - """Tests failure when the gaseous removal fractions are out of range.""" - + + # The expected result should be the previous number densities for the lumped fission + # product within the block that is subtracted by the gas removal fraction multiplied + # by the fraction of gas atoms produced from fission. + result = old - ( + old * gasRemovalFractions[b] * lfp.getGaseousYieldFraction() + ) + self.assertAlmostEqual(new, result) + + def test_removeAllGaseousFissionProductsLFP(self): + """Tests removal of gaseous fission products globally in the core with a removal fraction of 1.0.""" + gasRemovalFractions = {} + previousBlockFissionProductNumberDensities = {} + updatedBlockFissionProductNumberDensities = {} + + # Get the initial fission product number densities and setup the gas removal fractions. + for b in self.core.getBlocks(): + lfpCollection = b.getLumpedFissionProductCollection() + if lfpCollection is None: + continue + + previousBlockFissionProductNumberDensities[ + b + ] = _getLumpedFissionProductNumberDensities(b) + gasRemovalFractions = {b: 1.0} + + # Remove the fission gases + self.fpModel.removeFissionGasesFromBlocks(gasRemovalFractions) + + # Check that the fission gases were removed correctly. + for b in self.core.getBlocks(): + lfpCollection = b.getLumpedFissionProductCollection() + if lfpCollection is None: + continue + + updatedBlockFissionProductNumberDensities[ + b + ] = _getLumpedFissionProductNumberDensities(b) + for lfp in lfpCollection.values(): + old = previousBlockFissionProductNumberDensities[b][lfp] + new = updatedBlockFissionProductNumberDensities[b][lfp] + + # The expected result should be the previous number densities for the lumped fission + # product within the block that is subtracted by the gas removal fraction multiplied + # by the fraction of gas atoms produced from fission. + result = old - ( + old * gasRemovalFractions[b] * lfp.getGaseousYieldFraction() + ) + self.assertAlmostEqual(new, result) + + +class TestFissionProductModelGasRemovalExplicitFissionProducts(unittest.TestCase): + """Tests the fission product model interface behavior when `explicitFissionProducts` are enabled.""" + + def setUp(self): + o = buildOperatorOfEmptyHexBlocks() + o.removeAllInterfaces() + o.cs["fgRemoval"] = True + o.cs["fpModel"] = "explicitFissionProducts" + o.cs["fpModelLibrary"] = "MC2-3" + + nuclidesToAdd = [] + for nb in lumpedFissionProduct.getAllNuclideBasesByLibrary(o.cs): + nuclidesToAdd.append(nb.name) + o.r.blueprints.allNuclidesInProblem = OrderedSet(sorted(nuclidesToAdd)) + + self.core = o.r.core + self.fpModel = fissionProductModel.FissionProductModel(o.r, o.cs) + o.addInterface(self.fpModel) + + # Set up the global LFPs and check that they are setup. + self.fpModel.setAllBlockLFPs() + self.assertFalse(self.fpModel._useGlobalLFPs) + + # Setup the blocks to have the same lumped fission product densities. + for b in self.core.getBlocks(): + updatedNumberDensities = b.getNumberDensities() + for nuc in b.getNuclides(): + nb = nuclideBases.byName[nuc] + if not lumpedFissionProduct.isGas(nb): + continue + updatedNumberDensities[nuc] = 1e5 + b.setNumberDensities(updatedNumberDensities) + + def test_removeZeroGaseousFissionProducts(self): + """Tests removal of gaseous fission products in the core with a removal fraction of zero.""" + gasRemovalFractions = {} + previousBlockFissionProductNumberDensities = {} + updatedBlockFissionProductNumberDensities = {} + + # Get the initial fission product number densities and setup the gas removal fractions. + for b in self.core.getBlocks(): + previousBlockFissionProductNumberDensities[b] = b.getNumberDensities() + gasRemovalFractions = {b: 0.0} + + # Remove the fission gases + self.fpModel.removeFissionGasesFromBlocks(gasRemovalFractions) + + # Check that the fission gases were removed correctly. + for b in self.core.getBlocks(): + updatedBlockFissionProductNumberDensities[b] = b.getNumberDensities() + for nuc in b.getNuclides(): + nb = nuclideBases.byName[nuc] + if not lumpedFissionProduct.isGas(nb): + continue + + old = previousBlockFissionProductNumberDensities[b][nuc] + new = updatedBlockFissionProductNumberDensities[b][nuc] + result = old - (old * gasRemovalFractions[b]) + self.assertAlmostEqual(new, result) + self.assertAlmostEqual(new, old) + + def test_removeHalfGaseousFissionProducts(self): + """Tests removal of gaseous fission products in the core with a removal fraction of 0.5.""" + gasRemovalFractions = {} + previousBlockFissionProductNumberDensities = {} + updatedBlockFissionProductNumberDensities = {} + + # Get the initial fission product number densities and setup the gas removal fractions. + for b in self.core.getBlocks(): + previousBlockFissionProductNumberDensities[b] = b.getNumberDensities() + gasRemovalFractions = {b: 0.5} + + # Remove the fission gases + self.fpModel.removeFissionGasesFromBlocks(gasRemovalFractions) + + # Check that the fission gases were removed correctly. + for b in self.core.getBlocks(): + updatedBlockFissionProductNumberDensities[b] = b.getNumberDensities() + for nuc in b.getNuclides(): + nb = nuclideBases.byName[nuc] + if not lumpedFissionProduct.isGas(nb): + continue + + old = previousBlockFissionProductNumberDensities[b][nuc] + new = updatedBlockFissionProductNumberDensities[b][nuc] + result = old - (old * gasRemovalFractions[b]) + self.assertAlmostEqual(new, result) + + def test_removeAllGaseousFissionProducts(self): + """Tests removal of gaseous fission products in the core with a removal fraction of 1.0.""" + gasRemovalFractions = {} + previousBlockFissionProductNumberDensities = {} + updatedBlockFissionProductNumberDensities = {} + + # Get the initial fission product number densities and setup the gas removal fractions. + for b in self.core.getBlocks(): + previousBlockFissionProductNumberDensities[b] = b.getNumberDensities() + gasRemovalFractions = {b: 1.0} + + # Remove the fission gases + self.fpModel.removeFissionGasesFromBlocks(gasRemovalFractions) + + # Check that the fission gases were removed correctly. + for b in self.core.getBlocks(): + updatedBlockFissionProductNumberDensities[b] = b.getNumberDensities() + for nuc in b.getNuclides(): + nb = nuclideBases.byName[nuc] + if not lumpedFissionProduct.isGas(nb): + continue + + old = previousBlockFissionProductNumberDensities[b][nuc] + new = updatedBlockFissionProductNumberDensities[b][nuc] + result = old - (old * gasRemovalFractions[b]) + self.assertAlmostEqual(new, result) if __name__ == "__main__": diff --git a/armi/physics/neutronics/fissionProductModel/tests/test_lumpedFissionProduct.py b/armi/physics/neutronics/fissionProductModel/tests/test_lumpedFissionProduct.py index 0b2af5ef2..18852b63f 100644 --- a/armi/physics/neutronics/fissionProductModel/tests/test_lumpedFissionProduct.py +++ b/armi/physics/neutronics/fissionProductModel/tests/test_lumpedFissionProduct.py @@ -18,11 +18,14 @@ import unittest import io import math +import os from armi.physics.neutronics.fissionProductModel import ( lumpedFissionProduct, REFERENCE_LUMPED_FISSION_PRODUCT_FILE, ) +from armi.context import RES +from armi.settings import Settings from armi.reactor.tests.test_reactors import buildOperatorOfEmptyHexBlocks from armi.reactor.flags import Flags from armi.nucDirectory import nuclideBases @@ -102,10 +105,10 @@ def test_getYield(self): """Test of the yield of a fission product""" xe135 = nuclideBases.fromName("XE135") lfp = self.fpd.createSingleLFPFromFile("LFP39") - lfp[xe135] = 3 + lfp[xe135] = 1.2 val3 = lfp[xe135] - self.assertEqual(val3, 3) - self.assertIsNone(lfp[5]) + self.assertEqual(val3, 1.2) + self.assertEqual(lfp[5], 0.0) def test_getExpandedMass(self): xe135 = nuclideBases.fromName("XE135") @@ -140,10 +143,6 @@ def test_getAllFissionProductNuclideBases(self): self.assertIn(xe135, clideBases) self.assertIn(kr85, clideBases) - def test_getGasRemovedFrac(self): - val = self.lfps.getGasRemovedFrac() - self.assertEqual(val, 0.0) - def test_duplicate(self): """Test to ensure that when we duplicate, we don't adjust the original file""" newLfps = self.lfps.duplicate() @@ -151,7 +150,7 @@ def test_duplicate(self): lfp1 = self.lfps["LFP39"] lfp2 = newLfps["LFP39"] v1 = lfp1[ba] - lfp1[ba] += 5.0 # make sure copy doesn't change w/ first. + lfp1[ba] += 1.3 # make sure copy doesn't change w/ first. v2 = lfp2[ba] self.assertEqual(v1, v2) @@ -188,6 +187,30 @@ def test_getMassFrac(self): self.assertAlmostEqual(newMassFrac, refMassFrac[fp.name]) +class TestLumpedFissionProductsFromReferenceFile(unittest.TestCase): + """Tests loading from the `referenceFissionProducts.dat` file.""" + + def test_fissionProductYields(self): + """Test that the fission product yields for the lumped fission products sums to 2.0""" + cs = Settings() + cs["fpModel"] = "infinitelyDilute" + cs["lfpCompositionFilePath"] = os.path.join(RES, "referenceFissionProducts.dat") + self.lfps = lumpedFissionProduct.lumpedFissionProductFactory(cs) + for lfp in self.lfps.values(): + self.assertAlmostEqual(lfp.getTotalYield(), 2.0, places=3) + + +class TestLumpedFissionProductsExplicit(unittest.TestCase): + """Tests loading fission products with explicit modeling.""" + + def test_explicitFissionProducts(self): + """Tests that there are no lumped fission products added when the `explicitFissionProducts` model is enabled.""" + cs = Settings() + cs["fpModel"] = "explicitFissionProducts" + self.lfps = lumpedFissionProduct.lumpedFissionProductFactory(cs) + self.assertIsNone(self.lfps) + + class TestMo99LFP(unittest.TestCase): """Test of the fission product model from Mo99""" diff --git a/armi/physics/neutronics/tests/test_cross_section_manager.py b/armi/physics/neutronics/tests/test_cross_section_manager.py index 9c183b45b..e9fb25b5c 100644 --- a/armi/physics/neutronics/tests/test_cross_section_manager.py +++ b/armi/physics/neutronics/tests/test_cross_section_manager.py @@ -109,9 +109,6 @@ def test_createRepresentativeBlock(self): # 0 + 1/4 + 2/4 + 3/4 + 4/4 = # (0 + 1 + 2 + 3 + 4 ) / 5 = 10/5 = 2.0 self.assertAlmostEqual(avgB.getNumberDensity("U235"), 2.0) - lfps = avgB.getLumpedFissionProductCollection() - lfp = list(lfps.values())[0] - self.assertAlmostEqual(lfp.gasRemainingFrac, 0.5) class TestBlockCollectionComponentAverage(unittest.TestCase): @@ -226,9 +223,6 @@ def test_createRepresentativeBlock(self): avgB = self.bc.createRepresentativeBlock() self.assertNotIn(avgB, self.bc) self.assertAlmostEqual(avgB.getNumberDensity("U235"), 1.0) - lfps = avgB.getLumpedFissionProductCollection() - lfp = list(lfps.values())[0] - self.assertAlmostEqual(lfp.gasRemainingFrac, 0.75) def test_invalidWeights(self): self.bc[0].p.flux = 0.0 diff --git a/armi/reactor/tests/test_assemblies.py b/armi/reactor/tests/test_assemblies.py index 01fb8871b..4f080a652 100644 --- a/armi/reactor/tests/test_assemblies.py +++ b/armi/reactor/tests/test_assemblies.py @@ -704,7 +704,7 @@ def test_getBlockData(self): "residence": 3.145, "eqRegion": -1, "id": 299.0, - "bondRemoved": 33.7, + "bondRemoved": 0.337, "buRate": 42.0, } # Set some params diff --git a/armi/resources/referenceFissionProducts.dat b/armi/resources/referenceFissionProducts.dat index 11773b769..067f5207c 100644 --- a/armi/resources/referenceFissionProducts.dat +++ b/armi/resources/referenceFissionProducts.dat @@ -56,7 +56,7 @@ LFP35 CD111 2.7000E-04 LFP35 CD112 3.9000E-04 LFP35 CD113 3.4000E-04 LFP35 CD114 3.4000E-04 -LFP35 CD15M 1.0000E-05 +LFP35 CD115M 1.0000E-05 LFP35 CD116 3.6000E-04 LFP35 IN115 2.0000E-04 LFP35 SN115 1.0000E-05 @@ -76,9 +76,9 @@ LFP35 SB126 1.0000E-05 LFP35 TE122 1.0000E-05 LFP35 TE125 5.0000E-05 LFP35 TE126 6.0000E-05 -LFP35 TE27M 1.7000E-04 +LFP35 TE127M 1.7000E-04 LFP35 TE128 3.2100E-03 -LFP35 TE29M 1.0800E-03 +LFP35 TE129M 1.0800E-03 LFP35 TE130 1.4390E-02 LFP35 TE132 8.9000E-04 LFP35 I127 1.7400E-03 @@ -120,7 +120,7 @@ LFP35 ND148 1.6910E-02 LFP35 ND150 7.1500E-03 LFP35 PM147 1.6510E-02 LFP35 PM148 4.0000E-05 -LFP35 PM48M 2.0000E-04 +LFP35 PM148M 2.0000E-04 LFP35 PM149 1.5000E-04 LFP35 SM147 1.3500E-03 LFP35 SM148 8.6000E-04 @@ -195,7 +195,7 @@ LFP38 CD111 7.3000E-04 LFP38 CD112 7.1000E-04 LFP38 CD113 5.5000E-04 LFP38 CD114 4.6000E-04 -LFP38 CD15M 1.0000E-05 +LFP38 CD115M 1.0000E-05 LFP38 CD116 3.5000E-04 LFP38 IN115 3.7000E-04 LFP38 SN115 2.0000E-05 @@ -215,9 +215,9 @@ LFP38 SB125 6.6000E-04 LFP38 TE122 1.0000E-05 LFP38 TE125 5.0000E-05 LFP38 TE126 1.0000E-05 -LFP38 TE27M 1.3000E-04 +LFP38 TE127M 1.3000E-04 LFP38 TE128 5.0600E-03 -LFP38 TE29M 1.8900E-03 +LFP38 TE129M 1.8900E-03 LFP38 TE130 1.9590E-02 LFP38 TE132 9.5000E-04 LFP38 I127 1.3200E-03 @@ -259,7 +259,7 @@ LFP38 ND148 2.3590E-02 LFP38 ND150 1.4640E-02 LFP38 PM147 2.1310E-02 LFP38 PM148 5.0000E-05 -LFP38 PM48M 2.5000E-04 +LFP38 PM148M 2.5000E-04 LFP38 PM149 2.5000E-04 LFP38 SM147 1.6800E-03 LFP38 SM148 1.0400E-03 @@ -337,7 +337,7 @@ LFP39 CD111 4.1000E-03 LFP39 CD112 1.3500E-03 LFP39 CD113 9.1000E-04 LFP39 CD114 1.0000E-03 -LFP39 CD15M 8.0000E-05 +LFP39 CD115M 8.0000E-05 LFP39 CD116 6.4000E-04 LFP39 IN115 8.9000E-04 LFP39 SN115 4.0000E-05 @@ -358,9 +358,9 @@ LFP39 SB126 1.0000E-05 LFP39 TE122 2.0000E-05 LFP39 TE125 1.2000E-04 LFP39 TE126 8.0000E-05 -LFP39 TE27M 4.5000E-04 +LFP39 TE127M 4.5000E-04 LFP39 TE128 7.9000E-03 -LFP39 TE29M 2.3000E-03 +LFP39 TE129M 2.3000E-03 LFP39 TE130 1.9560E-02 LFP39 TE132 1.0400E-03 LFP39 I127 4.4600E-03 @@ -402,7 +402,7 @@ LFP39 ND148 1.7150E-02 LFP39 ND150 1.0390E-02 LFP39 PM147 1.6640E-02 LFP39 PM148 4.0000E-05 -LFP39 PM48M 1.9000E-04 +LFP39 PM148M 1.9000E-04 LFP39 PM149 1.9000E-04 LFP39 SM147 1.3300E-03 LFP39 SM148 8.3000E-04 @@ -482,7 +482,7 @@ LFP40 CD111 5.5800E-03 LFP40 CD112 2.8800E-03 LFP40 CD113 1.5000E-03 LFP40 CD114 1.0200E-03 -LFP40 CD15M 8.0000E-05 +LFP40 CD115M 8.0000E-05 LFP40 CD116 8.5000E-04 LFP40 IN115 8.6000E-04 LFP40 SN115 4.0000E-05 @@ -503,9 +503,9 @@ LFP40 SB126 1.0000E-05 LFP40 TE122 2.0000E-05 LFP40 TE125 9.0000E-05 LFP40 TE126 5.0000E-05 -LFP40 TE27M 3.3000E-04 +LFP40 TE127M 3.3000E-04 LFP40 TE128 6.1800E-03 -LFP40 TE29M 2.1500E-03 +LFP40 TE129M 2.1500E-03 LFP40 TE130 1.8890E-02 LFP40 TE132 1.0400E-03 LFP40 I127 3.1800E-03 @@ -547,7 +547,7 @@ LFP40 ND148 1.8140E-02 LFP40 ND150 1.1570E-02 LFP40 PM147 1.7680E-02 LFP40 PM148 4.0000E-05 -LFP40 PM48M 2.0000E-04 +LFP40 PM148M 2.0000E-04 LFP40 PM149 2.3000E-04 LFP40 SM147 1.3700E-03 LFP40 SM148 8.4000E-04 @@ -626,7 +626,7 @@ LFP41 CD111 8.5400E-03 LFP41 CD112 4.3300E-03 LFP41 CD113 2.2200E-03 LFP41 CD114 1.0300E-03 -LFP41 CD15M 7.0000E-05 +LFP41 CD115M 7.0000E-05 LFP41 CD116 1.0000E-03 LFP41 IN115 9.7000E-04 LFP41 SN115 5.0000E-05 @@ -646,9 +646,9 @@ LFP41 SB125 9.5000E-04 LFP41 TE122 2.0000E-05 LFP41 TE125 7.0000E-05 LFP41 TE126 2.0000E-05 -LFP41 TE27M 2.8000E-04 +LFP41 TE127M 2.8000E-04 LFP41 TE128 5.2000E-03 -LFP41 TE29M 1.7900E-03 +LFP41 TE129M 1.7900E-03 LFP41 TE130 1.5340E-02 LFP41 TE132 9.4000E-04 LFP41 I127 2.7700E-03 @@ -690,7 +690,7 @@ LFP41 ND148 1.9290E-02 LFP41 ND150 1.2900E-02 LFP41 PM147 1.9790E-02 LFP41 PM148 5.0000E-05 -LFP41 PM48M 2.3000E-04 +LFP41 PM148M 2.3000E-04 LFP41 PM149 2.4000E-04 LFP41 SM147 1.5500E-03 LFP41 SM148 9.6000E-04 diff --git a/armi/settings/caseSettings.py b/armi/settings/caseSettings.py index cda9e8f4a..126ad6f78 100644 --- a/armi/settings/caseSettings.py +++ b/armi/settings/caseSettings.py @@ -168,10 +168,10 @@ def _directAccessOfSettingAllowed(self, key): if key in SIMPLE_CYCLES_INPUTS and self.__settings["cycles"].value != []: err = ValueError( - "Cannot grab simple cycles information from the case settings" - " when detailed cycles information is also entered.\n In general" - " cycles information should be pulled off the operator or parsed" - " using the appropriate getter in the utils." + "Cannot grab simple cycles information from the case settings " + "when detailed cycles information is also entered. In general " + "cycles information should be pulled off the operator or parsed " + "using the appropriate getter in the utils. " ) return False, err diff --git a/armi/settings/fwSettings/globalSettings.py b/armi/settings/fwSettings/globalSettings.py index 6cf31c328..ae2a9c9b5 100644 --- a/armi/settings/fwSettings/globalSettings.py +++ b/armi/settings/fwSettings/globalSettings.py @@ -134,7 +134,6 @@ def defineSettings() -> List[setting.Setting]: CONF_INITIALIZE_BURN_CHAIN, default=True, label="Initialize Burn Chain", - schema=vol.Coerce(type(bool)), description=( f"This setting is paired with the `{CONF_BURN_CHAIN_FILE_NAME}` setting. " f"When enabled, this will initialize the burn-chain on initializing the case and " From 75b427824fb5697728f85f6a72515629997b7034 Mon Sep 17 00:00:00 2001 From: jakehader Date: Tue, 20 Dec 2022 18:57:04 -0800 Subject: [PATCH 05/11] Update setting descriptions for the fission product model. --- .../fissionProductModelSettings.py | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/armi/physics/neutronics/fissionProductModel/fissionProductModelSettings.py b/armi/physics/neutronics/fissionProductModel/fissionProductModelSettings.py index e755d2d1b..5016c61d9 100644 --- a/armi/physics/neutronics/fissionProductModel/fissionProductModelSettings.py +++ b/armi/physics/neutronics/fissionProductModel/fissionProductModelSettings.py @@ -29,7 +29,15 @@ def defineSettings(): CONF_FP_MODEL, default="infinitelyDilute", label="Fission Product Model", - description="", + description=( + "This setting is used to determine how fission products are treated in an analysis. " + "By choosing `noFissionProducts`, no fission products will be added. By selecting, `infinitelyDilute`, " + "lumped fission products will be initialized to a very small number on the blocks/components that require them. " + "By choosing `MO99`, the fission products will be represented only by Mo-99. This is a simplistic assumption that " + "is commonly used by fast reactor analyses in scoping calculations and is not necessarily a great assumption for " + "depletion evaluations. Finally, by choosing `explicitFissionProducts` the fission products will be added explicitly " + "to the blocks/components that are depletable. This is useful for detailed tracking of fission products." + ), options=[ "noFissionProducts", "infinitelyDilute", @@ -49,7 +57,9 @@ def defineSettings(): f"selected code library (i.e., MC2-3) within the blueprints " f"`nuclideFlags` to be [xs:true, burn:false]. This option acts " f"as a short-cut so that analysts do not need to change their " - f"inputs when modifying the fission product treatment for calculations." + f"inputs when modifying the fission product treatment for " + f"calculations. This may be extended for other cross section " + f"generation codes." ), options=[ "", @@ -60,7 +70,10 @@ def defineSettings(): CONF_MAKE_ALL_BLOCK_LFPS_INDEPENDENT, default=False, label="Use Independent LFPs", - description="Flag to make all blocks have independent lumped fission products", + description=( + "Flag to make all blocks have independent lumped fission products. Note that this is forced to be True " + "when the `explicitFissionProducts` modeling option is selected." + ), ), setting.Setting( CONF_LFP_COMPOSITION_FILE_PATH, @@ -68,7 +81,8 @@ def defineSettings(): label="LFP Definition File", description=( "Path to the file that contains lumped fission product composition " - "definitions (e.g. equilibrium yields)" + "definitions (e.g. equilibrium yields). This is unused when the " + "`explicitFissionProducts` or `MO99` modeling options are selected." ), ), ] From 1ea3c2c9ba64b302f1213410f7de3caaefe4257b Mon Sep 17 00:00:00 2001 From: jakehader Date: Wed, 21 Dec 2022 14:23:19 -0800 Subject: [PATCH 06/11] Updates to the mcc-nuclides to fix ZR94 label for MC2-2 and updating the nucDirectory to get the displacement energies from the element symbol rather than the element object --- armi/nucDirectory/nucDir.py | 2 +- armi/resources/mcc-nuclides.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/armi/nucDirectory/nucDir.py b/armi/nucDirectory/nucDir.py index 44a79daa9..a0e667557 100644 --- a/armi/nucDirectory/nucDir.py +++ b/armi/nucDirectory/nucDir.py @@ -362,7 +362,7 @@ def getThresholdDisplacementEnergy(nuc): nuc = getNuclide(nuc) el = elements.byZ[nuc.z] try: - ed = eDisplacement[el] + ed = eDisplacement[el.symbol] except KeyError: print( "The element {0} of nuclide {1} does not have a displacement energy in the library. Please add one." diff --git a/armi/resources/mcc-nuclides.yaml b/armi/resources/mcc-nuclides.yaml index 8ac1994f1..8ba519a08 100644 --- a/armi/resources/mcc-nuclides.yaml +++ b/armi/resources/mcc-nuclides.yaml @@ -1229,7 +1229,7 @@ ZR93: ENDF/B-V.2: ZR93 5 ENDF/B-VII.0: ZR93_7 ZR94: - ENDF/B-V.2: ZR94 5 + ENDF/B-V.2: ZR94SV ENDF/B-VII.0: ZR94_7 ZR95: ENDF/B-V.2: ZR95 5 From 8e70edbb72ee45fa342e95c357da187e4b0b5b23 Mon Sep 17 00:00:00 2001 From: jakehader Date: Wed, 21 Dec 2022 16:14:50 -0800 Subject: [PATCH 07/11] Removing exception on MCNP within the fission product model interface after some more testing was done. --- .../neutronics/fissionProductModel/fissionProductModel.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/armi/physics/neutronics/fissionProductModel/fissionProductModel.py b/armi/physics/neutronics/fissionProductModel/fissionProductModel.py index b690f2778..ebf5afe47 100644 --- a/armi/physics/neutronics/fissionProductModel/fissionProductModel.py +++ b/armi/physics/neutronics/fissionProductModel/fissionProductModel.py @@ -94,13 +94,7 @@ def _fissionProductBlockType(self): products be consistent across all blocks, even if fission products are not generated when the block is depleted. """ - blockType = None if self.getInterface("mcnp") is not None else Flags.FUEL - if blockType is None and self._explicitFissionProducts: - raise ValueError( - f"The explicit fission products model is not compatible with the MCNP interface. " - f"Select another `fpModel` option." - ) - return blockType + return None if self.getInterface("mcnp") is not None else Flags.FUEL def interactBOL(self): interfaces.Interface.interactBOL(self) From 317271d70fe551da2f148e2655fadcb719af809d Mon Sep 17 00:00:00 2001 From: jakehader Date: Thu, 22 Dec 2022 16:01:12 -0800 Subject: [PATCH 08/11] Update. Still more comments to address. Decided to ditch the fission gas removal code because it wasn't working correctly. --- armi/physics/fuelPerformance/executers.py | 2 + armi/physics/fuelPerformance/parameters.py | 18 +- armi/physics/fuelPerformance/settings.py | 7 + .../fissionProductModel.py | 253 +++++-------- .../fissionProductModelSettings.py | 3 +- .../lumpedFissionProduct.py | 32 +- .../tests/test_fissionProductModel.py | 338 ------------------ armi/reactor/blueprints/__init__.py | 29 +- armi/reactor/blueprints/isotopicOptions.py | 50 +++ 9 files changed, 168 insertions(+), 564 deletions(-) diff --git a/armi/physics/fuelPerformance/executers.py b/armi/physics/fuelPerformance/executers.py index fd0549a01..2b24b9ce9 100644 --- a/armi/physics/fuelPerformance/executers.py +++ b/armi/physics/fuelPerformance/executers.py @@ -30,6 +30,7 @@ CONF_FGR_REMOVAL, CONF_CLADDING_WASTAGE, CONF_CLADDING_STRAIN, + CONF_FGYF, ) @@ -53,6 +54,7 @@ def fromUserSettings(self, cs): self.fissionGasRemoval = cs[CONF_FGR_REMOVAL] self.claddingWastage = cs[CONF_CLADDING_WASTAGE] self.claddingStrain = cs[CONF_CLADDING_STRAIN] + self.fissionGasYieldFraction = cs[CONF_FGYF] def fromReactor(self, reactor): """Load options from reactor.""" diff --git a/armi/physics/fuelPerformance/parameters.py b/armi/physics/fuelPerformance/parameters.py index 0584a8473..276bc2049 100644 --- a/armi/physics/fuelPerformance/parameters.py +++ b/armi/physics/fuelPerformance/parameters.py @@ -27,14 +27,6 @@ def _getFuelPerformanceBlockParams(): pDefs = parameters.ParameterDefinitionCollection() with pDefs.createBuilder(default=0.0, location=ParamLocation.AVERAGE) as pb: - def gasReleaseFraction(self, value): - if value < 0.0 or value > 1.0: - raise ValueError( - f"Cannot set a gas release fraction " - f"of {value} outside of the bounds of [0.0, 1.0]" - ) - self._p_gasReleaseFraction = value - pb.defParam( "fuelCladLocked", units="", @@ -44,11 +36,19 @@ def gasReleaseFraction(self, value): " thermal and/or burn-up expansion of the fuel and cladding materials.", ) + def gasReleaseFraction(self, value): + if value < 0.0 or value > 1.0: + raise ValueError( + f"Cannot set a gas release fraction " + f"of {value} outside of the bounds of [0.0, 1.0]" + ) + self._p_gasReleaseFraction = value + pb.defParam( "gasReleaseFraction", setter=gasReleaseFraction, units="fraction", - description="Fraction of generated fission gas that no longer exists in the block", + description="Fraction of generated fission gas that no longer exists in the block.", categories=["eq cumulative shift"], ) diff --git a/armi/physics/fuelPerformance/settings.py b/armi/physics/fuelPerformance/settings.py index 2d9f66d34..727da19b1 100644 --- a/armi/physics/fuelPerformance/settings.py +++ b/armi/physics/fuelPerformance/settings.py @@ -23,6 +23,7 @@ CONF_CLADDING_STRAIN = "claddingStrain" CONF_CLADDING_WASTAGE = "claddingWastage" CONF_FGR_REMOVAL = "fgRemoval" +CONF_FGYF = "fissionGasYieldFraction" CONF_FUEL_PERFORMANCE_ENGINE = "fuelPerformanceEngine" @@ -39,6 +40,12 @@ def defineSettings(): ), options=[""], ), + setting.Setting( + CONF_FGYF, + default=0.25, + label="Fission Gas Yield Fraction", + description="The fraction of gaseous atoms produced per fission event, assuming a fission product yield of 2.0", + ), setting.Setting( CONF_AXIAL_EXPANSION, default=False, diff --git a/armi/physics/neutronics/fissionProductModel/fissionProductModel.py b/armi/physics/neutronics/fissionProductModel/fissionProductModel.py index ebf5afe47..928cd3214 100644 --- a/armi/physics/neutronics/fissionProductModel/fissionProductModel.py +++ b/armi/physics/neutronics/fissionProductModel/fissionProductModel.py @@ -11,40 +11,94 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """ -Fission product model - -All blocks have a _lumpedFissionProducts attribute that points to a -:py:class:`~armi.physics.neutronics.fissionProductModel.lumpedFissionProduct.LumpedFissionProductCollection`. -The LFP collection may be global or each block may have its own. -The collection may have multiple LFPs from various parents or just one single one. -This module is the shepherd of the block's _lumpedFissionProducts attribute. -All other modules can just assume there's a LFP collection and use it as needed. - - -Examples --------- - - from armi.physics.neutronics.fissionProductModel import fissionProductModel - fpInterface = fissionProductModel.FissionProductModel() - lfp = fpInterface.getGlobalLumpedFissionProducts() - lfp['LFP35'] - lfp35 = lfp['LFP35'] - lfp35.printDensities(0.05) - lfp35.values() - allFPs = [(fpY, fpNuc) for (fpNuc,fpY) in lfp35.items()] - allFPs.sort() - lfp35.keys() - +This module contains the implementation of the ``FissionProductModel`` interface. + + +This ``FissionProductModel`` class implements the management of fission products within +the reactor core and can be extended to support more general applications. Currently, the +fission product model supports explicit modeling of fission products in each of the +blocks/components, independent management of lumped fission products for each +blocks/components within the core, or global management of lumped fission products +where the fission products between all blocks/components are shared and are modified +together. + +Within the framework, there is a coupling between the management of the fission products +through this model to neutronics evaluations of flux and depletion calculations. + +When using a Monte Carlo solver, such as MCNP (i.e., there is an interface that is attached +to the operator that has a name of "mcnp"), the fission products will always be treated +independently and fission products (either explicit or lumped) will be added to all +blocks/components in the core. The reason for this is that Monte Carlo solvers, like MCNP, +may implement their own coupling between flux and depletion evaluations and having the +initialization of these fission products in each block/component independently will +allow that solver to manage the inventory over time. + +When determining which fission product model to use (either explicit or lumped) it is +important to consider which cross section data is available to the flux and/or depletion +solvers, and what level of fidelity is required for the analysis. This is where decisions +as a developer/user need to be made, and the implementation of this specific model may +not be, in general, accurate for any reactor system. It is dependent on which plugins +are implemented and the requirements of the individual flux/depletion solver. + +Lumped fission products are generally useful for fast reactor applications, especially +in fuel cycle calculations or scoping evaluations where the tracking of the detailed +nuclide inventory would not have substantial impacts on core reactivity predictions. +This is typically done by collapsing all fission products into lumped nuclides, like +``LFP35``, ``LFP38``, ``LFP39``, ``LFP40``, and ``LFP41``. This is the implementation +in the framework, which is discussed a bit more in the ``fpModel`` setting. These +lumped fission products are separated into different bins that represent the fission +product yields from U-235, U-238, Pu-239, Pu-240, and Pu-241/Am-241, respectively. The +exact binning of which fission events from which target nuclides is specified by the +``burn-chain.yaml`` file, which can be modified by a user/developer. When selecting this +modeling option, the blocks/components will have these ``LFP`` nuclides in the number +density dictionaries. The key thing here is that these lumped nuclides do not exist +in nature and therefore do not have nuclear data directly available in cross section +evaluations, like ENDF/B. If the user wishes to consider these nuclides in the flux/depletion +evaluations, then cross sections for these ``LFP`` nuclides will need to be prepared. Generally +speaking, the the ``crossSectionGroupManager`` and the ``latticePhysicsInterface`` could be +used to implement this for cross section generation codes, like NJOY, CASMO, MC2-3, Serpent, +etc. + +.. warning:: + + The lumped fission product model and the ``burn-chain.yaml`` data may not be directly + applicable to light water reactor systems, especially if there are strong reactivity + impacts with fission products like ``Xe`` and ``Sm`` that need to be tracked independently. + A user/developer may update the ``referenceFissionProducts.dat`` data file to exclude + these important nuclides from the lumped fission product models if need be, but this + would also require updating the ``burn-chain.yaml`` file as well as updating the + ``nuclideFlags`` specification within the reactor blueprints input. + +A further simplified option for lumped fission product treatment that is available is to +treat all fission products explicitly as ``Mo-99``. This is not guaranteed to be an accurate +treatment of the fission products from a reactivity/depletion perspective, but it is +available for quick scoping evaluations and model building. + +Finally, the explicit fission product modeling aims to include as many nuclides on the +blocks/components as the user wishes to consider, but the nuclides that are modeled +must be compatible with the plugins that are implemented for the application. When using this +option, the user should look to set the ``fpModelLibrary`` setting. + + - If this setting is not set, then it is expected that the user will need to manually add + all nuclides to the ``nuclideFlags`` section of the reactor core blueprints. + + - If the ``fpModelLibrary`` is selected then this will automatically add to the + ``nuclideFlags`` input using :py:func:`isotopicOptions.autoUpdateNuclideFlags` + and this class will initialize all added nuclides to have zero number densities. + +.. warning:: + + The explicit fission product model is being implemented with the vision of using + generating multi-group cross sections for nuclides that are added with the + ``fpModelLibrary`` setting with follow-on depletion calculations that will be managed by + a detailed depletion solver, like ORIGEN. There are many caveats to how this model + is initialized and may not be an out-of-the-box general solution. """ -import collections - from armi import runLog from armi import interfaces from armi.reactor.flags import Flags -from armi.nucDirectory import nuclideBases from armi.physics.neutronics.fissionProductModel import lumpedFissionProduct NUM_FISSION_PRODUCTS_PER_LFP = 2.0 @@ -169,7 +223,7 @@ def interactDistributeState(self): def getAllFissionProductNames(self): """ - Find all fission product names in the problem + Find all fission product names in the problem Considers all LFP collections, whether they be global, block-level, or a mix of these. @@ -194,144 +248,13 @@ def getAllFissionProductNames(self): return fissionProductNames - def removeFissionGasesFromBlocks(self, gasRemovalFractions: dict): + def removeFissionGasesFromBlocks(self): """ - Removes the fission gases from each of the blocks in the core. - - Parameters - ---------- - gasRemovalFractions : dict - Dictionary with block objects as the keys and the fraction - of gaseous fission products to remove. + Return False to indicate that no fission products are being removed. Notes ----- - The current implementation will update the number density vector - of each of the blocks and the gaseous fission products are not - moved or displaced to another area in the core. + This should be implemented on an application-specific model. """ - if not self.cs["fgRemoval"]: - runLog.info( - "Skipping removal of gaseous fission products since the `fgRemoval` setting is disabled." - ) - return - - runLog.info(f"Removing the gaseous fission products from the core.") - if not isinstance(gasRemovalFractions, dict): - raise TypeError(f"The gas removal fractions input is not a dictionary.") - - # Check that the gas removal fractions supplied are within [0.0, 1.0] and cap them - # at these limits otherwise. - updatedGasRemovalFractions = {} - for b, frac in gasRemovalFractions.items(): - if frac < 0.0: - runLog.warning( - f"The fission gas removal fraction is less than zero for {b}. Setting to zero." - ) - updatedGasRemovalFractions[b] = 0.0 - elif frac > 1.0: - runLog.warning( - f"The fission gas removal fraction is greater than one for {b}. Setting to one." - ) - updatedGasRemovalFractions[b] = 1.0 - else: - updatedGasRemovalFractions[b] = frac - gasRemovalFractions.update(updatedGasRemovalFractions) - - def _getGaseousFissionProductNumberDensities(b, lfp): - """Look into a single lumped fission product object and pull out the gaseous atom number densities.""" - numberDensities = {} - for nb in lfp.keys(): - if not lumpedFissionProduct.isGas(nb): - continue - yld = lfp[nb] - ndens = b.getNumberDensity(lfp.name) - numberDensities[nb.name] = ndens * yld - return numberDensities - - def _removeFissionGasesExplicitFissionProductModeling( - core, gasRemovalFractions - ): - """ - This is called when `explicitFissionProducts` are selected as the fission product model. - - Notes - ----- - The input provided has the amount of gas to be removed by each block in the core. The - gas that remains in a block is first calculated and then the number density of the - gaseous nuclides is multiplied by the fraction that remains and the update number - density vector for the block is updated. - """ - for b in core.getBlocks(): - if b not in gasRemovalFractions: - continue - removedFraction = gasRemovalFractions[b] - remainingFraction = 1.0 - removedFraction - updatedNumberDensities = {} - for nuc, val in b.getNumberDensities().items(): - nb = nuclideBases.byName[nuc] - updatedNumberDensities[nuc] = ( - remainingFraction * val - if lumpedFissionProduct.isGas(nb) - else val - ) - b.updateNumberDensities(updatedNumberDensities) - - def _removeFissionGasesLumpedFissionProductModeling(core, gasRemovalFractions): - weightedGasRemoved = collections.defaultdict(float) - totalWeight = collections.defaultdict(float) - for b in core.getBlocks(): - lfpCollection = b.getLumpedFissionProductCollection() - if lfpCollection is None or b not in gasRemovalFractions: - continue - - numberDensities = b.getNumberDensities() - for lfp in lfpCollection.values(): - ndens = _getGaseousFissionProductNumberDensities(b, lfp) - removedFraction = gasRemovalFractions[b] - remainingFraction = 1.0 - removedFraction - - # If the lumped fission products are global then we are going - # release the average across all the blocks in the core and - # this data is collected iteratively. - if self._useGlobalLFPs: - weight = b.getVolume() * (b.p.flux or 1.0) - weightedGasRemoved[lfp] += ( - sum(ndens.values()) * removedFraction * weight - ) - totalWeight[lfp] += weight - - # Otherwise, if the lumped fission products are not global - # go ahead of make the change now. - else: - updatedLFPNumberDensity = ( - numberDensities[lfp.name] * remainingFraction - ) - numberDensities.update({lfp.name: updatedLFPNumberDensity}) - b.setNumberDensities(numberDensities) - - # Apply the global updates to the fission products uniformly across the core. - if self._useGlobalLFPs and totalWeight: - for b in self.r.core.getBlocks(): - lfpCollection = b.getLumpedFissionProductCollection() - if lfpCollection is None or b not in gasRemovalFractions: - continue - - for lfp in lfpCollection.values(): - # The updated number density is calculated as the current number density subtracting - # the amount of gaseous fission products that are being removed. - updatedLFPNumberDensity = b.getNumberDensity(lfp.name) - ( - weightedGasRemoved[lfp] / totalWeight[lfp] - ) - numberDensities.update({lfp.name: updatedLFPNumberDensity}) - b.setNumberDensities(numberDensities) - - if self._explicitFissionProducts: - _removeFissionGasesExplicitFissionProductModeling( - self.r.core, gasRemovalFractions - ) - - else: - _removeFissionGasesLumpedFissionProductModeling( - self.r.core, gasRemovalFractions - ) + runLog.warning(f"Fission gas removal is not implemented in {self}") + return False diff --git a/armi/physics/neutronics/fissionProductModel/fissionProductModelSettings.py b/armi/physics/neutronics/fissionProductModel/fissionProductModelSettings.py index 5016c61d9..d04aca869 100644 --- a/armi/physics/neutronics/fissionProductModel/fissionProductModelSettings.py +++ b/armi/physics/neutronics/fissionProductModel/fissionProductModelSettings.py @@ -72,7 +72,8 @@ def defineSettings(): label="Use Independent LFPs", description=( "Flag to make all blocks have independent lumped fission products. Note that this is forced to be True " - "when the `explicitFissionProducts` modeling option is selected." + "when the ``explicitFissionProducts`` modeling option is selected or an interface named `mcnp` is " + "on registered on the operator stack." ), ), setting.Setting( diff --git a/armi/physics/neutronics/fissionProductModel/lumpedFissionProduct.py b/armi/physics/neutronics/fissionProductModel/lumpedFissionProduct.py index d3d36f8cf..91a888aa1 100644 --- a/armi/physics/neutronics/fissionProductModel/lumpedFissionProduct.py +++ b/armi/physics/neutronics/fissionProductModel/lumpedFissionProduct.py @@ -11,7 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """ The lumped fission product (LFP) module deals with representing LFPs and loading them from files. @@ -90,13 +89,19 @@ def __getitem__(self, fissionProduct): return self.yld.get(fissionProduct, 0.0) def __setitem__(self, key, val): + from armi.physics.neutronics.fissionProductModel.fissionProductModel import ( + NUM_FISSION_PRODUCTS_PER_LFP, + ) + if val < 0.0: raise ValueError( - f"Cannot set the yield of {key} in {self} to be less than zero as this is non-physical." + f"Cannot set the yield of {key} in {self} to be " + f"less than zero as this is non-physical." ) - if val > 2.0: + if val > NUM_FISSION_PRODUCTS_PER_LFP: raise ValueError( - f"Cannot set the yield of {key} in {self} to be greater than two as this is non-physical." + f"Cannot set the yield of {key} in {self} to be " + f"greater than {NUM_FISSION_PRODUCTS_PER_LFP}." ) self.yld[key] = val @@ -404,13 +409,9 @@ def _readOneLFP(self, linesOfOneLFP): def lumpedFissionProductFactory(cs): """Build lumped fission products.""" if cs["fpModel"] == "explicitFissionProducts": - runLog.info("Fission products will be modeled explicitly.") return None if cs["fpModel"] == "MO99": - runLog.info( - "Activating MO99-fission product model. All FPs are treated a MO99!" - ) return _buildMo99LumpedFissionProduct() lfpPath = cs[CONF_LFP_COMPOSITION_FILE_PATH] @@ -427,21 +428,6 @@ def lumpedFissionProductFactory(cs): return lfps -def getAllNuclideBasesByLibrary(cs): - """Return a list of nuclide bases that are available for a given `fpModelLibrary`.""" - nbs = [] - if cs["fpModel"] == "explicitFissionProducts": - if cs["fpModelLibrary"] == "MC2-3": - nbs = nuclideBases.byMcc3Id.values() - else: - raise ValueError( - f"An option to handle the `fpModelLibrary` " - f"set to `{cs['fpModelLibrary']}` has not been " - f"implemented." - ) - return nbs - - def _buildMo99LumpedFissionProduct(): """ Build a dummy MO-99 LFP collection. diff --git a/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py b/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py index 14243011a..6d150880b 100644 --- a/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py +++ b/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py @@ -15,10 +15,7 @@ Test the fission product module to ensure all FP are available. """ import unittest -from ordered_set import OrderedSet -from armi import nuclideBases -from armi.physics.neutronics.fissionProductModel import lumpedFissionProduct from armi.physics.neutronics.fissionProductModel import fissionProductModel from armi.reactor.tests.test_reactors import buildOperatorOfEmptyHexBlocks @@ -69,341 +66,6 @@ def test_getAllFissionProductNames(self): self.assertIn("XE135", fissionProductNames) -class TestFissionProductModelNoGasRemoval(unittest.TestCase): - """Tests that no gaseous fission products are removed when the `fgRemoval` settings is disabled.""" - - def setUp(self): - o = buildOperatorOfEmptyHexBlocks() - o.removeAllInterfaces() - o.cs["fgRemoval"] = False - - self.core = o.r.core - self.fpModel = fissionProductModel.FissionProductModel(o.r, o.cs) - o.addInterface(self.fpModel) - - # Set up the global LFPs and check that they are setup. - self.fpModel.setAllBlockLFPs() - self.assertTrue(self.fpModel._useGlobalLFPs) - - # Setup the blocks to have the same lumped fission product densities. - for b in self.core.getBlocks(): - if b.getLumpedFissionProductCollection() is None: - continue - for lfp in b.getLumpedFissionProductCollection(): - updatedNumberDensities = b.getNumberDensities() - updatedNumberDensities[lfp] = 1e5 - b.setNumberDensities(updatedNumberDensities) - - def test_removeAllGaseousFissionProductsLFP(self): - """ - Same as ``TestFissionProductModelGasRemovalLumpedFissionProducts.test_removeAllGaseousFissionProductsLFP`` - but the `fgRemoval` setting is disabled so there is expected to be no change. - """ - gasRemovalFractions = {} - previousBlockFissionProductNumberDensities = {} - updatedBlockFissionProductNumberDensities = {} - - # Get the initial fission product number densities and setup the gas removal fractions. - for b in self.core.getBlocks(): - lfpCollection = b.getLumpedFissionProductCollection() - if lfpCollection is None: - continue - - previousBlockFissionProductNumberDensities[ - b - ] = _getLumpedFissionProductNumberDensities(b) - gasRemovalFractions = {b: 1.0} - - # Remove the fission gases - self.fpModel.removeFissionGasesFromBlocks(gasRemovalFractions) - - # Check that the fission gases were removed correctly. - for b in self.core.getBlocks(): - lfpCollection = b.getLumpedFissionProductCollection() - if lfpCollection is None: - continue - - updatedBlockFissionProductNumberDensities[ - b - ] = _getLumpedFissionProductNumberDensities(b) - for lfp in lfpCollection.values(): - old = previousBlockFissionProductNumberDensities[b][lfp] - new = updatedBlockFissionProductNumberDensities[b][lfp] - self.assertAlmostEqual(new, old) - - -class TestFissionProductModelGasRemovalLumpedFissionProducts(unittest.TestCase): - """ - Tests the fission product model interface behavior when lumped fission products are enabled. - - Notes - ----- - This loads the global fission products from the default file. - """ - - def setUp(self): - o = buildOperatorOfEmptyHexBlocks() - o.removeAllInterfaces() - o.cs["fgRemoval"] = True - - self.core = o.r.core - self.fpModel = fissionProductModel.FissionProductModel(o.r, o.cs) - o.addInterface(self.fpModel) - - # Set up the global LFPs and check that they are setup. - self.fpModel.setAllBlockLFPs() - self.assertTrue(self.fpModel._useGlobalLFPs) - - # Setup the blocks to have the same lumped fission product densities. - for b in self.core.getBlocks(): - if b.getLumpedFissionProductCollection() is None: - continue - for lfp in b.getLumpedFissionProductCollection(): - updatedNumberDensities = b.getNumberDensities() - updatedNumberDensities[lfp] = 1e5 - b.setNumberDensities(updatedNumberDensities) - - def test_removeZeroGaseousFissionProductsLFP(self): - """Tests removal of gaseous fission products globally in the core with a removal fraction of zero.""" - gasRemovalFractions = {} - previousBlockFissionProductNumberDensities = {} - updatedBlockFissionProductNumberDensities = {} - - # Get the initial fission product number densities and setup the gas removal fractions. - for b in self.core.getBlocks(): - lfpCollection = b.getLumpedFissionProductCollection() - if lfpCollection is None: - continue - - previousBlockFissionProductNumberDensities[ - b - ] = _getLumpedFissionProductNumberDensities(b) - gasRemovalFractions = {b: 0.0} - - # Remove the fission gases - self.fpModel.removeFissionGasesFromBlocks(gasRemovalFractions) - - # Check that the fission gases were removed correctly. - for b in self.core.getBlocks(): - lfpCollection = b.getLumpedFissionProductCollection() - if lfpCollection is None: - continue - - updatedBlockFissionProductNumberDensities[ - b - ] = _getLumpedFissionProductNumberDensities(b) - for lfp in lfpCollection.values(): - old = previousBlockFissionProductNumberDensities[b][lfp] - new = updatedBlockFissionProductNumberDensities[b][lfp] - - # The expected result should be the previous number densities for the lumped fission - # product within the block that is subtracted by the gas removal fraction multiplied - # by the fraction of gas atoms produced from fission. - result = old - ( - old * gasRemovalFractions[b] * lfp.getGaseousYieldFraction() - ) - self.assertAlmostEqual(new, result) - self.assertEqual(new, old) - - def test_removeHalfGaseousFissionProductsLFP(self): - """Tests removal of gaseous fission products globally in the core with a removal fraction of 0.5.""" - gasRemovalFractions = {} - previousBlockFissionProductNumberDensities = {} - updatedBlockFissionProductNumberDensities = {} - - # Get the initial fission product number densities and setup the gas removal fractions. - for b in self.core.getBlocks(): - lfpCollection = b.getLumpedFissionProductCollection() - if lfpCollection is None: - continue - - previousBlockFissionProductNumberDensities[ - b - ] = _getLumpedFissionProductNumberDensities(b) - gasRemovalFractions = {b: 0.5} - - # Remove the fission gases - self.fpModel.removeFissionGasesFromBlocks(gasRemovalFractions) - - # Check that the fission gases were removed correctly. - for b in self.core.getBlocks(): - lfpCollection = b.getLumpedFissionProductCollection() - if lfpCollection is None: - continue - - updatedBlockFissionProductNumberDensities[ - b - ] = _getLumpedFissionProductNumberDensities(b) - for lfp in lfpCollection.values(): - old = previousBlockFissionProductNumberDensities[b][lfp] - new = updatedBlockFissionProductNumberDensities[b][lfp] - - # The expected result should be the previous number densities for the lumped fission - # product within the block that is subtracted by the gas removal fraction multiplied - # by the fraction of gas atoms produced from fission. - result = old - ( - old * gasRemovalFractions[b] * lfp.getGaseousYieldFraction() - ) - self.assertAlmostEqual(new, result) - - def test_removeAllGaseousFissionProductsLFP(self): - """Tests removal of gaseous fission products globally in the core with a removal fraction of 1.0.""" - gasRemovalFractions = {} - previousBlockFissionProductNumberDensities = {} - updatedBlockFissionProductNumberDensities = {} - - # Get the initial fission product number densities and setup the gas removal fractions. - for b in self.core.getBlocks(): - lfpCollection = b.getLumpedFissionProductCollection() - if lfpCollection is None: - continue - - previousBlockFissionProductNumberDensities[ - b - ] = _getLumpedFissionProductNumberDensities(b) - gasRemovalFractions = {b: 1.0} - - # Remove the fission gases - self.fpModel.removeFissionGasesFromBlocks(gasRemovalFractions) - - # Check that the fission gases were removed correctly. - for b in self.core.getBlocks(): - lfpCollection = b.getLumpedFissionProductCollection() - if lfpCollection is None: - continue - - updatedBlockFissionProductNumberDensities[ - b - ] = _getLumpedFissionProductNumberDensities(b) - for lfp in lfpCollection.values(): - old = previousBlockFissionProductNumberDensities[b][lfp] - new = updatedBlockFissionProductNumberDensities[b][lfp] - - # The expected result should be the previous number densities for the lumped fission - # product within the block that is subtracted by the gas removal fraction multiplied - # by the fraction of gas atoms produced from fission. - result = old - ( - old * gasRemovalFractions[b] * lfp.getGaseousYieldFraction() - ) - self.assertAlmostEqual(new, result) - - -class TestFissionProductModelGasRemovalExplicitFissionProducts(unittest.TestCase): - """Tests the fission product model interface behavior when `explicitFissionProducts` are enabled.""" - - def setUp(self): - o = buildOperatorOfEmptyHexBlocks() - o.removeAllInterfaces() - o.cs["fgRemoval"] = True - o.cs["fpModel"] = "explicitFissionProducts" - o.cs["fpModelLibrary"] = "MC2-3" - - nuclidesToAdd = [] - for nb in lumpedFissionProduct.getAllNuclideBasesByLibrary(o.cs): - nuclidesToAdd.append(nb.name) - o.r.blueprints.allNuclidesInProblem = OrderedSet(sorted(nuclidesToAdd)) - - self.core = o.r.core - self.fpModel = fissionProductModel.FissionProductModel(o.r, o.cs) - o.addInterface(self.fpModel) - - # Set up the global LFPs and check that they are setup. - self.fpModel.setAllBlockLFPs() - self.assertFalse(self.fpModel._useGlobalLFPs) - - # Setup the blocks to have the same lumped fission product densities. - for b in self.core.getBlocks(): - updatedNumberDensities = b.getNumberDensities() - for nuc in b.getNuclides(): - nb = nuclideBases.byName[nuc] - if not lumpedFissionProduct.isGas(nb): - continue - updatedNumberDensities[nuc] = 1e5 - b.setNumberDensities(updatedNumberDensities) - - def test_removeZeroGaseousFissionProducts(self): - """Tests removal of gaseous fission products in the core with a removal fraction of zero.""" - gasRemovalFractions = {} - previousBlockFissionProductNumberDensities = {} - updatedBlockFissionProductNumberDensities = {} - - # Get the initial fission product number densities and setup the gas removal fractions. - for b in self.core.getBlocks(): - previousBlockFissionProductNumberDensities[b] = b.getNumberDensities() - gasRemovalFractions = {b: 0.0} - - # Remove the fission gases - self.fpModel.removeFissionGasesFromBlocks(gasRemovalFractions) - - # Check that the fission gases were removed correctly. - for b in self.core.getBlocks(): - updatedBlockFissionProductNumberDensities[b] = b.getNumberDensities() - for nuc in b.getNuclides(): - nb = nuclideBases.byName[nuc] - if not lumpedFissionProduct.isGas(nb): - continue - - old = previousBlockFissionProductNumberDensities[b][nuc] - new = updatedBlockFissionProductNumberDensities[b][nuc] - result = old - (old * gasRemovalFractions[b]) - self.assertAlmostEqual(new, result) - self.assertAlmostEqual(new, old) - - def test_removeHalfGaseousFissionProducts(self): - """Tests removal of gaseous fission products in the core with a removal fraction of 0.5.""" - gasRemovalFractions = {} - previousBlockFissionProductNumberDensities = {} - updatedBlockFissionProductNumberDensities = {} - - # Get the initial fission product number densities and setup the gas removal fractions. - for b in self.core.getBlocks(): - previousBlockFissionProductNumberDensities[b] = b.getNumberDensities() - gasRemovalFractions = {b: 0.5} - - # Remove the fission gases - self.fpModel.removeFissionGasesFromBlocks(gasRemovalFractions) - - # Check that the fission gases were removed correctly. - for b in self.core.getBlocks(): - updatedBlockFissionProductNumberDensities[b] = b.getNumberDensities() - for nuc in b.getNuclides(): - nb = nuclideBases.byName[nuc] - if not lumpedFissionProduct.isGas(nb): - continue - - old = previousBlockFissionProductNumberDensities[b][nuc] - new = updatedBlockFissionProductNumberDensities[b][nuc] - result = old - (old * gasRemovalFractions[b]) - self.assertAlmostEqual(new, result) - - def test_removeAllGaseousFissionProducts(self): - """Tests removal of gaseous fission products in the core with a removal fraction of 1.0.""" - gasRemovalFractions = {} - previousBlockFissionProductNumberDensities = {} - updatedBlockFissionProductNumberDensities = {} - - # Get the initial fission product number densities and setup the gas removal fractions. - for b in self.core.getBlocks(): - previousBlockFissionProductNumberDensities[b] = b.getNumberDensities() - gasRemovalFractions = {b: 1.0} - - # Remove the fission gases - self.fpModel.removeFissionGasesFromBlocks(gasRemovalFractions) - - # Check that the fission gases were removed correctly. - for b in self.core.getBlocks(): - updatedBlockFissionProductNumberDensities[b] = b.getNumberDensities() - for nuc in b.getNuclides(): - nb = nuclideBases.byName[nuc] - if not lumpedFissionProduct.isGas(nb): - continue - - old = previousBlockFissionProductNumberDensities[b][nuc] - new = updatedBlockFissionProductNumberDensities[b][nuc] - result = old - (old * gasRemovalFractions[b]) - self.assertAlmostEqual(new, result) - - if __name__ == "__main__": # import sys;sys.argv = ['', 'Test.testName'] unittest.main() diff --git a/armi/reactor/blueprints/__init__.py b/armi/reactor/blueprints/__init__.py index 6076f6007..f6e0e7892 100644 --- a/armi/reactor/blueprints/__init__.py +++ b/armi/reactor/blueprints/__init__.py @@ -344,33 +344,6 @@ def _assignTypeNums(self): if bDesign not in self.blockDesigns: self.blockDesigns.add(bDesign) - def _autoUpdateNuclideFlags(self, cs): - """ - This method is responsible for examining the fission product model treatment - that is selected by the user and adding a set of nuclides to the `nuclideFlags` - list. - Notes - ----- - The reason for adding this method is that when switching between fission product - modeling treatments it can be time-consuming to manually adjust the `nuclideFlags` - inputs. This is specifically the case with the fission product model is set to - `explicitFissionProducts`. - """ - nbs = lumpedFissionProduct.getAllNuclideBasesByLibrary(cs) - if nbs: - runLog.info( - f"Adding explicit fission products to the nuclide flags based on the " - f"fission product model set to `{cs['fpModel']}`." - ) - for nb in nbs: - nuc = nb.name - if nuc in self.nuclideFlags or elements.byZ[nb.z] in self.nuclideFlags: - continue - nuclideFlag = isotopicOptions.NuclideFlag( - nuc, burn=False, xs=True, expandTo=[] - ) - self.nuclideFlags[nuc] = nuclideFlag - def _resolveNuclides(self, cs): """ Process elements and determine how to expand them to natural isotopics. @@ -393,7 +366,7 @@ def _resolveNuclides(self, cs): if self.nuclideFlags is None: self.nuclideFlags = isotopicOptions.genDefaultNucFlags() - self._autoUpdateNuclideFlags(cs) + isotopicOptions.autoUpdateNuclideFlags(cs, self.nuclideFlags) self.elementsToExpand = [] for nucFlag in self.nuclideFlags: diff --git a/armi/reactor/blueprints/isotopicOptions.py b/armi/reactor/blueprints/isotopicOptions.py index c60c97900..a1fa306ba 100644 --- a/armi/reactor/blueprints/isotopicOptions.py +++ b/armi/reactor/blueprints/isotopicOptions.py @@ -555,3 +555,53 @@ def genDefaultNucFlags(): ) flags[nucName] = flag return flags + + +def autoUpdateNuclideFlags(cs, nuclideFlags): + """ + This function is responsible for examining the fission product model treatment + that is selected by the user and adding a set of nuclides to the `nuclideFlags` + list. + + Notes + ----- + The reason for adding this method is that when switching between fission product + modeling treatments it can be time-consuming to manually adjust the ``nuclideFlags`` + inputs. + + See Also + -------- + genDefaultNucFlags + """ + nbs = getAllNuclideBasesByLibrary(cs) + if nbs: + runLog.info( + f"Adding explicit fission products to the nuclide flags based on the " + f"fission product model set to `{cs['fpModel']}`." + ) + for nb in nbs: + nuc = nb.name + if nuc in nuclideFlags or elements.byZ[nb.z] in nuclideFlags: + continue + nuclideFlag = NuclideFlag(nuc, burn=False, xs=True, expandTo=[]) + nuclideFlags[nuc] = nuclideFlag + + +def getAllNuclideBasesByLibrary(cs): + """ + Return a list of nuclide bases available for cross section modeling + based on the ``fpModelLibrary`` setting. + """ + nbs = [] + if cs["fpModel"] == "explicitFissionProducts": + if not cs["fpModelLibrary"]: + nbs = [] + if cs["fpModelLibrary"] == "MC2-3": + nbs = nuclideBases.byMcc3Id.values() + else: + raise ValueError( + f"An option to handle the `fpModelLibrary` " + f"set to `{cs['fpModelLibrary']}` has not been " + f"implemented." + ) + return nbs From fc108c95d7af1eb26795461c9dbeeabf2feee47d Mon Sep 17 00:00:00 2001 From: jakehader Date: Tue, 27 Dec 2022 11:56:53 -0800 Subject: [PATCH 09/11] Address reviewer comments. --- armi/nucDirectory/tests/test_nucDirectory.py | 8 ++ armi/operators/settingsValidation.py | 10 +- .../neutronics/crossSectionGroupManager.py | 16 ---- .../fissionProductModel.py | 42 +++++---- .../fissionProductModelSettings.py | 47 +++++----- .../lumpedFissionProduct.py | 91 ++----------------- .../tests/test_fissionProductModel.py | 40 +++++++- .../tests/test_lumpedFissionProduct.py | 6 -- armi/settings/caseSettings.py | 2 +- 9 files changed, 107 insertions(+), 155 deletions(-) diff --git a/armi/nucDirectory/tests/test_nucDirectory.py b/armi/nucDirectory/tests/test_nucDirectory.py index 1988b8bcc..7735cec08 100644 --- a/armi/nucDirectory/tests/test_nucDirectory.py +++ b/armi/nucDirectory/tests/test_nucDirectory.py @@ -57,6 +57,14 @@ def test_nucDir_getNuclidesFromForBadName(self): with self.assertRaises(Exception): nucDir.getNuclideFromName("Charlie") + def test_getDisplacementEnergy(self): + """Test getting the displacement energy for a given nuclide.""" + ed = nucDir.getThresholdDisplacementEnergy("H1") + self.assertEqual(ed, 10.0) + + with self.assertRaises(KeyError): + nucDir.getThresholdDisplacementEnergy("fail") + if __name__ == "__main__": unittest.main() diff --git a/armi/operators/settingsValidation.py b/armi/operators/settingsValidation.py index c3ea0129a..b3cf099e3 100644 --- a/armi/operators/settingsValidation.py +++ b/armi/operators/settingsValidation.py @@ -65,6 +65,7 @@ def __init__(self, condition, statement, question, correction): self.question = question self.correction = correction # True if the query is `passed` and does not result in an immediate failure + self.corrected = False self._passed = False self.autoResolved = True @@ -105,6 +106,7 @@ def resolve(self): ) if make_correction: self.correction() + self.corrected = True self._passed = True except RunLogPromptCancel as ki: raise KeyboardInterrupt from ki @@ -185,8 +187,8 @@ def run(self, cs=None): if context.MPI_RANK != 0: return False - # the following attribute changes will alter what the queries investigate when - # resolved + # the following attribute changes will alter what the queries investigate when resolved + correctionsMade = False self.cs = cs or self.cs runLog.debug("{} executing queries.".format(self.__class__.__name__)) if not any(self.queries): @@ -198,6 +200,8 @@ def run(self, cs=None): else: for query in self.queries: query.resolve() + if query.corrected: + correctionsMade = True issues = [ query for query in self.queries @@ -214,6 +218,8 @@ def run(self, cs=None): ) runLog.debug("{} has finished querying.".format(self.__class__.__name__)) + return correctionsMade + def addQuery(self, condition, statement, question, correction): """Convenience method, query must be resolved, else run fails""" if not callable(correction): diff --git a/armi/physics/neutronics/crossSectionGroupManager.py b/armi/physics/neutronics/crossSectionGroupManager.py index 85c64e490..cccf6b819 100644 --- a/armi/physics/neutronics/crossSectionGroupManager.py +++ b/armi/physics/neutronics/crossSectionGroupManager.py @@ -326,22 +326,6 @@ def _getAverageNumberDensities(self): ndens = weights.dot([b.getNuclideNumberDensities(nuclides) for b in blocks]) return dict(zip(nuclides, ndens)) - def _getAverageFissionGasRemoved(self): - """ - Get weighted average fission gas release fraction. - - Notes - ----- - - Will be applied to LFP composition. - """ - totalWeight = 0.0 - fgRelease = 0.0 - for b in self.getCandidateBlocks(): - weight = self.getWeight(b) - totalWeight += weight - fgRelease += b.p.gasReleaseFraction * weight - return fgRelease / totalWeight - def _getAverageFuelLFP(self): """Compute the average lumped fission products.""" # TODO: make do actual average of LFPs diff --git a/armi/physics/neutronics/fissionProductModel/fissionProductModel.py b/armi/physics/neutronics/fissionProductModel/fissionProductModel.py index 928cd3214..207bf5007 100644 --- a/armi/physics/neutronics/fissionProductModel/fissionProductModel.py +++ b/armi/physics/neutronics/fissionProductModel/fissionProductModel.py @@ -158,34 +158,37 @@ def setAllBlockLFPs(self): """ Sets all the block lumped fission products attributes and adds fission products to each block if `self._explicitFissionProducts` is set to True. + See Also -------- armi.reactor.components.Component.setLumpedFissionProducts """ for b in self.r.core.getBlocks(self._fissionProductBlockType, includeAll=True): + if self._useGlobalLFPs: b.setLumpedFissionProducts(self.getGlobalLumpedFissionProducts()) else: lfps = self.getGlobalLumpedFissionProducts() + + # There will be no lumped fission products when explicitFissionProducts + # are modeled. if lfps is None: b.setLumpedFissionProducts(None) + if not self._initialized: + targetComponent = b.getComponent(self._fissionProductBlockType) + if not targetComponent: + continue + ndens = targetComponent.getNumberDensities() + updatedNDens = {} + for nuc in self.r.blueprints.allNuclidesInProblem: + if nuc in ndens: + continue + updatedNDens[nuc] = 0.0 + targetComponent.updateNumberDensities(updatedNDens) else: - independentLFPs = self.getGlobalLumpedFissionProducts().duplicate() + independentLFPs = lfps.duplicate() b.setLumpedFissionProducts(independentLFPs) - # Initialize the fission products explicitly on the block component - # that matches the `self._fissionProductBlockType` if it exists. - if self._explicitFissionProducts and not self._initialized: - targetComponent = b.getComponent(self._fissionProductBlockType) - if not targetComponent: - continue - ndens = targetComponent.getNumberDensities() - updatedNDens = {} - for nuc in self.r.blueprints.allNuclidesInProblem: - if nuc in ndens: - continue - updatedNDens[nuc] = 0.0 - targetComponent.updateNumberDensities(updatedNDens) self._initialized = True def getGlobalLumpedFissionProducts(self): @@ -223,13 +226,12 @@ def interactDistributeState(self): def getAllFissionProductNames(self): """ - Find all fission product names in the problem + Find all fission product names from the lumped fission product collection. - Considers all LFP collections, whether they be global, - block-level, or a mix of these. - - sets fissionProductNames, a list of nuclide names of all the - fission products + Notes + ----- + This considers all LFP collections, whether they are global, block-level, + or a mix of these. """ runLog.debug("Gathering all possible fission products that are modeled.") fissionProductNames = [] diff --git a/armi/physics/neutronics/fissionProductModel/fissionProductModelSettings.py b/armi/physics/neutronics/fissionProductModel/fissionProductModelSettings.py index d04aca869..0b0a8c05b 100644 --- a/armi/physics/neutronics/fissionProductModel/fissionProductModelSettings.py +++ b/armi/physics/neutronics/fissionProductModel/fissionProductModelSettings.py @@ -50,16 +50,16 @@ def defineSettings(): default="", label="Fission Product Library", description=( - f"This setting is used when the `{CONF_FP_MODEL}` setting " - f"is set to `explicitFissionProducts` and is used to configure " - f"all the nuclides that should be modeled within the core. " - f"Setting this is equivalent to adding all nuclides in the " - f"selected code library (i.e., MC2-3) within the blueprints " - f"`nuclideFlags` to be [xs:true, burn:false]. This option acts " - f"as a short-cut so that analysts do not need to change their " - f"inputs when modifying the fission product treatment for " - f"calculations. This may be extended for other cross section " - f"generation codes." + f"This setting can used when the `{CONF_FP_MODEL}` setting " + "is set to `explicitFissionProducts` and is used to configure " + "all the nuclides that should be modeled within the core. " + "Setting this is equivalent to adding all nuclides in the " + "selected code library (i.e., MC2-3) within the blueprints " + "`nuclideFlags` to be [xs:true, burn:false]. This option acts " + "as a short-cut so that analysts do not need to change their " + "inputs when modifying the fission product treatment for " + "calculations. This may be extended for other cross section " + "generation codes." ), options=[ "", @@ -103,10 +103,10 @@ def getFissionProductModelSettingValidators(inspector): lambda: inspector.cs["fpModel"] != "explicitFissionProducts" and not bool(inspector.cs["initializeBurnChain"]), ( - f"The burn chain is not being initialized and the fission product model is not set to `explicitFissionProducts`. " - f"This will likely fail." + "The burn chain is not being initialized and the fission product model is not set to `explicitFissionProducts`. " + "This will likely fail." ), - (f"Would you like to set the `fpModel` to `explicitFissionProducts`?"), + "Would you like to set the `fpModel` to `explicitFissionProducts`?", lambda: inspector._assignCS("fpModel", "explicitFissionProducts"), ) ) @@ -116,10 +116,10 @@ def getFissionProductModelSettingValidators(inspector): lambda: inspector.cs["fpModel"] != "explicitFissionProducts" and inspector.cs["fpModelLibrary"] != "", ( - f"The explicit fission product model is disabled and the fission product model library is set. This will have no " - f"impact on the results, but it is best to disable the `fpModelLibrary` option." + "The explicit fission product model is disabled and the fission product model library is set. This will have no " + "impact on the results, but it is best to disable the `fpModelLibrary` option." ), - (f"Would you like to do this?"), + "Would you like to do this?", lambda: inspector._assignCS("fpModelLibrary", ""), ) ) @@ -129,10 +129,10 @@ def getFissionProductModelSettingValidators(inspector): lambda: inspector.cs["fpModel"] == "explicitFissionProducts" and bool(inspector.cs["initializeBurnChain"]), ( - f"The explicit fission product model is enabled, but initializing the burn chain is also enabled. This will " - f"likely fail." + "The explicit fission product model is enabled, but initializing the burn chain is also enabled. This will " + "likely fail." ), - (f"Would you like to disable the burn chain initialization?"), + "Would you like to disable the burn chain initialization?", lambda: inspector._assignCS("initializeBurnChain", False), ) ) @@ -142,13 +142,10 @@ def getFissionProductModelSettingValidators(inspector): lambda: inspector.cs["fpModel"] == "explicitFissionProducts" and inspector.cs["fpModelLibrary"] == "", ( - f"The explicit fission product model is enabled and the fission product model library is disabled. This will result " - f"in a failure. Note that the fission product model library will determine which nuclides to add to the depletable regions of the core " - f"that are not already included in the blueprints `nuclideFlags`." - ), - ( - f"Would you like to set the `fpModelLibrary` option to be equal to the default implementation of MC2-3?." + "The explicit fission product model is enabled and the fission product model library is disabled. May result in " + "no fission product nuclides being added to the case, unless these have manually added in `nuclideFlags`." ), + "Would you like to set the `fpModelLibrary` option to be equal to the default implementation of MC2-3?.", lambda: inspector._assignCS("fpModelLibrary", "MC2-3"), ) ) diff --git a/armi/physics/neutronics/fissionProductModel/lumpedFissionProduct.py b/armi/physics/neutronics/fissionProductModel/lumpedFissionProduct.py index 91a888aa1..8552b72c4 100644 --- a/armi/physics/neutronics/fissionProductModel/lumpedFissionProduct.py +++ b/armi/physics/neutronics/fissionProductModel/lumpedFissionProduct.py @@ -158,9 +158,7 @@ def getMassFracs(self): massFracs[nuc] = self.getMassFrac(nuclideBase=nuc) return massFracs - def getMassFrac( - self, nucName=None, nuclideBase=None, useCache=True, storeCache=True - ): + def getMassFrac(self, nucName=None, nuclideBase=None): """ Return the mass fraction of the given nuclide. @@ -168,41 +166,21 @@ def getMassFrac( ------- nuclide mass fraction (float) """ - massFracDenom = self.getMassFracDenom(useCache=useCache, storeCache=storeCache) + massFracDenom = self.getMassFracDenom() if not nuclideBase: nuclideBase = nuclideBases.byName[nucName] return self.__getitem__(nuclideBase) * (nuclideBase.weight / massFracDenom) - def getMassFracDenom(self, useCache=True, storeCache=True): + def getMassFracDenom(self): """ See Also -------- armi.physics.neutronics.fissionProductModel.lumpedFissionProduct.LumpedFissionProduct.getMassFrac """ - if hasattr(self, "massFracDenom") and useCache: - return self.massFracDenom - else: - massFracDenom = 0 - for nuc in self.keys(): - massFracDenom += self[nuc] * nuc.weight - if storeCache: - self.massFracDenom = massFracDenom - return massFracDenom - - def getExpandedMass(self, mass=1.0): - """ - returns a dictionary of masses indexed by nuclide base objects - - Parameters - ---------- - mass : float, - the mass of all the expanded mass of the given LFP. - """ - - massVector = self.getMassFracs() - massVector.update((nuc, mass * mFrac) for nuc, mFrac in massVector.items()) - - return massVector + massFracDenom = 0.0 + for nuc in self.keys(): + massFracDenom += self[nuc] * nuc.weight + return massFracDenom def printDensities(self, lfpDens): """ @@ -322,7 +300,7 @@ class FissionProductDefinitionFile: >>> fpd = FissionProductDefinitionFile(stream) >>> lfps = fpd.createLFPsFromFile() - The path to this file name is specified by the + The path to this file is specified by the `lfpCompositionFilePath` user setting. """ def __init__(self, stream): @@ -449,59 +427,6 @@ def _buildMo99LumpedFissionProduct(): return mo99LFPs -def expandFissionProducts(massFrac, lumpedFissionProducts): - """ - expands lumped fission products in a massFrac vector - - Parameters - ---------- - massFrac : dict - - lumpedFissionProducts : LumpedFissionProductCollection (acts like a dict) - result of .getGlobalLumpedFissionProducts - - Returns - ------- - newMassFracs : dict - """ - lfpNbs = [] - elementalNbs = [] - newMassFrac = {} - - for nucName in massFrac.keys(): - nB = nuclideBases.byName[nucName] - if isinstance(nB, nuclideBases.LumpNuclideBase): - lfpNbs.append(nB) - elif isinstance(nB, nuclideBases.NaturalNuclideBase): - elementalNbs.append(nB) - else: - newMassFrac[nucName] = massFrac[nucName] - - for lfp in lfpNbs: - if massFrac[lfp.name] != 0: - try: - lfpFP = lumpedFissionProducts[lfp.name] - except KeyError: - errorMessage = ["{}".format(lumpedFissionProducts)] - errorMessage.append("\ntype {}".format(type(lumpedFissionProducts))) - errorMessage.append("\nmassFrac dict {}".format(massFrac)) - errorMessage.append("\nLumped Fission Product Name {}".format(lfp.name)) - runLog.debug("".join(errorMessage)) - - for nB in lfpFP.keys(): - newMassFrac[nB.name] = massFrac.get(nB.name, 0) + massFrac[ - lfp.name - ] * lfpFP.getMassFrac(nuclideBase=nB) - - for element in elementalNbs: - for nB in element.getNaturalIsotopics(): - newMassFrac[nB.name] = ( - massFrac.get(nB.name, 0) - + massFrac[element.name] * nB.abundance * nB.weight / element.weight - ) - return newMassFrac - - def isGas(nuc): """True if nuclide is considered a gas.""" for element in elements.getElementsByChemicalPhase(elements.ChemicalPhase.GAS): diff --git a/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py b/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py index 6d150880b..15dcbf079 100644 --- a/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py +++ b/armi/physics/neutronics/fissionProductModel/tests/test_fissionProductModel.py @@ -16,9 +16,15 @@ """ import unittest -from armi.physics.neutronics.fissionProductModel import fissionProductModel -from armi.reactor.tests.test_reactors import buildOperatorOfEmptyHexBlocks +from armi import nuclideBases +from armi.reactor.flags import Flags + +from armi.physics.neutronics.fissionProductModel import fissionProductModel +from armi.reactor.tests.test_reactors import ( + buildOperatorOfEmptyHexBlocks, + loadTestReactor, +) from armi.physics.neutronics.fissionProductModel.tests import test_lumpedFissionProduct @@ -66,6 +72,36 @@ def test_getAllFissionProductNames(self): self.assertIn("XE135", fissionProductNames) +class TestFissionProductModelExplicitMC2Library(unittest.TestCase): + """ + Tests the fission product model interface behavior when explicit fission products are enabled. + """ + + def setUp(self): + o, r = loadTestReactor( + customSettings={ + "fpModel": "explicitFissionProducts", + "fpModelLibrary": "MC2-3", + } + ) + self.r = r + self.fpModel = fissionProductModel.FissionProductModel(o.r, o.cs) + # Set up the global LFPs and check that they are setup. + self.fpModel.setAllBlockLFPs() + self.assertFalse(self.fpModel._useGlobalLFPs) + + def test_nuclideFlags(self): + """Test that the nuclide flags contain the set of MC2-3 modeled nuclides.""" + for nb in nuclideBases.byMcc3Id.values(): + self.assertIn(nb.name, self.r.blueprints.nuclideFlags.keys()) + + def test_nuclidesInModel(self): + """Test that the fuel blocks contain all the MC2-3 modeled nuclides.""" + b = self.r.core.getFirstBlock(Flags.FUEL) + for nb in nuclideBases.byMcc3Id.values(): + self.assertIn(nb.name, b.getNuclides()) + + if __name__ == "__main__": # import sys;sys.argv = ['', 'Test.testName'] unittest.main() diff --git a/armi/physics/neutronics/fissionProductModel/tests/test_lumpedFissionProduct.py b/armi/physics/neutronics/fissionProductModel/tests/test_lumpedFissionProduct.py index 18852b63f..1a51434e8 100644 --- a/armi/physics/neutronics/fissionProductModel/tests/test_lumpedFissionProduct.py +++ b/armi/physics/neutronics/fissionProductModel/tests/test_lumpedFissionProduct.py @@ -110,12 +110,6 @@ def test_getYield(self): self.assertEqual(val3, 1.2) self.assertEqual(lfp[5], 0.0) - def test_getExpandedMass(self): - xe135 = nuclideBases.fromName("XE135") - lfp = self.fpd.createSingleLFPFromFile("LFP38") - massVector = lfp.getExpandedMass(mass=0.99) - self.assertEqual(massVector.get(xe135), 0.99) - def test_printDensities(self): _ = nuclideBases.fromName("XE135") lfp = self.fpd.createSingleLFPFromFile("LFP38") diff --git a/armi/settings/caseSettings.py b/armi/settings/caseSettings.py index 126ad6f78..f1a121a9d 100644 --- a/armi/settings/caseSettings.py +++ b/armi/settings/caseSettings.py @@ -171,7 +171,7 @@ def _directAccessOfSettingAllowed(self, key): "Cannot grab simple cycles information from the case settings " "when detailed cycles information is also entered. In general " "cycles information should be pulled off the operator or parsed " - "using the appropriate getter in the utils. " + "using the appropriate getter in the utils." ) return False, err From 848af70edb134e678ed0e5ded856ae8f58825cde Mon Sep 17 00:00:00 2001 From: jakehader Date: Tue, 27 Dec 2022 12:27:30 -0800 Subject: [PATCH 10/11] Add couple more unit tests to bump `lumpedFissionProduct` module test coverage. --- .../tests/test_lumpedFissionProduct.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/armi/physics/neutronics/fissionProductModel/tests/test_lumpedFissionProduct.py b/armi/physics/neutronics/fissionProductModel/tests/test_lumpedFissionProduct.py index 1a51434e8..9e21aeb40 100644 --- a/armi/physics/neutronics/fissionProductModel/tests/test_lumpedFissionProduct.py +++ b/armi/physics/neutronics/fissionProductModel/tests/test_lumpedFissionProduct.py @@ -110,11 +110,35 @@ def test_getYield(self): self.assertEqual(val3, 1.2) self.assertEqual(lfp[5], 0.0) + def test_gaseousYieldFraction(self): + lfp = self.fpd.createSingleLFPFromFile("LFP39") + # This is equal to the Xe yield set in the dummy ``LFP_TEXT`` + # data for these tests. + self.assertEqual(lfp.getGaseousYieldFraction(), 8.9000e-05) + def test_printDensities(self): _ = nuclideBases.fromName("XE135") lfp = self.fpd.createSingleLFPFromFile("LFP38") lfp.printDensities(10.0) + def test_isGas(self): + """Tests that a nuclide is a gas or not at STP based on its chemical phase.""" + nb = nuclideBases.byName["H1"] + self.assertTrue(lumpedFissionProduct.isGas(nb)) + nb = nuclideBases.byName["H2"] + self.assertTrue(lumpedFissionProduct.isGas(nb)) + nb = nuclideBases.byName["H3"] + self.assertTrue(lumpedFissionProduct.isGas(nb)) + + nb = nuclideBases.byName["U235"] + self.assertFalse(lumpedFissionProduct.isGas(nb)) + + nb = nuclideBases.byName["O16"] + self.assertTrue(lumpedFissionProduct.isGas(nb)) + + nb = nuclideBases.byName["XE135"] + self.assertTrue(lumpedFissionProduct.isGas(nb)) + class TestLumpedFissionProductCollection(unittest.TestCase): """Test of the fission product collection""" From ff1ed59fc4c34db9afb72445d834abfc2f1a2c98 Mon Sep 17 00:00:00 2001 From: jakehader Date: Tue, 27 Dec 2022 12:42:58 -0800 Subject: [PATCH 11/11] Update latticePhysicsWriter to consider all LFPs when retriving the fission product number densities rather than assuming that the first LFP in the collection contains the entire set of nuclides required. --- .../neutronics/latticePhysics/latticePhysicsWriter.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py b/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py index 815c225a9..78eabf3dd 100644 --- a/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py +++ b/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py @@ -363,15 +363,12 @@ def _getDetailedFPDensities(self): return dfpDensities lfpCollection = self.block.getLumpedFissionProductCollection() if self.diluteFissionProducts: - # set all densities to near zero. - try: - _, dfp = list(lfpCollection.items())[0] - except IndexError: - raise IndexError( + if lfpCollection is None: + raise ValueError( "Lumped fission products are not initialized. Did interactAll BOL run?" ) - - for individualFpBase in dfp.keys(): + dfps = lfpCollection.getAllFissionProductNuclideBases() + for individualFpBase in dfps: dfpDensities[individualFpBase] = self.minimumNuclideDensity else: # expand densities and sum