Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rotating more parameters in HexBlock.rotate #1877

Closed
wants to merge 55 commits into from
Closed
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
71ca23d
Fix docstring for parameterDefinitions.Category
drewj-tp Sep 13, 2024
2221d28
Move Block rotate tests to separate TestCase
drewj-tp Sep 13, 2024
acf46f6
Add test for HexBlock rotate boundary parameters
drewj-tp Sep 13, 2024
4162cd7
Provide Parameter.hasCategory
drewj-tp Sep 13, 2024
c1e638a
Add parameter Category.rotatable
drewj-tp Sep 13, 2024
4215ce7
Move Category docstrings to attributes
drewj-tp Sep 13, 2024
dea16bc
Add some type hints to parameter methods
drewj-tp Sep 13, 2024
8fcd775
Mark pointsEdgeDpa and pointsCornerDpa as rotatable parameters
drewj-tp Sep 13, 2024
cac6b6f
Only rotate pointsCornerDpa and pointsEdgeDpa in HexBlock.rotate
drewj-tp Sep 13, 2024
2cdcd77
Add release note about HexBlock.rotate only rotating rotatable parame…
drewj-tp Sep 13, 2024
7ff0914
Revert "Provide Parameter.hasCategory"
drewj-tp Sep 18, 2024
ba32f6f
Revert "Add parameter Category.rotatable"
drewj-tp Sep 18, 2024
0474b59
Revert "Mark pointsEdgeDpa and pointsCornerDpa as rotatable parameters"
drewj-tp Sep 18, 2024
e8ded77
Revert "Only rotate pointsCornerDpa and pointsEdgeDpa in HexBlock.rot…
drewj-tp Sep 18, 2024
4549227
Revert "Add release note about HexBlock.rotate only rotating rotatabl…
drewj-tp Sep 18, 2024
e047b1c
Use iterator of parameters for rotating block boundary params
drewj-tp Sep 18, 2024
1378938
Add test for block rotated pin parameters
drewj-tp Sep 18, 2024
6662389
Skeleton of rotating block pin parameters with ParamLocation.CHILDREN
drewj-tp Sep 18, 2024
af9afb2
Type hint HexBlock.rotate args
drewj-tp Sep 18, 2024
d7feb0f
Fix docstring for pin data rotate test
drewj-tp Sep 19, 2024
61fabbb
Call out HexBlock not Block in rotate tests
drewj-tp Sep 19, 2024
b8e7516
Start working on rotating data defined on hex lattices
drewj-tp Sep 20, 2024
f0b9c1b
Use hexagon.rotateHexCellData in HexBlock pin param rotation
drewj-tp Sep 20, 2024
82f11de
Single line skip for scalars and not-defined unrotatable pin data
drewj-tp Sep 20, 2024
ea77cc5
Show rotating of vector data assigned at hex cell points
drewj-tp Sep 20, 2024
50df31c
Refactor hex cell data rotate tests to reduce duplicate code
drewj-tp Sep 20, 2024
4c939e7
Check two ring hex rotation data against wide variety of rotations
drewj-tp Sep 20, 2024
5f63878
Improve docstrings for TestHexCellRotate
drewj-tp Sep 20, 2024
2ebfa57
Add a maybe silly test that matrix data at hex cells can be rotated
drewj-tp Sep 20, 2024
fdd8c43
Test rotation of hex data for three rings
drewj-tp Sep 20, 2024
3494e4d
Change a helper test method name to be more explicit
drewj-tp Sep 20, 2024
bd5e662
Fix rotateHexCellData to account for number of cells per edge
drewj-tp Sep 20, 2024
0e5c200
Test rotating hex data with many rings
drewj-tp Sep 20, 2024
a580b3a
Fix rotatedHexCellData to cound entries along first axis
drewj-tp Sep 20, 2024
bcbac9c
Add test that hex cell data in lists can also be rotated
drewj-tp Sep 20, 2024
0c944b4
Docs for rotateHexCellData
drewj-tp Sep 20, 2024
1c174e7
rotateHexCellData no longer takes cells entry
drewj-tp Sep 20, 2024
de16d2a
Flush out more Block.rotate checks for pin data
drewj-tp Sep 20, 2024
28d16d8
Add release note for HexBlock.rotate rotating ParamLocation.CHILDREN …
drewj-tp Sep 20, 2024
14bb890
Add docstring for Category.thermalHydraulics
drewj-tp Sep 20, 2024
63a4095
Add docstring for ParamLocation.CHILDREN
drewj-tp Sep 20, 2024
4741505
Allow rotateHexCellData to do nothing on empty data
drewj-tp Sep 20, 2024
ca410f0
Add more useful exception if block parameter fails to be rotated
drewj-tp Sep 20, 2024
586b850
Add test for invalid rotatable data types
drewj-tp Sep 20, 2024
52f7d50
Merge branch 'main' into drewj/rotate-block-params/1860
drewj-tp Sep 20, 2024
f893079
Apply suggestions from code review
drewj-tp Sep 20, 2024
28d1a35
Update HexBlock.rotate docs to describe rotation logic
drewj-tp Sep 24, 2024
2ddc312
Add docstring for HexBlock._rotatePinParameters
drewj-tp Sep 24, 2024
888c25a
Merge remote-tracking branch 'origin/main' into drewj/rotate-block-pa…
drewj-tp Sep 24, 2024
a41bbc6
Add THcornTemp and THedgeTemp to HexBlock.rotate tests
drewj-tp Sep 24, 2024
6839fcf
Minor test doc update
drewj-tp Sep 24, 2024
8b81b3e
Expand HexBlock.rotate tests to include list and array data types
drewj-tp Sep 24, 2024
c5e52bb
Merge remote-tracking branch 'origin/main' into drewj/rotate-block-pa…
drewj-tp Sep 26, 2024
00c487f
Use ParameterCollection.where for HexBlock.rotate
drewj-tp Sep 26, 2024
e6b7a5a
Remove unused Callable type var from Blocks.py
drewj-tp Sep 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions armi/physics/neutronics/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,12 @@ def _getNeutronicsBlockParams():
setter=isNumpyArray("pointsEdgeDpa"),
units=units.DPA,
description="displacements per atom at edges of the block",
categories=["cumulative", "detailedAxialExpansion", "depletion"],
categories=[
"cumulative",
"detailedAxialExpansion",
"depletion",
parameters.Category.rotatable,
],
)

