Skip to content

Commit

Permalink
Merge branch 'main' into prelim_design
Browse files Browse the repository at this point in the history
  • Loading branch information
mgjarrett committed Jul 9, 2024
2 parents d164bb2 + 36a5bdf commit 0af93c5
Show file tree
Hide file tree
Showing 21 changed files with 446 additions and 138 deletions.
5 changes: 4 additions & 1 deletion armi/cases/tests/test_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,10 @@ def test_run(self):

self.assertIn("Triggering BOL Event", mock.getStdout())
self.assertIn("xsGroups", mock.getStdout())
self.assertIn("Completed EveryNode - cycle 0", mock.getStdout())
self.assertIn(
"Completed EveryNode - timestep: cycle 0, node 0 Event",
mock.getStdout(),
)

def test_clone(self):
testTitle = "CLONE_TEST"
Expand Down
47 changes: 23 additions & 24 deletions armi/operators/operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,19 +487,15 @@ def _interactAll(self, interactionName, activeInterfaces, *args):

halt = False

cycleNodeTag = self._expandCycleAndTimeNodeArgs(
*args, interactionName=interactionName
)
cycleNodeTag = self._expandCycleAndTimeNodeArgs(interactionName)
runLog.header(
"=========== Triggering {} Event ===========".format(
interactionName + cycleNodeTag
)
)

for statePointIndex, interface in enumerate(activeInterfaces, start=1):
self.printInterfaceSummary(
interface, interactionName, statePointIndex, *args
)
self.printInterfaceSummary(interface, interactionName, statePointIndex)

# maybe make this a context manager
if printMemUsage:
Expand Down Expand Up @@ -546,41 +542,42 @@ def _finalizeInteract(self):
"""
pass

def printInterfaceSummary(self, interface, interactionName, statePointIndex, *args):
def printInterfaceSummary(self, interface, interactionName, statePointIndex):
"""
Log which interaction point is about to be executed.
This looks better as multiple lines but it's a lot easier to grep as one line.
We leverage newlines instead of long banners to save disk space.
"""
nodeInfo = self._expandCycleAndTimeNodeArgs(
*args, interactionName=interactionName
)
nodeInfo = self._expandCycleAndTimeNodeArgs(interactionName)
line = "=========== {:02d} - {:30s} {:15s} ===========".format(
statePointIndex, interface.name, interactionName + nodeInfo
)
runLog.header(line)

@staticmethod
def _expandCycleAndTimeNodeArgs(*args, interactionName):
def _expandCycleAndTimeNodeArgs(self, interactionName):
"""Return text annotating information for current run event.
Notes
-----
- Init, BOL, EOL: empty
- Everynode: (cycle, time node)
- BOC: cycle number
- Coupling: iteration number
- Everynode: cycle, time node
- BOC, EOC: cycle number
- Coupled: cycle, time node, iteration number
"""
cycleNodeInfo = ""
if args:
if len(args) == 1:
if interactionName == "Coupled":
cycleNodeInfo = f" - iteration {args[0]}"
elif interactionName in ("BOC", "EOC"):
cycleNodeInfo = f" - cycle {args[0]}"
else:
cycleNodeInfo = f" - cycle {args[0]}, node {args[1]}"
if interactionName == "Coupled":
cycleNodeInfo = (
f" - timestep: cycle {self.r.p.cycle}, node {self.r.p.timeNode}"
f" - iteration {self.r.core.p.coupledIteration}"
)
elif interactionName in ("BOC", "EOC"):
cycleNodeInfo = f" - timestep: cycle {self.r.p.cycle}"
elif interactionName in ("Init", "BOL", "EOL"):
cycleNodeInfo = ""
else:
cycleNodeInfo = (
f" - timestep: cycle {self.r.p.cycle}, node {self.r.p.timeNode}"
)
return cycleNodeInfo

def _debugDB(self, interactionName, interfaceName, statePointIndex=0):
Expand Down Expand Up @@ -1077,6 +1074,8 @@ def detach(self):
"""
if self.r:
self.r.o = None
for comp in self.r:
comp.parent = None
self.r = None
for i in self.interfaces:
i.o = None
Expand Down
2 changes: 2 additions & 0 deletions armi/operators/operatorMPI.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ def _resetWorker(self):
cs = self.cs
bp = self.r.blueprints
spatialGrid = self.r.core.spatialGrid
spatialGrid.armiObject = None
xsGroups = self.getInterface("xsGroups")
if xsGroups:
xsGroups.clearRepresentativeBlocks()
Expand All @@ -246,6 +247,7 @@ def _resetWorker(self):
core = reactors.Core("Core")
self.r.add(core)
core.spatialGrid = spatialGrid
core.spatialGrid.armiObject = core
self.reattach(self.r, cs)

