Skip to content

Commit

Permalink
Merge branch 'main' into drewj/improve-assem-axial-linkage
Browse files Browse the repository at this point in the history
  • Loading branch information
john-science authored Oct 9, 2024
2 parents f02fca2 + 6dd28a1 commit dbea10c
Show file tree
Hide file tree
Showing 13 changed files with 2,459 additions and 2,595 deletions.
20 changes: 19 additions & 1 deletion armi/bookkeeping/db/database3.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ def open(self):
self.h5db.attrs["pluginPaths"] = ps
self.h5db.attrs["localCommitHash"] = Database3.grabLocalCommitHash()

def isOpen(self):
return self.h5db is not None

@staticmethod
def writeSystemAttributes(h5db):
"""Write system attributes to the database.
Expand Down Expand Up @@ -1065,9 +1068,24 @@ def _readParams(h5group, compTypeName, comps, allowMissing=False):
if "linkedDims" in attrs:
linkedDims = np.char.decode(attrs["linkedDims"])

unpackedData = data.tolist()
if len(comps) != len(unpackedData):
msg = (
"While unpacking special data for {}, encountered "
"composites and parameter data with unmatched sizes.\n"
"Length of composites list = {}\n"
"Length of data list = {}\n"
"This could indicate an error in data unpacking, which could "
"result in faulty data on the resulting reactor model.".format(
paramName, len(comps), len(unpackedData)
)
)
runLog.error(msg)
raise ValueError(msg)

# iterating of np is not fast...
for c, val, linkedDim in itertools.zip_longest(
comps, data.tolist(), linkedDims, fillvalue=""
comps, unpackedData, linkedDims, fillvalue=""
):
try:
if linkedDim != "":
Expand Down
17 changes: 17 additions & 0 deletions armi/bookkeeping/db/tests/test_database3.py
Original file line number Diff line number Diff line change
Expand Up @@ -841,3 +841,20 @@ def test_readWriteRoundTrip(self):
self.assertEqual(len(evst.getChildren()), 1)
b1 = evst.getChildren()[0].getChildren()[0]
self.assertEqual(b1.p.power, 12345.6)

def test_badData(self):
# create a DB to be modified
self.db.writeToDB(self.r)
self.db.close()

# modify the HDF5 file to corrupt a dataset
with h5py.File(self.db.fileName, "r+") as hf:
circleGroup = hf["c00n00"]["Circle"]
circleMass = np.array(circleGroup["massHmBOL"][()])
badData = circleMass[:-1]
del circleGroup["massHmBOL"]
circleGroup.create_dataset("massHmBOL", data=badData)

with self.assertRaises(ValueError):
with database3.Database3(self.db.fileName, "r") as db:
_r = db.load(0, 0, allowMissing=True)
163 changes: 1 addition & 162 deletions armi/physics/fuelCycle/hexAssemblyFuelMgmtUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,11 @@
-----
We are keeping these in ARMI even if they appear unused internally.
"""
import math

import numpy as np

from armi import runLog
from armi.utils.hexagon import getIndexOfRotatedCell
from armi.reactor.flags import Flags
from armi.utils.hexagon import getIndexOfRotatedCell
from armi.utils.mathematics import findClosest


Expand Down Expand Up @@ -341,162 +339,3 @@ def buildConvergentRingSchedule(chargeRing, dischargeRing=1, coarseFactor=0.0):

# step 4. assemble and return
return convergent, conWidths


def _buildEqRingScheduleHelper(ringSchedule, numRings):
r"""
turns ``ringScheduler`` into explicit list of rings.
Pulled out of buildEqRingSchedule for testing.
Parameters
----------
ringSchedule : list
List of ring bounds that is required to be an even number of entries. These
entries then are used in a from - to approach to add the rings. The from ring will
always be included.
numRings : int
The number of rings in the hex assembly reactor.
Returns
-------
ringList : list
List of all rings in the order they should be shuffled.
Examples
--------
>>> _buildEqRingScheduleHelper([1,5])
[1,2,3,4,5]
>>> _buildEqRingScheduleHelper([1,5,9,6])
[1,2,3,4,5,9,8,7,6]
>>> _buildEqRingScheduleHelper([9,5,3,4,1,2])
[9,8,7,6,5,3,4,1,2]
>>> _buildEqRingScheduleHelper([2,5,1,1])
[2,3,4,5,1]
"""
if len(ringSchedule) % 2 != 0:
runLog.error("Ring schedule: {}".format(ringSchedule))
raise RuntimeError("Ring schedule does not have an even number of entries.")