pb.defParam(
Expand Down Expand Up @@ -368,7 +373,12 @@ def _getNeutronicsBlockParams():
units=units.DPA,
description="displacements per atom at corners of the block",
location=ParamLocation.CORNERS,
categories=["cumulative", "detailedAxialExpansion", "depletion"],
categories=[
"cumulative",
"detailedAxialExpansion",
"depletion",
parameters.Category.rotatable,
],
)

pb.defParam(
Expand Down
35 changes: 30 additions & 5 deletions armi/reactor/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

Assemblies are made of blocks. Blocks are made of components.
"""
from typing import Optional, Type, Tuple, ClassVar
from typing import Optional, Type, Tuple, ClassVar, Callable
import collections
import copy
import math
Expand All @@ -42,7 +42,7 @@
from armi.reactor.components.basicShapes import Hexagon, Circle
from armi.reactor.components.complexShapes import Helix
from armi.reactor.flags import Flags
from armi.reactor.parameters import ParamLocation
from armi.reactor.parameters import ParamLocation, Parameter, Category
from armi.utils import densityTools
from armi.utils import hexagon
from armi.utils import iterables
Expand Down Expand Up @@ -2004,6 +2004,9 @@ def setPinPowers(self, powers, powerKeySuffix=""):
else:
self.p.linPowByPin = self.p[powerKey]

def _paramsWhere(self, f: Callable[[Parameter], bool]):
return filter(f, self.p.paramDefs)

def rotate(self, rad):
"""
Rotates a block's spatially varying parameters by a specified angle in the
Expand All @@ -2022,6 +2025,22 @@ def rotate(self, rad):
in 60-degree increments (i.e., PI/6, PI/3, PI, 2 * PI/3, 5 * PI/6,
and 2 * PI)

Notes
-----
Parameters marked with the ``rotatable``
:class:`~armi.reactor.parameters.parameterDefinitions.Category` will be rotated.
These parameters must adhere to one of the following rules to be rotated:
drewj-tp marked this conversation as resolved.
Show resolved Hide resolved

* triplets of float signifying rotation about the x, y, and z dimension like `Block.p.orientation`, or
* vectors of scalars with a number of dimensions equal to the number of edges in the bounding
surface of that ``Block`` e.g., ``Block.p.pointsCornerDpa`` should have six elements to be rotatable
on a :class:`armi.reactor.blocks.HexBlock`
* vectors or arrays with the outer dimension corresponding to a number of pins in a lattice of that
block. For example, ``Block.p.percentBuByPin`` is a vector of linear power for each pin in the block.
The center pin does not rotate so the first row of ``percentBuByPin`` will not be updated.
The first ring of pins has data in entries ``percentBuByPin[1:7]`` and will be shifted to correspond
to the new location. Similar for the second ring of pins in ``percentByPyBin[7:19]`` and so on.

"""
rotNum = round((rad % (2 * math.pi)) / math.radians(60))
self._rotatePins(rotNum)
Expand All @@ -2038,9 +2057,15 @@ def _rotateBoundaryParameters(self, rotNum: int):
rotations have taken place.

"""
names = self.p.paramDefs.atLocation(ParamLocation.CORNERS).names
names += self.p.paramDefs.atLocation(ParamLocation.EDGES).names
for name in names:
params = self._paramsWhere(
lambda pd: pd.hasCategory(Category.rotatable)
and (
pd.atLocation(ParamLocation.EDGES)
or pd.atLocation(ParamLocation.CORNERS)
)
)
for param in params:
name = param.name
original = self.p[name]
if isinstance(original, (list, np.ndarray)):
if len(original) == 6:
Expand Down
55 changes: 30 additions & 25 deletions armi/reactor/parameters/parameterDefinitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,38 +55,39 @@


class Category:
"""
A "namespace" for storing parameter categories.

Notes
-----
* `cumulative` parameters are accumulated over many time steps
* `pinQuantities` parameters are defined on the pin level within a block
* `multiGroupQuantities` parameters have group dependence (often a 1D numpy array)
* `fluxQuantities` parameters are related to neutron or gamma flux
* `neutronics` parameters are calculated in a neutronics global flux solve
* `gamma` parameters are calculated in a fixed-source gamma solve
* `detailedAxialExpansion` parameters are marked as such so that they are mapped from the
uniform mesh back to the non-uniform mesh
* `reactivity coefficients` parameters are related to reactivity coefficient or kinetics
parameters for kinetics solutions
* `thermal hydraulics` parameters come from a thermal hydraulics physics plugin (e.g., flow
rates, temperatures, etc.)
"""
"""A "namespace" for storing parameter categories."""

depletion = "depletion"
"""Parameters used in or calculated by a depletion plugin."""
cumulative = "cumulative"
"""Parameters are accumulated over many time steps"""
cumulativeOverCycle = "cumulative over cycle"
"""Parameters that are reset at beginning of cycle and accumulated over each cycle."""
assignInBlueprints = "assign in blueprints"
"""Parameters that should be assigned in blueprints (e.g., control rod elevation)"""
retainOnReplacement = "retain on replacement"
pinQuantities = "pinQuantities"
"""Parameters are defined on the pin level within a block"""
fluxQuantities = "fluxQuantities"
"""Parameters are related to neutron or gamma flux"""
drewj-tp marked this conversation as resolved.
Show resolved Hide resolved
multiGroupQuantities = "multi-group quantities"
"""Parameters have group dependence (often a 1D numpy array)"""
neutronics = "neutronics"
"""Parameters are calculated in a neutronics global flux solve"""
gamma = "gamma"
"""Parameters are calculated in a fixed-source gamma solve"""
detailedAxialExpansion = "detailedAxialExpansion"
"""Parameters that are mapped from the uniform mesh back to the non-uniform mesh"""
reactivityCoefficients = "reactivity coefficients"
"""Parameters are related to reactivity coefficient or kinetics parameters for kinetics solutions"""
thermalHydraulics = "thermal hydraulics"
"""Parameters come from a thermal hydraulics physics plugin (e.g., flow rates, temperatures, etc.)"""
rotatable = "rotatable"
"""Parameters that should be rotated.

For parameters defined for and used on hexagonal reactors, see
:meth:`armi.reactors.blocks.HexBlock.rotate` for instructions on how rotatable
parmeters should be structured."""


class ParamLocation(enum.Flag):
Expand Down Expand Up @@ -417,10 +418,14 @@ def restoreBackup(self, paramsToApply):
else:
self._backup, self.assigned = self._backup

def atLocation(self, loc):
def atLocation(self, loc: ParamLocation) -> bool:
"""True if parameter is defined at location."""
return self.location and self.location & loc

def hasCategory(self, category: str) -> bool:
"""True if a parameter has a specific category."""
return category in self.categories


class ParameterDefinitionCollection:
"""
Expand All @@ -438,7 +443,7 @@ class ParameterDefinitionCollection:
__slots__ = ("_paramDefs", "_paramDefDict", "_representedTypes", "_locked")

def __init__(self):
self._paramDefs = list()
self._paramDefs: list[Parameter] = list()
self._paramDefDict = dict()
self._representedTypes = set()
self._locked = False
Expand Down Expand Up @@ -505,14 +510,14 @@ def extend(self, other):
for pd in other:
self.add(pd)

def inCategory(self, categoryName):
def inCategory(self, categoryName: str):
"""
Create a :py:class:`ParameterDefinitionCollection` that contains definitions that are in a
specific category.
"""
return self._filter(lambda pd: categoryName in pd.categories)

def atLocation(self, paramLoc):
def atLocation(self, paramLoc: ParamLocation):
"""
Make a param definition collection with all defs defined at a specific location.

Expand Down Expand Up @@ -571,22 +576,22 @@ def byNameAndCollectionType(self, name, collectionType):
return self._paramDefDict[name, collectionType]

@property
def categories(self):
def categories(self) -> set[str]:
"""Get the categories of all the :py:class:`~Parameter` instances within this collection."""
categories = set()
for paramDef in self:
categories |= paramDef.categories
return categories

@property
def names(self):
def names(self) -> list[str]:
return [pd.name for pd in self]

def lock(self):
self._locked = True

@property
def locked(self):
def locked(self) -> bool:
return self._locked

def toWriteToDB(self, assignedMask: Optional[int] = None):
Expand Down
108 changes: 76 additions & 32 deletions armi/reactor/tests/test_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from armi.reactor.flags import Flags
from armi.reactor.tests.test_assemblies import makeTestAssembly
from armi.tests import ISOAA_PATH, TEST_ROOT
from armi.utils import hexagon, units
from armi.utils import hexagon, units, iterables
from armi.utils.units import MOLES_PER_CC_TO_ATOMS_PER_BARN_CM

NUM_PINS_IN_TEST_BLOCK = 217
Expand Down Expand Up @@ -1451,37 +1451,6 @@ def test_106_getAreaFractions(self):

self.assertAlmostEqual(sum(fracs.values()), sum([a for c, a in cur]))

def test_rotatePins(self):
b = self.block
b.setRotationNum(0)
index = b._rotatePins(0, justCompute=True)
self.assertEqual(b.getRotationNum(), 0)
self.assertEqual(index[5], 5)
self.assertEqual(index[2], 2) # pin 1 is center and never rotates.

index = b._rotatePins(1)
self.assertEqual(b.getRotationNum(), 1)
self.assertEqual(index[2], 3)
self.assertEqual(b.p.pinLocation[1], 3)

index = b._rotatePins(1)
self.assertEqual(b.getRotationNum(), 2)
self.assertEqual(index[2], 4)
self.assertEqual(b.p.pinLocation[1], 4)

index = b._rotatePins(2)
index = b._rotatePins(4) # over-rotate to check modulus
self.assertEqual(b.getRotationNum(), 2)
self.assertEqual(index[2], 4)
self.assertEqual(index[6], 2)
self.assertEqual(b.p.pinLocation[1], 4)
self.assertEqual(b.p.pinLocation[5], 2)

self.assertRaises(ValueError, b._rotatePins, -1)
self.assertRaises(ValueError, b._rotatePins, 10)
self.assertRaises((ValueError, TypeError), b._rotatePins, None)
self.assertRaises((ValueError, TypeError), b._rotatePins, "a")

def test_expandElementalToIsotopics(self):
r"""Tests the expand to elementals capability."""
initialN = {}
Expand Down Expand Up @@ -1770,6 +1739,81 @@ def test_getReactionRates(self):
)


