Skip to content

Commit

Permalink
Merge branch 'main' into calcRR_speedup
Browse files Browse the repository at this point in the history
  • Loading branch information
mgjarrett committed Oct 17, 2024
2 parents 8ee7df7 + 8b5163a commit 7d71752
Show file tree
Hide file tree
Showing 60 changed files with 3,694 additions and 3,734 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
4 changes: 2 additions & 2 deletions armi/bookkeeping/db/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@

from armi import runLog
from armi.reactor import grids
from armi.reactor.assemblyLists import AssemblyList
from armi.reactor.components import Component
from armi.reactor.composites import ArmiObject
from armi.reactor.excoreStructure import ExcoreStructure
from armi.reactor.reactors import Core
from armi.reactor.reactors import Reactor

Expand Down Expand Up @@ -356,7 +356,7 @@ def _initComps(self, caseTitle, bp):
comp = Klass(caseTitle, bp)
elif issubclass(Klass, Core):
comp = Klass(name)
elif issubclass(Klass, AssemblyList):
elif issubclass(Klass, ExcoreStructure):
comp = Klass(name)
elif issubclass(Klass, Component):
# init all dimensions to 0, they will be loaded and assigned after load
Expand Down
166 changes: 165 additions & 1 deletion armi/bookkeeping/db/tests/test_database3.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for the Database3 class."""
from glob import glob
import os
import shutil
import subprocess
import unittest
Expand All @@ -21,9 +23,13 @@

from armi.bookkeeping.db import _getH5File
from armi.bookkeeping.db import database3
from armi.bookkeeping.db.jaggedArray import JaggedArray
from armi.bookkeeping.db.databaseInterface import DatabaseInterface
from armi.bookkeeping.db.jaggedArray import JaggedArray
from armi.reactor import parameters
from armi.reactor.excoreStructure import ExcoreCollection, ExcoreStructure
from armi.reactor.reactors import Core
from armi.reactor.reactors import Reactor
from armi.reactor.spentFuelPool import SpentFuelPool
from armi.reactor.tests.test_reactors import loadTestReactor, reduceTestReactorRings
from armi.settings.fwSettings.globalSettings import CONF_SORT_REACTOR
from armi.tests import TEST_ROOT
Expand Down Expand Up @@ -694,3 +700,161 @@ def test_computeParents(self):
self.assertEqual(
database3.Layout.computeAncestors(serialNums, numChildren, 3), expected_3
)


class TestWriteReadDatabase(unittest.TestCase):
"""Round-trip tests that we can write/read data to and from a Database."""

SMALL_YAML = """!include refOneBlockReactor.yaml
systems:
core:
grid name: core
origin:
x: 0.0
y: 0.0
z: 0.0
sfp:
type: sfp
grid name: sfp
origin:
x: 1000.0
y: 1000.0
z: 1000.0
evst:
type: excore
grid name: evst
origin:
x: 2000.0
y: 2000.0
z: 2000.0
grids:
core:
geom: hex_corners_up
lattice map: |
IC
symmetry: full
evst:
lattice pitch:
x: 32.0
y: 32.0
geom: hex
symmetry: full
"""

def setUp(self):
self.td = TemporaryDirectoryChanger()
self.td.__enter__()

# copy these test files over, so we can edit them
thisDir = self.td.destination
yamls = glob(os.path.join(TEST_ROOT, "smallestTestReactor", "*.yaml"))
for yam in yamls:
shutil.copy(os.path.join(TEST_ROOT, "smallestTestReactor", yam), thisDir)

# Add an EVST to this reactor
with open("refSmallestReactor.yaml", "w") as f:
f.write(self.SMALL_YAML)

self.o, self.r = loadTestReactor(thisDir, inputFileName="armiRunSmallest.yaml")
self.dbi = DatabaseInterface(self.r, self.o.cs)
self.dbi.initDB(fName=self._testMethodName + ".h5")
self.db: database3.Database3 = self.dbi.database

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

def test_readWriteRoundTrip(self):
"""Test DB some round tripping, writing some data to a DB, then reading from it.
In particular, we test some parameters on the reactor, core, and blocks. And we move an
assembly from the core to an EVST between timenodes, and test that worked.
"""
# put some data in the DB, for timenode 0
self.r.p.cycle = 0
self.r.p.timeNode = 0
self.r.core.p.keff = 0.99
b = self.r.core.getFirstBlock()
b.p.power = 12345.6

self.db.writeToDB(self.r)

# put some data in the DB, for timenode 1
self.r.p.timeNode = 1
self.r.core.p.keff = 1.01

# move the assembly from the core to the EVST
a = self.r.core.getFirstAssembly()
loc = self.r.excore.evst.spatialGrid[(0, 0, 0)]
self.r.core.remove(a)
self.r.excore.evst.add(a, loc)

self.db.writeToDB(self.r)

# close the DB
self.db.close()

# open the DB and verify, the first timenode
with database3.Database3(self._testMethodName + ".h5", "r") as db:
r0 = db.load(0, 0, allowMissing=True)
self.assertEqual(r0.p.cycle, 0)
self.assertEqual(r0.p.timeNode, 0)
self.assertEqual(r0.core.p.keff, 0.99)

# check the types of the data model objects
self.assertTrue(isinstance(r0, Reactor))
self.assertTrue(isinstance(r0.core, Core))
self.assertTrue(isinstance(r0.excore, ExcoreCollection))
self.assertTrue(isinstance(r0.excore.evst, ExcoreStructure))
self.assertTrue(isinstance(r0.excore.sfp, SpentFuelPool))

# Prove our one special block is in the core
self.assertEqual(len(r0.core.getChildren()), 1)
b0 = r0.core.getFirstBlock()
self.assertEqual(b0.p.power, 12345.6)

# the ex-core structures should be empty
self.assertEqual(len(r0.excore["sfp"].getChildren()), 0)
self.assertEqual(len(r0.excore["evst"].getChildren()), 0)

# open the DB and verify, the second timenode
with database3.Database3(self._testMethodName + ".h5", "r") as db:
r1 = db.load(0, 1, allowMissing=True)
self.assertEqual(r1.p.cycle, 0)
self.assertEqual(r1.p.timeNode, 1)
self.assertEqual(r1.core.p.keff, 1.01)

# check the types of the data model objects
self.assertTrue(isinstance(r1, Reactor))
self.assertTrue(isinstance(r1.core, Core))
self.assertTrue(isinstance(r1.excore, ExcoreCollection))
self.assertTrue(isinstance(r1.excore.evst, ExcoreStructure))
self.assertTrue(isinstance(r1.excore.sfp, SpentFuelPool))

# Prove our one special block is NOT in the core, or the SFP
self.assertEqual(len(r1.core.getChildren()), 0)
self.assertEqual(len(r1.excore["sfp"].getChildren()), 0)
self.assertEqual(len(r1.excore.sfp.getChildren()), 0)

# Prove our one special block is in the EVST
evst = r1.excore["evst"]
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)
3 changes: 3 additions & 0 deletions armi/bookkeeping/historyTracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ def __init__(self, r, cs):
self.xsHistory = {}
self._preloadedBlockHistory = None

msg = "The HistoryTrackerInterface is deprecated, and will be removed."
runLog.warning(msg)

def interactBOL(self):
self.addDetailAssembliesBOL()

Expand Down
6 changes: 4 additions & 2 deletions armi/bookkeeping/memoryProfiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,10 @@ def _reactorAssemblyTrackingBreakdown(self):
"Dict {:30s} has {:4d} ArmiObjects".format(attrName, len(attrObj))
)

if self.r.sfp is not None:
runLog.important("SFP has {:4d} ArmiObjects".format(len(self.r.sfp)))
if self.r.excore.get("sfp") is not None:
runLog.important(
"SFP has {:4d} ArmiObjects".format(len(self.r.excore["sfp"]))
)

def checkForDuplicateObjectsOnArmiModel(self, attrName, refObject):
"""Scans thorugh ARMI model for duplicate objects."""
Expand Down
64 changes: 60 additions & 4 deletions armi/bookkeeping/report/reportInterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from armi.reactor.flags import Flags
from armi.utils import directoryChangers
from armi.utils import reportPlotting
from armi.utils import units

ORDER = interfaces.STACK_ORDER.BEFORE + interfaces.STACK_ORDER.BOOKKEEPING

Expand Down Expand Up @@ -166,11 +167,66 @@ def writeReports(self):
report_.writeHTML()

# --------------------------------------------
# Misc Summaries
# Ex-Core Summaries
# --------------------------------------------
def writeRunSummary(self):
"""Make a summary of the run."""
# spent fuel pool report
if self.r.sfp is not None:
self.r.sfp.report()
self.r.sfp.count()
if self.r.excore.get("sfp") is not None:
self.reportSFP(self.r.excore["sfp"])
self.countAssembliesSFP(self.r.excore["sfp"])

@staticmethod
def reportSFP(sfp):
"""A high-level summary of the Spent Fuel Pool."""
title = "SpentFuelPool Report"
runLog.important("-" * len(title))
runLog.important(title)
runLog.important("-" * len(title))
totFis = 0.0
for a in sfp.getChildren():
runLog.important(
"{assembly:15s} discharged at t={dTime:10f} after {residence:10f} yrs. It entered at cycle: {cycle}. "
"It has {fiss:10f} kg (x {mult}) fissile and peak BU={bu:.2f} %.".format(
assembly=a,
dTime=a.p.dischargeTime,
residence=(a.p.dischargeTime - a.p.chargeTime),
cycle=a.p.chargeCycle,
fiss=a.getFissileMass(),
bu=a.getMaxParam("percentBu"),
mult=a.p.multiplicity,
)
)
totFis += a.getFissileMass() * a.p.multiplicity / 1000 # convert to kg

runLog.important(
"Total SFP fissile inventory of {0} is {1:.4E} MT".format(
sfp, totFis / 1000.0
)
)

@staticmethod
def countAssembliesSFP(sfp):
"""Report on the count of assemblies in the SFP at each timestep."""
if not sfp.getChildren():
return

runLog.important("Count:")
totCount = 0
thisTimeCount = 0
a = sfp.getChildren()[0]
lastTime = a.getAge() / units.DAYS_PER_YEAR + a.p.chargeTime

for a in sfp.getChildren():
thisTime = a.getAge() / units.DAYS_PER_YEAR + a.p.chargeTime

if thisTime != lastTime:
runLog.important(
"Number of assemblies moved at t={0:6.2f}: {1:04d}. Cumulative: {2:04d}".format(
lastTime, thisTimeCount, totCount
)
)
lastTime = thisTime
thisTimeCount = 0
totCount += 1 # noqa: SIM113
thisTimeCount += 1
14 changes: 9 additions & 5 deletions armi/bookkeeping/tests/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@
also re-exported by `__init__.py`, so that other things (like the documentation system)
can use it without having to import the rest of ARMI.
"""
import os

from armi.tests import TEST_ROOT


# These files are needed to run the data_model ipython notebook, which is done in
# test_historyTracker, and when building the docs.
TUTORIAL_FILES = [
"anl-afci-177-blueprints.yaml",
"anl-afci-177-coreMap.yaml",
"anl-afci-177-fuelManagement.py",
"anl-afci-177.yaml",
"data_model.ipynb",
os.path.join(TEST_ROOT, "anl-afci-177", "anl-afci-177-blueprints.yaml"),
os.path.join(TEST_ROOT, "anl-afci-177", "anl-afci-177-coreMap.yaml"),
os.path.join(TEST_ROOT, "anl-afci-177", "anl-afci-177-fuelManagement.py"),
os.path.join(TEST_ROOT, "anl-afci-177", "anl-afci-177.yaml"),
os.path.join(TEST_ROOT, "tutorials", "data_model.ipynb"),
]
Loading

0 comments on commit 7d71752

Please sign in to comment.