ringList = []
for i in range(0, len(ringSchedule), 2):
fromRing = ringSchedule[i]
toRing = ringSchedule[i + 1]
numRings = abs(toRing - fromRing) + 1

ringList.extend([int(j) for j in np.linspace(fromRing, toRing, numRings)])

# eliminate doubles (but allow a ring to show up multiple times)
newList = []
lastRing = None
for ring in ringList:
if ring != lastRing:
newList.append(ring)
if ring > numRings:
# error checking
runLog.warning(
"Ring {0} in eqRingSchedule larger than largest ring in reactor {1}. "
"Adjust shuffling.".format(ring, numRings),
single=True,
label="too many rings",
)
lastRing = ring

return newList


def _squaredDistanceFromOrigin(a):
"""Get the squared distance from the origin of an assembly.
Notes
-----
Just a helper for ``buildEqRingSchedule()``
Parameters
----------
a: Assembly
Fully initialize Assembly object; already part of a reactor core.
Returns
-------
float: Distance from reactor center
"""
origin = np.array([0.0, 0.0, 0.0])
p = np.array(a.spatialLocator.getLocalCoordinates())
return ((p - origin) ** 2).sum()


def _assemAngle(a):
"""Get the angle of the Assembly, in the reactor core.
Notes
-----
Just a helper for ``buildEqRingSchedule()``
Parameters
----------
a: Assembly
Fully initialize Assembly object; already part of a reactor core.
Returns
-------
float: Angle position of assembly around the reactor core
"""
x, y, _ = a.spatialLocator.getLocalCoordinates()
return math.atan2(y, x)


def buildEqRingSchedule(core, ringSchedule, circularRingOrder):
r"""
Expands simple ``ringSchedule`` input into full-on location schedule.
Parameters
----------
core : Core object
Fully initialized Core object, for a hex assembly reactor.
ringSchedule : list
List of ring bounds that is required to be an even number of entries. These
entries then are used in a from - to approach to add the rings. The from ring will
always be included.
circularRingOrder : str
From the circularRingOrder setting. Valid values include angle and distanceSmart,
anything else will
Returns
-------
list: location schedule
"""
# start by expanding the user-input eqRingSchedule list into a list containing
# all the rings as it goes.
ringList = _buildEqRingScheduleHelper(ringSchedule, core.getNumRings())

# now build the locationSchedule ring by ring using this ringSchedule
lastRing = 0
locationSchedule = []
for ring in ringList:
assemsInRing = core.getAssembliesInRing(ring, typeSpec=Flags.FUEL)
if circularRingOrder == "angle":
sorter = lambda a: _assemAngle(a)
elif circularRingOrder == "distanceSmart":
if lastRing == ring + 1:
# converging. Put things on the outside first.
sorter = lambda a: -_squaredDistanceFromOrigin(a)
else:
# diverging. Put things on the inside first.
sorter = _squaredDistanceFromOrigin
else:
# purely based on distance. Can mix things up in convergent-divergent cases. Prefer distanceSmart
sorter = _squaredDistanceFromOrigin

assemsInRing = sorted(assemsInRing, key=sorter)
for a in assemsInRing:
locationSchedule.append(a.getLocation())
lastRing = ring

return locationSchedule
39 changes: 1 addition & 38 deletions armi/physics/fuelCycle/tests/test_hexAssemblyFuelMgmtUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,55 +13,18 @@
# limitations under the License.
"""Tests some fuel handling tools, specific to hex-assembly reactors."""
from armi.physics.fuelCycle import hexAssemblyFuelMgmtUtils as hexUtils
from armi.reactor.tests import test_reactors
from armi.tests import ArmiTestHelper, TEST_ROOT
from armi.tests import ArmiTestHelper
from armi.utils import directoryChangers


class TestHexAssemMgmtTools(ArmiTestHelper):
def setUp(self):
# prepare the input files. This is important so the unit tests run from wherever
# they need to run from.
self.td = directoryChangers.TemporaryDirectoryChanger()
self.td.__enter__()

def tearDown(self):
self.td.__exit__(None, None, None)

def test_buildEqRingScheduleHelper(self):
numRings = 9
ringList1 = [1, 5]
buildRing1 = hexUtils._buildEqRingScheduleHelper(ringList1, numRings)
self.assertEqual(buildRing1, [1, 2, 3, 4, 5])