class BlockRotateTests(unittest.TestCase):
"""Tests for the ability for a block to be rotated."""

ROTATABLE_BOUNDARY_PARAMS = ["pointsEdgeDpa", "pointsCornerDpa"]

STATIC_BOUNDARY_PARAMS = [
"cornerFastFlux",
drewj-tp marked this conversation as resolved.
Show resolved Hide resolved
"pointsCornerFastFluxFr",
"pointsCornerDpaRate",
"pointsEdgeFastFluxFr",
"pointsEdgeDpaRate",
]
BOUNDARY_DATA = np.arange(6, dtype=float) * 10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the rotate method work on things that aren't numpy arrays? Like a standard python list? We probably should work on both data types and test that. We had an error early on where Mongoose was adding the data to the TH parameters as numpy objects and that caused errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arrays and lists only. Tested in bcbac9c

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how that test is related to the boundary data rotation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My fault. While the comment is on the boundary data attribute, the call out to "rotate method" focused me elsewhere. I'll modify this test to rotate some boundary params as lists

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does 8b81b3e look?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with the content of the test. Honestly, it took me a minute to parse what you were doing in _assignParamData. It might be worth elaborating on the comment to be like even index parameters in the the names list get an array and odd index parameters in the names list get a list. Also it would help to rename referenceData to referenceArrayData and listData to referenceListData.


