Skip to content

Commit

Permalink
Merge da2be57 into 827d223
Browse files Browse the repository at this point in the history
  • Loading branch information
albeanth committed Mar 26, 2024
2 parents 827d223 + da2be57 commit fb03d36
Show file tree
Hide file tree
Showing 13 changed files with 1,847 additions and 1,526 deletions.
2 changes: 1 addition & 1 deletion armi/reactor/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -1623,7 +1623,7 @@ class during axial expansion.
See Also
--------
armi.reactor.converters.axialExpansionChanger.py::ExpansionData::_setTargetComponents
armi.reactor.converters.axialExpansion.expansionData.py::ExpansionData::_setTargetComponents
"""
self.p.axialExpTargetComponent = targetComponent.name

Expand Down
11 changes: 7 additions & 4 deletions armi/reactor/blueprints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@
from armi.reactor.blueprints.componentBlueprint import ComponentKeyedList
from armi.reactor.blueprints.gridBlueprint import Grids, Triplet
from armi.reactor.blueprints.reactorBlueprint import Systems, SystemBlueprint
from armi.reactor.converters import axialExpansionChanger
from armi.reactor.converters.axialExpansion.axialExpansionChanger import (
expandColdDimsToHot,
)
from armi.reactor.converters.axialExpansion import makeAssemsAbleToSnapToUniformMesh
from armi.reactor.flags import Flags
from armi.settings.fwSettings.globalSettings import (
CONF_DETAILED_AXIAL_EXPANSION,
Expand Down Expand Up @@ -151,7 +154,7 @@ def __new__(mcs, name, bases, attrs):
else:
pluginSections = pm.hook.defineBlueprintsSections()
for plug in pluginSections:
for (attrName, section, resolver) in plug:
for attrName, section, resolver in plug:
assert isinstance(section, yamlize.Attribute)
if attrName in attrs:
raise plugins.PluginError(
Expand Down Expand Up @@ -307,7 +310,7 @@ def _prepConstruction(self, cs):
# this is required to set up assemblies so they know how to snap
# to the reference mesh. They wont know the mesh to conform to
# otherwise....
axialExpansionChanger.makeAssemsAbleToSnapToUniformMesh(
makeAssemsAbleToSnapToUniformMesh(
self.assemblies.values(), cs[CONF_NON_UNIFORM_ASSEM_FLAGS]
)

Expand All @@ -326,7 +329,7 @@ def _prepConstruction(self, cs):
for a in list(self.assemblies.values())
if not any(a.hasFlags(f) for f in assemsToSkip)
)
axialExpansionChanger.expandColdDimsToHot(
expandColdDimsToHot(
assemsToExpand,
cs[CONF_DETAILED_AXIAL_EXPANSION],
)
Expand Down
56 changes: 56 additions & 0 deletions armi/reactor/converters/axialExpansion/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Copyright 2023 TerraPower, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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.

"""Particles! Expand axially!."""

from armi.materials import material
from armi.reactor.flags import Flags


def getSolidComponents(b):
"""
Return list of components in the block that have solid material.
Notes
-----
Axial expansion only needs to be applied to solid materials. We should not update
number densities on fluid materials to account for changes in block height.
"""
return [c for c in b if not isinstance(c.material, material.Fluid)]


def _getDefaultReferenceAssem(assems):
"""Return a default reference assembly."""
# if assemblies are defined in blueprints, handle meshing
# assume finest mesh is reference
assemsByNumBlocks = sorted(
assems,
key=lambda a: len(a),
reverse=True,
)
return assemsByNumBlocks[0] if assemsByNumBlocks else None


def makeAssemsAbleToSnapToUniformMesh(
assems, nonUniformAssemFlags, referenceAssembly=None
):
"""Make this set of assemblies aware of the reference mesh so they can stay uniform as they axially expand."""
if not referenceAssembly:
referenceAssembly = _getDefaultReferenceAssem(assems)
# make the snap lists so assems know how to expand
nonUniformAssems = [Flags.fromStringIgnoreErrors(t) for t in nonUniformAssemFlags]
for a in assems:
if any(a.hasFlags(f) for f in nonUniformAssems):
continue
a.makeAxialSnapList(referenceAssembly)
220 changes: 220 additions & 0 deletions armi/reactor/converters/axialExpansion/assemblyAxialLinkage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
# Copyright 2023 TerraPower, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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.

from armi import runLog
from armi.reactor.flags import Flags
from armi.reactor.components import UnshapedComponent
from armi.reactor.converters.axialExpansion import getSolidComponents


class AssemblyAxialLinkage:
"""Determines and stores the block- and component-wise axial linkage for an assembly.
Attributes
----------
a : :py:class:`Assembly <armi.reactor.assemblies.Assembly>`
reference to original assembly; is directly modified/changed during expansion.
linkedBlocks : dict
keys --> :py:class:`Block <armi.reactor.blocks.Block>`
values --> list of axially linked blocks; index 0 = lower linked block; index 1: upper linked block.
see also: self._getLinkedBlocks()
linkedComponents : dict
keys --> :py:class:`Component <armi.reactor.components.component.Component>`
values --> list of axially linked components; index 0 = lower linked component; index 1: upper linked component.
see also: self._getLinkedComponents
"""

def __init__(self, StdAssem):
self.a = StdAssem
self.linkedBlocks = {}
self.linkedComponents = {}
self._determineAxialLinkage()

def _determineAxialLinkage(self):
"""Gets the block and component based linkage."""
for b in self.a:
self._getLinkedBlocks(b)
for c in getSolidComponents(b):
self._getLinkedComponents(b, c)

def _getLinkedBlocks(self, b):
"""Retrieve the axial linkage for block b.
Parameters
----------
b : :py:class:`Block <armi.reactor.blocks.Block>`
block to determine axial linkage for
Notes
-----
- block linkage is determined by matching ztop/zbottom (see below)
- block linkage is stored in self.linkedBlocks[b]
_ _
| |
| 2 | Block 2 is linked to block 1.
|_ _|
| |
| 1 | Block 1 is linked to both block 0 and 1.
|_ _|
| |
| 0 | Block 0 is linked to block 1.
|_ _|
"""
lowerLinkedBlock = None
upperLinkedBlock = None
block_list = self.a.getChildren()
for idx, otherBlk in enumerate(block_list):
if b.name != otherBlk.name:
if b.p.zbottom == otherBlk.p.ztop:
lowerLinkedBlock = otherBlk
elif b.p.ztop == otherBlk.p.zbottom:
upperLinkedBlock = otherBlk
else:
bIdx = idx

self.linkedBlocks[b] = [lowerLinkedBlock, upperLinkedBlock]

if lowerLinkedBlock is None and bIdx != 0:
# only print if this isn't the bottom block
runLog.debug(
f"Assembly {self.a.getName()} at location {self.a.getLocation()}, Block {b}"
"is not linked to a block below!",
single=True,
)
if upperLinkedBlock is None and bIdx != idx:
# only print if this isn't the topmost block
runLog.debug(
f"Assembly {self.a.getName()} at location {self.a.getLocation()}, Block {b}"
"is not linked to a block above!",
single=True,
)

def _getLinkedComponents(self, b, c):
"""Retrieve the axial linkage for component c.
Parameters
----------
b : :py:class:`Block <armi.reactor.blocks.Block>`
key to access blocks containing linked components
c : :py:class:`Component <armi.reactor.components.component.Component>`
component to determine axial linkage for
Raises
------
RuntimeError
multiple candidate components are found to be axially linked to a component
"""
lstLinkedC = [None, None]
for ib, linkdBlk in enumerate(self.linkedBlocks[b]):
if linkdBlk is not None:
for otherC in getSolidComponents(linkdBlk.getChildren()):
if self._determineLinked(c, otherC):
if lstLinkedC[ib] is not None:
errMsg = (
"Multiple component axial linkages have been found for "
f"Component {c}; Block {b}; Assembly {b.parent}."
" This is indicative of an error in the blueprints! Linked components found are"
f"{lstLinkedC[ib]} and {otherC}"
)
runLog.error(msg=errMsg)
raise RuntimeError(errMsg)
lstLinkedC[ib] = otherC

self.linkedComponents[c] = lstLinkedC

if lstLinkedC[0] is None and self.linkedBlocks[b][0] is not None:
# only print debug if there is a linked block below in the first place
runLog.debug(
f"Assembly {self.a}, Block {b}, Component {c} has nothing linked below it!",
single=True,
)
if (
lstLinkedC[1] is None
and self.linkedBlocks[b][1] is not None
and not self.linkedBlocks[b][1].hasFlags(Flags.DUMMY)
):
# only print debug is there is a linked block above in the first place,
# and if that linked block is not the DUMMY block
runLog.debug(
f"Assembly {self.a}, Block {b}, Component {c} has nothing linked above it!",
single=True,
)

@staticmethod
def _determineLinked(componentA, componentB):
"""Determine axial component linkage for two components.
Parameters
----------
componentA : :py:class:`Component <armi.reactor.components.component.Component>`
component of interest
componentB : :py:class:`Component <armi.reactor.components.component.Component>`
component to compare and see if is linked to componentA
Notes
-----
- Requires that shapes have the getCircleInnerDiameter and getBoundingCircleOuterDiameter defined
- For axial linkage to be True, components MUST be solids, the same Component Class, multiplicity, and meet inner
and outer diameter requirements.
- When component dimensions are retrieved, cold=True to ensure that dimensions are evaluated
at cold/input temperatures. At temperature, solid-solid interfaces in ARMI may produce
slight overlaps due to thermal expansion. Handling these potential overlaps are out of scope.
Returns
-------
linked : bool
status is componentA and componentB are axially linked to one another
"""
if (
(componentA.containsSolidMaterial() and componentB.containsSolidMaterial())
and isinstance(componentA, type(componentB))
and (componentA.getDimension("mult") == componentB.getDimension("mult"))
):
if isinstance(componentA, UnshapedComponent):
runLog.warning(
f"Components {componentA} and {componentB} are UnshapedComponents "
"and do not have 'getCircleInnerDiameter' or getBoundingCircleOuterDiameter methods; "
"nor is it physical to do so. Instead of crashing and raising an error, "
"they are going to be assumed to not be linked.",
single=True,
)
linked = False
else:
idA, odA = (
componentA.getCircleInnerDiameter(cold=True),
componentA.getBoundingCircleOuterDiameter(cold=True),
)
idB, odB = (
componentB.getCircleInnerDiameter(cold=True),
componentB.getBoundingCircleOuterDiameter(cold=True),
)

biggerID = max(idA, idB)
smallerOD = min(odA, odB)
if biggerID >= smallerOD:
# one object fits inside the other
linked = False
else:
linked = True

else:
linked = False

return linked

0 comments on commit fb03d36

Please sign in to comment.