@staticmethod
Expand Down
42 changes: 27 additions & 15 deletions armi/operators/tests/test_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,32 +543,44 @@ def test_getMaxBurnSteps(self):


class TestInterfaceAndEventHeaders(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.o, cls.r = test_reactors.loadTestReactor(
inputFileName="smallestTestReactor/armiRunSmallest.yaml",
customSettings={CONF_TIGHT_COUPLING: True},
)
cls.r.p.cycle = 0
cls.r.p.timeNode = 1
cls.r.core.p.coupledIteration = 7

def test_expandCycleAndTimeNodeArgs_Empty(self):
"""When *args are empty, cycleNodeInfo should be an empty string."""
"""When cycleNodeInfo should be an empty string."""
for task in ["Init", "BOL", "EOL"]:
self.assertEqual(
Operator._expandCycleAndTimeNodeArgs(interactionName=task), ""
self.o._expandCycleAndTimeNodeArgs(interactionName=task), ""
)

def test_expandCycleAndTimeNodeArgs_OneArg(self):
"""When *args is a single value, cycleNodeInfo should return the right string."""
cycle = 0
def test_expandCycleAndTimeNodeArgs_Cycle(self):
"""When cycleNodeInfo should return only the cycle."""
for task in ["BOC", "EOC"]:
self.assertEqual(
Operator._expandCycleAndTimeNodeArgs(cycle, interactionName=task),
f" - cycle {cycle}",
self.o._expandCycleAndTimeNodeArgs(interactionName=task),
f" - timestep: cycle {self.r.p.cycle}",
)

def test_expandCycleAndTimeNodeArgs_EveryNode(self):
"""When cycleNodeInfo should return the cycle and node."""
self.assertEqual(
Operator._expandCycleAndTimeNodeArgs(cycle, interactionName="Coupled"),
f" - iteration {cycle}",
self.o._expandCycleAndTimeNodeArgs(interactionName="EveryNode"),
f" - timestep: cycle {self.r.p.cycle}, node {self.r.p.timeNode}",
)

def test_expandCycleAndTimeNodeArgs_TwoArg(self):
"""When *args is two values, cycleNodeInfo should return the right string."""
cycle, timeNode = 0, 0
def test_expandCycleAndTimeNodeArgs_Coupled(self):
"""When cycleNodeInfo should return the cycle, node, and iteration number."""
self.assertEqual(
Operator._expandCycleAndTimeNodeArgs(
cycle, timeNode, interactionName="EveryNode"
self.o._expandCycleAndTimeNodeArgs(interactionName="Coupled"),
(
f" - timestep: cycle {self.r.p.cycle}, node {self.r.p.timeNode} "
f"- iteration {self.r.core.p.coupledIteration}"
),
f" - cycle {cycle}, node {timeNode}",
)
10 changes: 8 additions & 2 deletions armi/physics/fuelCycle/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,19 @@ def getFuelCycleSettings():
label="Plot shuffle arrows",
),
setting.Setting(
CONF_JUMP_RING_NUM, default=8, label="Jump Ring Number", description="None"
CONF_JUMP_RING_NUM,
default=8,
label="Jump Ring Number",
description="The number of hex rings jumped when distributing the feed assemblies in "
"the alternating concentric rings or checkerboard shuffle patterns (convergent / "
"divergent shuffling).",
),
setting.Setting(
CONF_LEVELS_PER_CASCADE,
default=14,
label="Move per cascade",
description="None",
description="The number of moves made per cascade when performing convergent or "
"divergent shuffle patterns.",
),
]
return settings
Expand Down
12 changes: 6 additions & 6 deletions armi/physics/neutronics/crossSectionGroupManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -1235,12 +1235,12 @@ def createRepresentativeBlocksUsingExistingBlocks(

# create a new block collection that inherits all of the properties
# and settings from oldBlockCollection.
if len(oldBlockCollection._validRepresentativeBlockTypes) > 0:
validBlockTypes = []
for flag in oldBlockCollection._validRepresentativeBlockTypes:
validBlockTypes.append(flags._toString(Flags, flag))
else:
validBlockTypes = None
validBlockTypes = oldBlockCollection._validRepresentativeBlockTypes
if validBlockTypes is not None and len(validBlockTypes) > 0:
validBlockTypes = [
flags._toString(Flags, flag)
for flag in oldBlockCollection._validRepresentativeBlockTypes
]
newBlockCollection = oldBlockCollection.__class__(
oldBlockCollection.allNuclidesInProblem,
validBlockTypes=validBlockTypes,
Expand Down
38 changes: 27 additions & 11 deletions armi/physics/neutronics/tests/test_crossSectionManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -816,16 +816,8 @@ def test_getRepresentativeBlocks(self):
self.assertIsNone(blocks[0].p.detailedNDens)
self.assertIsNone(blocks[1].p.detailedNDens)

def test_createRepresentativeBlocksUsingExistingBlocks(self):
"""
Demonstrates that a new representative block can be generated from an existing
representative block.
Notes
-----
This tests that the XS ID of the new representative block is correct and that the
compositions are identical between the original and the new representative blocks.
"""
def _createRepresentativeBlocksUsingExistingBlocks(self, validBlockTypes):
"""Reusable code used in multiple unit tests."""
o, r = test_reactors.loadTestReactor(
TEST_ROOT, inputFileName="smallestTestReactor/armiRunSmallest.yaml"
)
Expand All @@ -843,7 +835,7 @@ def test_createRepresentativeBlocksUsingExistingBlocks(self):
}
)
o.cs[CONF_CROSS_SECTION].setDefaults(
crossSectionGroupManager.AVERAGE_BLOCK_COLLECTION, ["fuel"]
crossSectionGroupManager.AVERAGE_BLOCK_COLLECTION, validBlockTypes
)
aaSettings = o.cs[CONF_CROSS_SECTION]["AA"]
self.csm.cs = copy.deepcopy(o.cs)
Expand Down Expand Up @@ -877,6 +869,30 @@ def test_createRepresentativeBlocksUsingExistingBlocks(self):
continue
self.assertEqual(baSettingValue, aaSettings.__dict__[setting])