def setUp(self):
self.block = loadTestBlock()
for name in self.STATIC_BOUNDARY_PARAMS + self.ROTATABLE_BOUNDARY_PARAMS:
self.block.p[name] = self.BOUNDARY_DATA

def test_rotatePins(self):
"""Test rotate pins updates pin locations."""
b = self.block
b.setRotationNum(0)
index = b._rotatePins(0, justCompute=True)
self.assertEqual(b.getRotationNum(), 0)
self.assertEqual(index[5], 5)
self.assertEqual(index[2], 2) # pin 1 is center and never rotates.

index = b._rotatePins(1)
self.assertEqual(b.getRotationNum(), 1)
self.assertEqual(index[2], 3)
self.assertEqual(b.p.pinLocation[1], 3)

index = b._rotatePins(1)
self.assertEqual(b.getRotationNum(), 2)
self.assertEqual(index[2], 4)
self.assertEqual(b.p.pinLocation[1], 4)

index = b._rotatePins(2)
index = b._rotatePins(4) # over-rotate to check modulus
self.assertEqual(b.getRotationNum(), 2)
self.assertEqual(index[2], 4)
self.assertEqual(index[6], 2)
self.assertEqual(b.p.pinLocation[1], 4)
self.assertEqual(b.p.pinLocation[5], 2)

