Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/terrapower/armi.git into rz…
Browse files Browse the repository at this point in the history
…theta_unittest
  • Loading branch information
sombrereau committed Dec 15, 2022
2 parents b1a7f20 + ce71855 commit c1604ca
Show file tree
Hide file tree
Showing 20 changed files with 584 additions and 204 deletions.
2 changes: 1 addition & 1 deletion .coveragerc
Expand Up @@ -7,7 +7,7 @@ omit =
venv/
source = armi
# change default .coverage file to something that doesn't have a dot
# because our Windows file server can't handle dots. :s
# because the Windows file server can't handle dots.
data_file = coverage_results.cov

[coverage:run]
Expand Down
14 changes: 13 additions & 1 deletion .github/workflows/coverage.yaml
Expand Up @@ -9,6 +9,7 @@ jobs:

env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}

steps:
- uses: actions/checkout@v2
Expand All @@ -25,5 +26,16 @@ jobs:
- name: Run Coverage Part 1
run: tox -e cov1 || true
- name: Run Coverage Part 2
run: tox -e cov2,report
run: tox -e cov2
- name: Convert Coverage Results
env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
run: |
pip install coveragepy-lcov
coveragepy-lcov --data_file_path coverage_results.cov --output_file_path lcov.txt
- name: Publish to coveralls.io
uses: coverallsapp/github-action@v1.1.2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: lcov.txt

12 changes: 12 additions & 0 deletions armi/bookkeeping/db/databaseInterface.py
Expand Up @@ -144,7 +144,19 @@ def interactEveryNode(self, cycle, node):
Write to database.
DBs should receive the state information of the run at each node.
Notes
-----
- if tight coupling is enabled, the DB will be written in operator.py::Operator::_timeNodeLoop
via writeDBEveryNode
"""
if self.o.cs["numCoupledIterations"]:
# h5 cant handle overwriting so we skip here and write once the tight coupling loop has completed
return
self.writeDBEveryNode(cycle, node)

def writeDBEveryNode(self, cycle, node):
"""write the database at the end of the time node"""
# skip writing for last burn step since it will be written at interact EOC
if node < self.o.burnSteps[cycle]:
self.r.core.p.minutesSinceStart = (
Expand Down
15 changes: 15 additions & 0 deletions armi/bookkeeping/db/tests/test_databaseInterface.py
Expand Up @@ -101,6 +101,12 @@ def tearDown(self):
self.stateRetainer.__exit__()
self.td.__exit__(None, None, None)

def test_interactEveryNodeReturn(self):
"""test that the DB is NOT written to if cs["numCoupledIterations"] != 0"""
self.o.cs["numCoupledIterations"] = 2
self.dbi.interactEveryNode(0, 0)
self.assertFalse(self.dbi.database.hasTimeStep(0, 0))

def test_interactBOL(self):
self.assertIsNotNone(self.dbi._db)
self.dbi.interactBOL()
Expand All @@ -115,6 +121,15 @@ def test_distributable(self):
self.dbi.interactDistributeState()
self.assertEqual(self.dbi.distributable(), 4)

def test_timeNodeLoop_numCoupledIterations(self):
"""test that database is written out after the coupling loop has completed"""
# clear out interfaces (no need to run physics) but leave database
self.o.interfaces = [self.dbi]
self.o.cs["numCoupledIterations"] = 1
self.assertFalse(self.dbi._db.hasTimeStep(0, 0))
self.o._timeNodeLoop(0, 0)
self.assertTrue(self.dbi._db.hasTimeStep(0, 0))


class TestDatabaseWriter(unittest.TestCase):
def setUp(self):
Expand Down
3 changes: 3 additions & 0 deletions armi/operators/operator.py
Expand Up @@ -380,6 +380,9 @@ def _timeNodeLoop(self, cycle, timeNode):
for coupledIteration in range(self.cs["numCoupledIterations"]):
self.r.core.p.coupledIteration = coupledIteration + 1
self.interactAllCoupled(coupledIteration)
# database has not yet been written, so we need to write it.
dbi = self.getInterface("database")
dbi.writeDBEveryNode(cycle, timeNode)

def _interactAll(self, interactionName, activeInterfaces, *args):
"""
Expand Down
10 changes: 10 additions & 0 deletions armi/operators/settingsValidation.py
Expand Up @@ -477,6 +477,16 @@ def _willBeCopiedFrom(fName):
self.NO_ACTION,
)

self.addQuery(
lambda: not self.cs["looseCoupling"]
and self.cs["numCoupledIterations"] > 0,
"You have {0} coupled iterations selected, but have not activated loose coupling.".format(
self.cs["numCoupledIterations"]
),
"Set looseCoupling to True?",
lambda: self._assignCS("looseCoupling", True),
)

