Skip to content

Commit

Permalink
Merge 49dfddc into 827d223
Browse files Browse the repository at this point in the history
  • Loading branch information
albeanth committed Mar 22, 2024
2 parents 827d223 + 49dfddc commit bcfe5f2
Show file tree
Hide file tree
Showing 14 changed files with 2,424 additions and 2,146 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
12 changes: 9 additions & 3 deletions armi/reactor/blueprints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,20 @@
from armi.reactor import assemblies
from armi.reactor import geometry
from armi.reactor import systemLayoutInput
# 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
# without adding detail
from armi.reactor.blueprints import isotopicOptions
from armi.reactor.blueprints.assemblyBlueprint import AssemblyKeyedList
from armi.reactor.blueprints.blockBlueprint import BlockKeyedList
from armi.reactor.blueprints.componentBlueprint import ComponentGroups
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 @@ -307,7 +313,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 +332,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)
203 changes: 203 additions & 0 deletions armi/reactor/converters/axialExpansion/assemblyAxialLinkage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# 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 otherBlk in 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

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

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
Loading

0 comments on commit bcfe5f2

Please sign in to comment.