Skip to content

Commit 71aea22

Browse files
committed
Merge branch 'materialExpansion' of https://github.com/terrapower/armi into materialExpansion
2 parents d2b1ea3 + 07a6ec5 commit 71aea22

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+304
-797
lines changed

.github/pull_request_template.md

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,14 @@
1313
## Checklist
1414

1515
<!--
16-
You (the pull requester) should put an `x` in the boxes below you have completed.
16+
You (the pull requester) should put an `x` if the condition is met.
1717
1818
(If a checkbox doesn't apply to your PR, check it anyway.)
1919
-->
2020

2121
- [ ] This PR has only [one purpose or idea](https://terrapower.github.io/armi/developer/tooling.html#one-idea-one-pr).
2222
- [ ] [Tests](https://terrapower.github.io/armi/developer/tooling.html#test-it) have been added/updated to verify any new/changed code.
23-
24-
<!-- Check the code quality -->
25-
2623
- [ ] The code style follows [good practices](https://terrapower.github.io/armi/developer/standards_and_practices.html).
27-
- [ ] The commit message(s) follow [good practices](https://terrapower.github.io/armi/developer/tooling.html).
28-
29-
<!-- Check the project-level cruft -->
30-
3124
- [ ] The [release notes](https://terrapower.github.io/armi/developer/tooling.html#add-release-notes) have been updated if necessary.
3225
- [ ] The [documentation](https://terrapower.github.io/armi/developer/tooling.html#document-it) is still up-to-date in the `doc` folder.
3326
- [ ] The dependencies are still up-to-date in `pyproject.toml`.

.github/workflows/docs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
- name: Setup Python
2222
uses: actions/setup-python@v2
2323
with:
24-
python-version: 3.9
24+
python-version: 3.13
2525
- name: Update package index
2626
run: sudo apt-get update
2727
- name: Install mpi libs

armi/apps.py

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -115,35 +115,17 @@ def pluginManager(self) -> pluginManager.ArmiPluginManager:
115115
return self._pm
116116

117117
def getSettings(self) -> Dict[str, Setting]:
118-
"""
119-
Return a dictionary containing all Settings defined by the framework and all plugins.
120-
121-
.. impl:: Applications will not allow duplicate settings.
122-
:id: I_ARMI_SETTINGS_UNIQUE
123-
:implements: R_ARMI_SETTINGS_UNIQUE
124-
125-
Each ARMI application includes a collection of Plugins. Among other
126-
things, these plugins can register new settings in addition to
127-
the default settings that come with ARMI. This feature provides a
128-
lot of utility, so application developers can easily configure
129-
their ARMI appliction in customizable ways.
130-
131-
However, it would get confusing if two different plugins registered
132-
a setting with the same name string. Or if a plugin registered a
133-
setting with the same name as an ARMI default setting. So this
134-
method throws an error if such a situation arises.
135-
"""
118+
"""Return a dictionary containing all Settings defined by the framework and all plugins."""
136119
# Start with framework settings
137120
settingDefs = {
138121
setting.name: setting for setting in fwSettings.getFrameworkSettings()
139122
}
140123

141-
# The optionsCache stores options that may have come from a plugin before the
142-
# setting to which they apply. Whenever a new setting is added, we check to see
143-
# if there are any options in the cache, popping them out and adding them to the
144-
# setting. If all plugins' settings have been processed and the cache is not
145-
# empty, that's an error, because a plugin must have provided options to a
146-
# setting that doesn't exist.
124+
# The optionsCache stores options that may have come from a plugin before the setting to
125+
# which they apply. Whenever a new setting is added, we check to see if there are any
126+
# options in the cache, popping them out and adding them to the setting. If all plugins'
127+
# settings have been processed and the cache is not empty, that's an error, because a plugin
128+
# must have provided options to a setting that doesn't exist.
147129
optionsCache: Dict[str, List[settings.Option]] = collections.defaultdict(list)
148130
defaultsCache: Dict[str, settings.Default] = {}
149131

@@ -205,11 +187,10 @@ def getParamRenames(self) -> Dict[str, str]:
205187
"""
206188
Return the parameter renames from all registered plugins.
207189
208-
This renders a merged dictionary containing all parameter renames from all of
209-
the registered plugins. It also performs simple error checking. The result of
210-
this operation is cached, since it is somewhat expensive to perform. If the App
211-
detects that its plugin manager's set of registered plugins has changed, the
212-
cache will be invalidated and recomputed.
190+
This renders a merged dictionary containing all parameter renames from all of the registered
191+
plugins. It also performs simple error checking. The result of this operation is cached,
192+
since it is somewhat expensive to perform. If the App detects that its plugin manager's set
193+
of registered plugins has changed, the cache will be invalidated and recomputed.
213194
"""
214195
cacheInvalid = False
215196
if self._paramRenames is not None:

armi/bookkeeping/db/database.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -724,17 +724,16 @@ def load(
724724
is read from the database.
725725
726726
.. impl:: Users can load a reactor from a DB.
727-
:id: I_ARMI_DB_R_LOAD
728-
:implements: R_ARMI_DB_R_LOAD
727+
:id: I_ARMI_DB_TIME1
728+
:implements: R_ARMI_DB_TIME
729729
730730
This method creates a ``Reactor`` object by reading the reactor state out
731731
of an ARMI database file. This is done by passing in mandatory arguements
732732
that specify the exact place in time you want to load the reactor from.
733733
(That is, the cycle and node numbers.) Users can either pass the settings
734734
and blueprints directly into this method, or it will attempt to read them
735735
from the database file. The primary work done here is to read the hierarchy
736-
of reactor objects from the data file, then reconstruct them in the correct
737-
order.
736+
of reactor objects from the data file, then reconstruct them in the correct order.
738737
739738
Parameters
740739
----------

armi/bookkeeping/db/layout.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ def writeToDB(self, h5group):
383383
"""Write a chunk of data to the database.
384384
385385
.. impl:: Write data to the DB for a given time step.
386-
:id: I_ARMI_DB_TIME
386+
:id: I_ARMI_DB_TIME0
387387
:implements: R_ARMI_DB_TIME
388388
389389
This method writes a snapshot of the current state of the reactor to the
@@ -393,8 +393,7 @@ def writeToDB(self, h5group):
393393
objects are written to the file. Though, this turns out to still be very
394394
powerful. For instance, the data for all ``HexBlock`` children of a given
395395
parent are stored contiguously within the ``HexBlock`` group, and will not
396-
be interleaved with data from the ``HexBlock`` children of any of the
397-
parent's siblings.
396+
be interleaved with data from the ``HexBlock`` children of any of the parent's siblings.
398397
"""
399398
if "layout/type" in h5group:
400399
# It looks like we have already written the layout to DB, skip for now

armi/bookkeeping/db/tests/test_database3.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ def test_load(self):
113113
"""Load a reactor at different time steps, from the database.
114114
115115
.. test:: Load the reactor from the database.
116-
:id: T_ARMI_DB_R_LOAD
117-
:tests: R_ARMI_DB_R_LOAD
116+
:id: T_ARMI_DB_TIME1
117+
:tests: R_ARMI_DB_TIME
118118
"""
119119
self.makeShuffleHistory()
120120
with self.assertRaises(KeyError):
@@ -301,7 +301,7 @@ def test_writeToDB(self):
301301
"""Test writing to the database.
302302
303303
.. test:: Write a single time step of data to the database.
304-
:id: T_ARMI_DB_TIME
304+
:id: T_ARMI_DB_TIME0
305305
:tests: R_ARMI_DB_TIME
306306
"""
307307
self.r.p.cycle = 0

armi/bookkeeping/report/reportingUtils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,8 +476,8 @@ def writeAssemblyMassSummary(r):
476476
blockType = b.getType()
477477
if blockType not in types:
478478
types.append(blockType)
479-
# if the BOL fuel assem is in the center of the core, its area is 1/3 of the full area b/c it's a sliced assem.
480-
# bug: mass came out way high for a case once. 265 MT vs. 92 MT hm.
479+
# If the BOL fuel assem is in the center of the core, its area is 1/3 of the full area b/c
480+
# its a sliced assem.
481481

482482
# count assemblies
483483
core = r.core

armi/cases/tests/test_cases.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -648,13 +648,7 @@ def test_copyInterfaceInputs_nonFilePath(self):
648648
self.assertEqual(newSettings[testSetting], fakeShuffle)
649649

650650
def test_failOnDuplicateSetting(self):
651-
"""
652-
That that if a plugin attempts to add a duplicate setting, it raises an error.
653-
654-
.. test:: Plugins cannot register duplicate settings.
655-
:id: T_ARMI_SETTINGS_UNIQUE
656-
:tests: R_ARMI_SETTINGS_UNIQUE
657-
"""
651+
"""That that if a plugin attempts to add a duplicate setting, it raises an error."""
658652
# register the new Plugin
659653
app = getApp()
660654
app.pluginManager.register(TestPluginWithDuplicateSetting)

armi/materials/material.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -146,18 +146,7 @@ def __repr__(self):
146146

147147
@property
148148
def name(self):
149-
"""
150-
Getter for the private name attribute of this Material.
151-
152-
.. impl:: The name of a material is accessible.
153-
:id: I_ARMI_MAT_NAME
154-
:implements: R_ARMI_MAT_NAME
155-
156-
Every instance of an ARMI material must have a simple, human-readable string name. And,
157-
if possible, we want this string to match the class name. (This, of course, puts some
158-
limits on both the string and the class name.) These names are easily retrievable as a
159-
class property.
160-
"""
149+
"""Getter for the private name attribute of this Material."""
161150
return self._name
162151

163152
@name.setter
@@ -757,6 +746,12 @@ def adjustTD(self, val):
757746
class Fluid(Material):
758747
"""A material that fills its container. Could also be a gas."""
759748

749+
def __init_subclass__(cls):
750+
# Undo the parent-aware density wrapping. Fluids do not expand in the same way solids, so
751+
# Fluid.density(T) is correct. This does not hold for solids because they thermally expand.
752+
if hasattr(cls.density, "__wrapped__"):
753+
cls.density = cls.density.__wrapped__
754+
760755
def getThermalExpansionDensityReduction(self, prevTempInC, newTempInC):
761756
"""Return the factor required to update thermal expansion going from one temperature (in
762757
Celcius) to a new temperature.

armi/materials/tests/test_fluids.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Copyright 2025 TerraPower, LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""Unit tests for fluid-specific behaviors.
15+
16+
The ARMI framework has a lot of thermal expansion machinery that applies to all components
17+
but doesn't make sense for fluids. The tests here help show fluid materials still
18+
play nice with the rest of the framework.
19+
"""
20+
21+
from unittest import TestCase
22+
23+
from armi.materials.material import Fluid, Material
24+
from armi.reactor.components import Circle
25+
from armi.tests import mockRunLogs
26+
27+
28+
class TestFluids(TestCase):
29+
class MyFluid(Fluid):
30+
"""Stand-in fluid that doesn't provide lots of functionality."""
31+
32+
class MySolid(Material):
33+
"""Stand-in solid that doesn't provide lots of functionality."""
34+
35+
def test_fluidDensityWrapperNoWarning(self):
36+
"""Test that Component.material.density does not raise a warning for fluids.
37+
38+
The ARMI Framework contains a mechanism to warn users if they ask for the density of a
39+
material attached to a component. But the component is the source of truth for volume and
40+
composition. And can be thermally expanded during operation. Much of the framework operates
41+
on ``Component.density`` and other ``Component`` methods for mass accounting. However,
42+
``comp.material.density`` does not know about the new composition or volumes and can diverge
43+
from ``component.density``.
44+
45+
Additionally, the framework does not do any thermal expansion on fluids. So the above calls
46+
to ``component.material.density`` are warranted for fluids.
47+
"""
48+
self._checkCompDensityLogs(
49+
mat=self.MySolid(),
50+
nExpectedWarnings=1,
51+
msg="Solids should have the density warning logged.",
52+
)
53+
self._checkCompDensityLogs(
54+
mat=self.MyFluid(),
55+
nExpectedWarnings=0,
56+
msg="Fluids should not have the density warning logged.",
57+
)
58+
59+
def _checkCompDensityLogs(self, mat: Material, nExpectedWarnings: int, msg: str):
60+
comp = Circle(name="test", material=mat, Tinput=20, Thot=20, id=0, od=1, mult=1)
61+
with mockRunLogs.LogCounter() as logs:
62+
comp.material.density(Tc=comp.temperatureInC)
63+
self.assertEqual(logs.messageCounts["warning"], nExpectedWarnings, msg=msg)

0 commit comments

Comments
 (0)