ringList2 = [1, 5, 9, 6]
buildRing2 = hexUtils._buildEqRingScheduleHelper(ringList2, numRings)
self.assertEqual(buildRing2, [1, 2, 3, 4, 5, 9, 8, 7, 6])

ringList3 = [9, 5, 3, 4, 1, 2]
buildRing3 = hexUtils._buildEqRingScheduleHelper(ringList3, numRings)
self.assertEqual(buildRing3, [9, 8, 7, 6, 5, 3, 4, 1, 2])

ringList4 = [2, 5, 1, 1]
buildRing1 = hexUtils._buildEqRingScheduleHelper(ringList4, numRings)
self.assertEqual(buildRing1, [2, 3, 4, 5, 1])

def test_buildEqRingSchedule(self):
_o, r = test_reactors.loadTestReactor(TEST_ROOT)
core = r.core

circularRingOrder = "angle"
locSchedule = hexUtils.buildEqRingSchedule(core, [2, 1], circularRingOrder)
self.assertEqual(locSchedule, ["002-001", "002-002", "001-001"])

circularRingOrder = "distanceSmart"
locSchedule = hexUtils.buildEqRingSchedule(core, [2, 1], circularRingOrder)
self.assertEqual(locSchedule, ["002-001", "002-002", "001-001"])

circularRingOrder = "somethingCrazy"
locSchedule = hexUtils.buildEqRingSchedule(core, [2, 1], circularRingOrder)
self.assertEqual(locSchedule, ["002-001", "002-002", "001-001"])

def test_buildConvergentRingSchedule(self):
schedule, widths = hexUtils.buildConvergentRingSchedule(1, 17, 0)
self.assertEqual(schedule, [1, 17])
Expand Down
18 changes: 9 additions & 9 deletions armi/reactor/assemblies.py
Original file line number Diff line number Diff line change
Expand Up @@ -1237,15 +1237,6 @@ def rotate(self, rad):
Each Block on the Assembly is rotated in turn.
.. impl:: An assembly can be rotated about its z-axis.
:id: I_ARMI_SHUFFLE_ROTATE
:implements: R_ARMI_SHUFFLE_ROTATE
This method loops through every ``Block`` in this ``Assembly`` and rotates
it by a given angle (in radians). The rotation angle is positive in the
counter-clockwise direction. To perform the ``Block`` rotation, the
:py:meth:`armi.reactor.blocks.Block.rotate` method is called.
Parameters
----------
rad: float
Expand All @@ -1266,6 +1257,15 @@ class HexAssembly(Assembly):
def rotate(self, rad: float):
"""Rotate an assembly and its children.
.. impl:: A hexagonal assembly shall support rotating around the z-axis in 60 degree increments.
:id: I_ARMI_ROTATE_HEX
:implements: R_ARMI_ROTATE_HEX
This method loops through every ``Block`` in this ``HexAssembly`` and rotates
it by a given angle (in radians). The rotation angle is positive in the
counter-clockwise direction. To perform the ``Block`` rotation, the
:meth:`armi.reactor.blocks.HexBlock.rotate` method is called.
Parameters
----------
rad : float
Expand Down
17 changes: 11 additions & 6 deletions armi/reactor/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,10 @@ def getSmearDensity(self, cold=True):
all the area is fuel, it has 100% smear density. Lower smear density allows more room for
swelling.
.. warning:: This requires circular fuel and circular cladding. Designs that vary
from this will be wrong. It may make sense in the future to put this somewhere a
bit more design specific.
Warning
-------
This requires circular fuel and circular cladding. Designs that vary from this will be
wrong. It may make sense in the future to put this somewhere a bit more design specific.
Notes
-----
Expand All @@ -225,12 +226,16 @@ def getSmearDensity(self, cold=True):
Returns
-------
smearDensity : float
The smear density as a fraction
float
The smear density as a fraction.
"""
fuels = self.getComponents(Flags.FUEL)
if not fuels:
return 0.0 # Smear density is not computed for non-fuel blocks
# smear density is not computed for non-fuel blocks
return 0.0
elif not self.getNumPins():
# smear density is only defined for pinned blocks
return 0.0

circles = self.getComponentsOfShape(components.Circle)
if not circles:
Expand Down
Loading

0 comments on commit dbea10c

Please sign in to comment.