self.assertRaises(ValueError, b._rotatePins, -1)
self.assertRaises(ValueError, b._rotatePins, 10)
self.assertRaises((ValueError, TypeError), b._rotatePins, None)
self.assertRaises((ValueError, TypeError), b._rotatePins, "a")

def test_rotateBoundaryParameters(self):
"""Test that boundary parameters are correctly rotated."""
# No rotation == no changes to data
self._rotateAndCompareBoundaryParams(0, self.BOUNDARY_DATA)
for rotNum in range(1, 6):
expected = iterables.pivot(self.BOUNDARY_DATA, -rotNum)
self._rotateAndCompareBoundaryParams(rotNum, expected)
# undo rotation to restore state for next test
self.block._rotateBoundaryParameters(6 - rotNum)
# Six rotations of 60 degrees puts us back to the original layout
self._rotateAndCompareBoundaryParams(6, self.BOUNDARY_DATA)

def _rotateAndCompareBoundaryParams(self, rotNum: int, expected: np.ndarray):
self.block._rotateBoundaryParameters(rotNum)
for name in self.ROTATABLE_BOUNDARY_PARAMS:
data = self.block.p[name]
msg = f"{name=} :: {rotNum=} :: {data=}"
np.testing.assert_array_equal(data, expected, err_msg=msg)
for name in self.STATIC_BOUNDARY_PARAMS:
data = self.block.p[name]
msg = f"{name=} :: {data=}"
np.testing.assert_array_equal(data, self.BOUNDARY_DATA, err_msg=msg)


class BlockEnergyDepositionConstants(unittest.TestCase):
"""Tests the energy deposition methods.

Expand Down
8 changes: 8 additions & 0 deletions armi/reactor/tests/test_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,10 +456,18 @@ class MockPC(parameters.ParameterCollection):
self.assertEqual(p2.categories, set(["awesome", "stuff", "bacon"]))
self.assertEqual(p3.categories, set(["bacon"]))

for p in [p1, p2, p3]:
self._testCategoryConsistency(p)

self.assertEqual(set(pc.paramDefs.inCategory("awesome")), set([p1, p2]))
self.assertEqual(set(pc.paramDefs.inCategory("stuff")), set([p1, p2]))
self.assertEqual(set(pc.paramDefs.inCategory("bacon")), set([p2, p3]))

def _testCategoryConsistency(self, p: parameters.Parameter):
for category in p.categories:
self.assertTrue(p.hasCategory(category))
self.assertFalse(p.hasCategory("this_shouldnot_exist"))

def test_parameterCollectionsHave__slots__(self):
"""Tests we prevent accidental creation of attributes."""
self.assertEqual(
Expand Down
2 changes: 2 additions & 0 deletions doc/release/0.4.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ New Features
:func:`armi.utils.hexagon.getIndexOfRotatedCell`
(`PR#1846 <https://github.com/terrapower/armi/1846`)
#. Allow merging a component with zero area into another component (`PR#1858 <https://github.com/terrapower/armi/pull/1858>`_)
#. :meth:`armi.reactor.blocks.HexBlock.rotate` only rotates parameters that are marked with
the ``rotatable`` :class:`~armi.reactor.parameters.parameterDefinitions.Category`
#. TBD

API Changes
Expand Down
Loading