def test_createRepresentativeBlocksUsingExistingBlocks(self):
"""
Demonstrates that a new representative block can be generated from an existing
representative block.
Notes
-----
This tests that the XS ID of the new representative block is correct and that the
compositions are identical between the original and the new representative blocks.
"""
self._createRepresentativeBlocksUsingExistingBlocks(["fuel"])

def test_createRepresentativeBlocksUsingExistingBlocksDisableValidBlockTypes(self):
"""
Demonstrates that a new representative block can be generated from an existing
representative block with the setting `disableBlockTypeExclusionInXsGeneration: true`.
Notes
-----
This tests that the XS ID of the new representative block is correct and that the
compositions are identical between the original and the new representative blocks.
"""
self._createRepresentativeBlocksUsingExistingBlocks(True)

def test_interactBOL(self):
"""Test `BOL` lattice physics update frequency.
Expand Down
10 changes: 0 additions & 10 deletions armi/reactor/assemblies.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,6 @@ class Assembly(composites.Composite):
"""
A single assembly in a reactor made up of blocks built from the bottom up.
Append blocks to add them up. Index blocks with 0 being the bottom.
Attributes
----------
pinNum : int
The number of pins in this assembly.
pinPeakingFactors : list of floats
The assembly-averaged pin power peaking factors. This is the ratio of pin
power to AVERAGE pin power in an assembly.
"""

pDefs = assemblyParameters.getAssemblyParameterDefinitions()
Expand Down Expand Up @@ -82,7 +73,6 @@ def __init__(self, typ, assemNum=None):
self.setType(typ)
self._current = 0 # for iterating
self.p.buLimit = self.getMaxParam("buLimit")
self.pinPeakingFactors = [] # assembly-averaged pin power peaking factors
self.lastLocationLabel = self.LOAD_QUEUE

def __repr__(self):
Expand Down
58 changes: 58 additions & 0 deletions armi/reactor/blueprints/componentBlueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,66 @@ class can then use to modify the isotopics as necessary.
constructedObject = components.factory(shape, [], kwargs)
_setComponentFlags(constructedObject, self.flags, blueprint)
insertDepletableNuclideKeys(constructedObject, blueprint)

# set the custom density for non-custom material components after construction
self.setCustomDensity(constructedObject, blueprint, matMods)

return constructedObject

def setCustomDensity(self, constructedComponent, blueprint, matMods):
"""Apply a custom density to a material with custom isotopics but not a 'custom material'."""
if self.isotopics is None:
# No custom isotopics specified
return

density = blueprint.customIsotopics[self.isotopics].density
if density is None:
# Nothing to do
return

if density <= 0:
runLog.error(
"A zero or negative density was specified in a custom isotopics input. "
"This is not permitted, if a 0 density material is needed, use 'Void'. "
"The component is {} and the isotopics entry is {}.".format(
constructedComponent, self.isotopics
)
)
raise ValueError(
"A zero or negative density was specified in the custom isotopics for a component"
)

mat = materials.resolveMaterialClassByName(self.material)()
if not isinstance(mat, materials.Custom):
# check for some problem cases
if "TD_frac" in matMods.keys():
runLog.warning(
"Both TD_frac and a custom density (custom isotopics) has been specified for "
"material {}. The custom density will override the density calculated using "
"TD_frac.".format(self.material)
)
if not mat.density(Tc=self.Tinput) > 0:
runLog.error(
"A custom density has been assigned to material '{}', which has no baseline "
"density. Only materials with a starting density may be assigned a density. "
"This comes up e.g. if isotopics are assigned to 'Void'.".format(
self.material
)
)
raise ValueError(
"Cannot apply custom densities to materials without density."
)

densityRatio = density / constructedComponent.density()
constructedComponent.changeNDensByFactor(densityRatio)

runLog.important(
"A custom material density was specified in the custom isotopics for non-custom "
"material {}. The component density has been altered to "
"{}.".format(mat, constructedComponent.density()),
single=True,
)

def _conformKwargs(self, blueprint, matMods):
"""This method gets the relevant kwargs to construct the component."""
kwargs = {"mergeWith": self.mergeWith or "", "isotopics": self.isotopics or ""}
Expand Down
15 changes: 6 additions & 9 deletions armi/reactor/blueprints/isotopicOptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,15 +393,12 @@ def apply(self, material):
material.massFrac = dict(self.massFracs)
if self.density is not None:
if not isinstance(material, materials.Custom):
runLog.warning(
"You either specified a custom mass density or number densities "
"(which implies a mass density) on `{}` with custom isotopics `{}`. "
"This has no effect on this Material class; you can only "
"override mass density on `Custom` "
"materials. Consider switching to number fraction input. "
"Continuing to use {} mass density.".format(
material, self.name, material
)
runLog.important(
"A custom density or number densities has been specified for non-custom "
"material {}. The material object's density will not be updated to prevent unintentional "
"density changes across the model. Only custom materials may have a density "
"specified.".format(material),
single=True,
)
# specifically, non-Custom materials only use refDensity and dLL, mat.customDensity has no effect
return
Expand Down
Loading

0 comments on commit 0af93c5

Please sign in to comment.