Skip to content

Commit

Permalink
Refactoring Axial Expansion (#1861)
Browse files Browse the repository at this point in the history
  • Loading branch information
john-science authored Sep 10, 2024
1 parent 2f95074 commit 5b3984b
Show file tree
Hide file tree
Showing 9 changed files with 980 additions and 915 deletions.
4 changes: 0 additions & 4 deletions armi/reactor/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -1565,10 +1565,6 @@ class during axial expansion.
---------
targetComponent: :py:class:`Component <armi.reactor.components.component.Component>` object
Component specified to be target component for axial expansion changer
See Also
--------
armi.reactor.converters.axialExpansionChanger.py::ExpansionData::_setTargetComponents
"""
self.p.axialExpTargetComponent = targetComponent.name

Expand Down
2 changes: 1 addition & 1 deletion armi/reactor/blueprints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ def _prepConstruction(self, cs):
for a in list(self.assemblies.values())
if not any(a.hasFlags(f) for f in assemsToSkip)
)
axialExpansionChanger.expandColdDimsToHot(
axialExpansionChanger.axialExpansionChanger.expandColdDimsToHot(
assemsToExpand,
cs[CONF_DETAILED_AXIAL_EXPANSION],
)
Expand Down
908 changes: 0 additions & 908 deletions armi/reactor/converters/axialExpansionChanger.py

This file was deleted.

29 changes: 29 additions & 0 deletions armi/reactor/converters/axialExpansionChanger/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright 2024 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.
"""Enable component-wise axial expansion for assemblies and/or a reactor."""

# ruff: noqa: F401
from armi.reactor.converters.axialExpansionChanger.assemblyAxialLinkage import (
AssemblyAxialLinkage,
)
from armi.reactor.converters.axialExpansionChanger.axialExpansionChanger import (
AxialExpansionChanger,
)
from armi.reactor.converters.axialExpansionChanger.axialExpansionChanger import (
makeAssemsAbleToSnapToUniformMesh,
)
from armi.reactor.converters.axialExpansionChanger.expansionData import ExpansionData
from armi.reactor.converters.axialExpansionChanger.expansionData import (
getSolidComponents,
)
213 changes: 213 additions & 0 deletions armi/reactor/converters/axialExpansionChanger/assemblyAxialLinkage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
# Copyright 2024 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.components import UnshapedComponent
from armi.reactor.converters.axialExpansionChanger.expansionData import (
getSolidComponents,
)


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


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.
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.
"""

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]

if lowerLinkedBlock is None:
runLog.debug(
"Assembly {0:22s} at location {1:22s}, Block {2:22s}"
"is not linked to a block below!".format(
str(self.a.getName()),
str(self.a.getLocation()),
str(b.p.flags),
),
single=True,
)
if upperLinkedBlock is None:
runLog.debug(
"Assembly {0:22s} at location {1:22s}, Block {2:22s}"
"is not linked to a block above!".format(
str(self.a.getName()),
str(self.a.getLocation()),
str(b.p.flags),
),
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 _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 "
f"components found are {lstLinkedC[ib]} and {otherC}"
)
runLog.error(msg=errMsg)
raise RuntimeError(errMsg)
lstLinkedC[ib] = otherC

self.linkedComponents[c] = lstLinkedC

if lstLinkedC[0] is None:
runLog.debug(
f"Assembly {self.a}, Block {b}, Component {c} has nothing linked below it!",
single=True,
)
if lstLinkedC[1] is None:
runLog.debug(
f"Assembly {self.a}, Block {b}, Component {c} has nothing linked above it!",
single=True,
)
Loading

0 comments on commit 5b3984b

Please sign in to comment.