self.addQuery(
lambda: self.cs["numCoupledIterations"] > 0,
"You have {0} coupling iterations selected.".format(
Expand Down
5 changes: 5 additions & 0 deletions armi/operators/snapshots.py
Expand Up @@ -63,6 +63,11 @@ def _mainOperate(self):
"Beginning snapshot ({0:02d}, {1:02d})".format(ssCycle, ssNode)
)
dbi.loadState(ssCycle, ssNode)

# need to update reactor power after the database load
# this is normally handled in operator._cycleLoop
self.r.p.core.power = self.cs["power"]

halt = self.interactAllBOC(self.r.p.cycle)
if halt:
break
Expand Down
67 changes: 32 additions & 35 deletions armi/operators/tests/test_operators.py
Expand Up @@ -14,7 +14,7 @@

"""Tests for operators"""

# pylint: disable=abstract-method,no-self-use,unused-argument
# pylint: disable=abstract-method,protected-access,unused-argument
import unittest

from armi import settings
Expand Down Expand Up @@ -44,78 +44,75 @@ class InterfaceC(Interface):

# TODO: Add a test that shows time evolution of Reactor (REQ_EVOLVING_STATE)
class OperatorTests(unittest.TestCase):
def setUp(self):
self.o, self.r = test_reactors.loadTestReactor()

def test_addInterfaceSubclassCollision(self):
self.cs = settings.Settings()
o, r = test_reactors.loadTestReactor()
cs = settings.Settings()

interfaceA = InterfaceA(r, self.cs)
interfaceA = InterfaceA(self.r, cs)

interfaceB = InterfaceB(r, self.cs)
o.addInterface(interfaceA)
interfaceB = InterfaceB(self.r, cs)
self.o.addInterface(interfaceA)

# 1) Adds B and gets rid of A
o.addInterface(interfaceB)
self.assertEqual(o.getInterface("Second"), interfaceB)
self.assertEqual(o.getInterface("First"), None)
self.o.addInterface(interfaceB)
self.assertEqual(self.o.getInterface("Second"), interfaceB)
self.assertEqual(self.o.getInterface("First"), None)

# 2) Now we have B which is a subclass of A,
# we want to not add A (but also not have an error)
o.addInterface(interfaceA)
self.assertEqual(o.getInterface("Second"), interfaceB)
self.assertEqual(o.getInterface("First"), None)
self.o.addInterface(interfaceA)
self.assertEqual(self.o.getInterface("Second"), interfaceB)
self.assertEqual(self.o.getInterface("First"), None)

# 3) Also if another class not a subclass has the same function,
# raise an error
interfaceC = InterfaceC(r, self.cs)
self.assertRaises(RuntimeError, o.addInterface, interfaceC)
interfaceC = InterfaceC(self.r, cs)
self.assertRaises(RuntimeError, self.o.addInterface, interfaceC)

# 4) Check adding a different function Interface
interfaceC.function = "C"
o.addInterface(interfaceC)
self.assertEqual(o.getInterface("Second"), interfaceB)
self.assertEqual(o.getInterface("Third"), interfaceC)
self.o.addInterface(interfaceC)
self.assertEqual(self.o.getInterface("Second"), interfaceB)
self.assertEqual(self.o.getInterface("Third"), interfaceC)

def test_checkCsConsistency(self):
o, _r = test_reactors.loadTestReactor()
o._checkCsConsistency() # passes without error
self.o._checkCsConsistency() # passes without error

o.cs = o.cs.modified(newSettings={"nCycles": 66})
self.o.cs = self.o.cs.modified(newSettings={"nCycles": 66})
with self.assertRaises(RuntimeError):
o._checkCsConsistency()
self.o._checkCsConsistency()

def test_interfaceIsActive(self):
o, _r = test_reactors.loadTestReactor()
self.assertTrue(o.interfaceIsActive("main"))
self.assertFalse(o.interfaceIsActive("Fake-o"))
self.o, _r = test_reactors.loadTestReactor()
self.assertTrue(self.o.interfaceIsActive("main"))
self.assertFalse(self.o.interfaceIsActive("Fake-o"))

def test_loadStateError(self):
"""The loadTestReactor() test tool does not have any history in the DB to load from"""
o, _r = test_reactors.loadTestReactor()

# a first, simple test that this method fails correctly
with self.assertRaises(RuntimeError):
o.loadState(0, 1)
self.o.loadState(0, 1)

def test_couplingIsActive(self):
o, _r = test_reactors.loadTestReactor()
self.assertFalse(o.couplingIsActive())
self.assertFalse(self.o.couplingIsActive())

def test_setStateToDefault(self):
o, _r = test_reactors.loadTestReactor()

# reset the runType for testing
self.assertEqual(o.cs["runType"], "Standard")
o.cs = o.cs.modified(newSettings={"runType": "fake"})
self.assertEqual(o.cs["runType"], "fake")
self.assertEqual(self.o.cs["runType"], "Standard")
self.o.cs = self.o.cs.modified(newSettings={"runType": "fake"})
self.assertEqual(self.o.cs["runType"], "fake")

# validate the method works
cs = o.setStateToDefault(o.cs)
cs = self.o.setStateToDefault(self.o.cs)
self.assertEqual(cs["runType"], "Standard")

def test_snapshotRequest(self):
o, _r = test_reactors.loadTestReactor()
with TemporaryDirectoryChanger():
o.snapshotRequest(0, 1)
self.o.snapshotRequest(0, 1)


class CyclesSettingsTests(unittest.TestCase):
Expand Down
6 changes: 3 additions & 3 deletions armi/physics/neutronics/globalFlux/globalFluxInterface.py
Expand Up @@ -39,7 +39,6 @@
RX_ABS_MICRO_LABELS = ["nGamma", "fission", "nalph", "np", "nd", "nt"]
RX_PARAM_NAMES = ["rateCap", "rateFis", "rateProdN2n", "rateProdFis", "rateAbs"]


# pylint: disable=too-many-public-methods
class GlobalFluxInterface(interfaces.Interface):
"""
Expand Down Expand Up @@ -286,7 +285,7 @@ def __init__(self, label: Optional[str] = None):
self.real = True
self.adjoint = False
self.neutrons = True
self.photons = None
self.photons = False
self.boundaryConditions = {}
self.epsFissionSourceAvg = None
self.epsFissionSourcePoint = None
Expand Down Expand Up @@ -422,7 +421,8 @@ def _performGeometryTransformations(self, makePlots=False):
converter = self.geomConverters.get("axial")
if not converter:
if self.options.detailedAxialExpansion or self.options.hasNonUniformAssems:
converter = uniformMesh.NeutronicsUniformMeshConverter(
converterCls = uniformMesh.converterFactory(self.options)
converter = converterCls(
cs=self.options.cs,
calcReactionRates=self.options.calcReactionRatesOnMeshConversion,
)
Expand Down
Expand Up @@ -77,6 +77,18 @@ def getExecuterCls(self):
return MockGlobalFluxExecuter


class MockGlobalFluxWithExecutersNonUniform(MockGlobalFluxWithExecuters):
def getExecuterOptions(self, label=None):
"""
Return modified executerOptions
"""
opts = globalFluxInterface.GlobalFluxInterfaceUsingExecuters.getExecuterOptions(
self, label=label
)
opts.hasNonUniformAssems = True # to increase test coverage
return opts


class MockGlobalFluxExecuter(globalFluxInterface.GlobalFluxExecuter):
"""Tests for code that uses Executers, which rely on OutputReaders to update state."""

Expand Down Expand Up @@ -180,6 +192,33 @@ def test_getExecuterCls(self):
self.assertEqual(class0, globalFluxInterface.GlobalFluxExecuter)


class TestGlobalFluxInterfaceWithExecutersNonUniform(unittest.TestCase):
"""Tests for global flux execution with non-uniform assemblies."""

@classmethod
def setUpClass(cls):
cs = settings.Settings()
_o, cls.r = test_reactors.loadTestReactor()
cls.r.core.p.keff = 1.0
cls.gfi = MockGlobalFluxWithExecutersNonUniform(cls.r, cs)

def test_executerInteractionNonUniformAssems(self):
gfi, r = self.gfi, self.r
gfi.interactBOC()
gfi.interactEveryNode(0, 0)
r.p.timeNode += 1
gfi.interactEveryNode(0, 1)
gfi.interactEOC()
self.assertAlmostEqual(r.core.p.rxSwing, (1.02 - 1.01) / 1.01 * 1e5)

def test_calculateKeff(self):
self.assertEqual(self.gfi.calculateKeff(), 1.05) # set in mock

def test_getExecuterCls(self):
class0 = globalFluxInterface.GlobalFluxInterfaceUsingExecuters.getExecuterCls()
self.assertEqual(class0, globalFluxInterface.GlobalFluxExecuter)


class TestGlobalFluxResultMapper(unittest.TestCase):
"""
Test that global flux result mappings run.
Expand Down

0 comments on commit c1604ca

Please sign in to comment.