Skip to content

Commit

Permalink
Merge 64b0b38 into 6a4eb3b
Browse files Browse the repository at this point in the history
  • Loading branch information
jakehader committed Oct 3, 2022
2 parents 6a4eb3b + 64b0b38 commit c090fb6
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 50 deletions.
17 changes: 17 additions & 0 deletions armi/physics/neutronics/settings.py
Expand Up @@ -24,6 +24,10 @@
)
from armi.settings import setting
from armi.utils import directoryChangers
from armi.settings.fwSettings.globalSettings import (
CONF_DETAILED_AXIAL_EXPANSION,
CONF_NON_UNIFORM_ASSEM_FLAGS,
)


CONF_BC_COEFFICIENT = "bcCoefficient"
Expand Down Expand Up @@ -509,4 +513,17 @@ def migrateDpaGridPlate():
)
)

queries.append(
settingsValidation.Query(
lambda: inspector.cs[CONF_DETAILED_AXIAL_EXPANSION]
and inspector.cs[CONF_NON_UNIFORM_ASSEM_FLAGS],
f"The use of {CONF_DETAILED_AXIAL_EXPANSION} and {CONF_NON_UNIFORM_ASSEM_FLAGS} is not supported.",
"Automatically set non-uniform assembly treatment to its default?",
lambda: inspector._assignCS(
CONF_NON_UNIFORM_ASSEM_FLAGS,
inspector.cs.getSetting(CONF_NON_UNIFORM_ASSEM_FLAGS).default,
),
)
)

return queries
2 changes: 1 addition & 1 deletion armi/physics/neutronics/tests/test_neutronicsPlugin.py
Expand Up @@ -249,7 +249,7 @@ def test_neutronicsSettingsValidators(self):
cs = settings.Settings()
inspector = settingsValidation.Inspector(cs)
sv = getNeutronicsSettingValidators(inspector)
self.assertEqual(len(sv), 7)
self.assertEqual(len(sv), 8)

# Test the Query: boundaries are now "Extrapolated", not "Normal"
cs = cs.modified(newSettings={"boundaries": "Normal"})
Expand Down
17 changes: 17 additions & 0 deletions armi/reactor/converters/tests/test_uniformMesh.py
Expand Up @@ -410,19 +410,36 @@ def setUp(self):
def test_reactorConversion(self):
"""Tests the reactor conversion to and from the original reactor."""
self.assertTrue(self.converter._hasNonUniformAssems)
self.assertTrue(self.r.core.lib)
self.assertEqual(self.r.core.p.keff, 1.0)

controlAssems = self.r.core.getAssemblies(Flags.PRIMARY | Flags.CONTROL)
# Add a bunch of multi-group flux to the control assemblies
# in the core to demonstrate that data can be mapped back
# to the original control rod assemblies if they are changed.
# Additionally, this will check that block-level reaction rates
# are being calculated (i.e., `rateAbs`).
for a in controlAssems:
for b in a:
b.p.mgFlux = [1.0] * 33
self.assertFalse(b.p.rateAbs)

self.converter.convert(self.r)

self.assertEqual(
len(controlAssems),
len(self.converter._nonUniformAssemStorage),
)

self.converter.applyStateToOriginal()
self.assertEqual(
len(self.converter._nonUniformAssemStorage),
0,
)
for a in controlAssems:
for b in a:
self.assertTrue(all(b.getMgFlux()))
self.assertTrue(b.p.rateAbs)


if __name__ == "__main__":
Expand Down
97 changes: 48 additions & 49 deletions armi/reactor/converters/uniformMesh.py
Expand Up @@ -11,7 +11,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Converts reactor with arbitrary axial meshing (e.g. multiple assemblies with different
axial meshes) to one with a global uniform axial mesh.
Expand Down Expand Up @@ -63,19 +62,16 @@

import armi
from armi import runLog
from armi import settings
from armi.utils.mathematics import average1DWithinTolerance
from armi.utils import iterables
from armi.utils import plotting
from armi.reactor import grids
from armi.reactor.reactors import Core
from armi.reactor.flags import Flags
from armi.reactor.converters.geometryConverters import GeometryConverter
from armi.reactor import parameters
from armi.reactor.reactors import Reactor

# unfortunate physics coupling, but still in the framework
from armi.physics.neutronics.globalFlux import globalFluxInterface


class UniformMeshGeometryConverter(GeometryConverter):
"""
Expand All @@ -99,6 +95,7 @@ def __init__(self, cs=None):
self._uniformMesh = None
self.reactorParamNames = []
self.blockParamNames = []
self.calcReactionRates = False

# These dictionaries represent back-up data from the source reactor
# that can be recovered if the data is not being brought back from
Expand Down Expand Up @@ -231,6 +228,7 @@ def applyStateToOriginal(self):
storedAssem,
self.blockParamNames,
mapNumberDensities=False,
calcReactionRates=self.calcReactionRates,
)

# Remove the stored assembly from the temporary storage list
Expand Down Expand Up @@ -397,6 +395,7 @@ def setAssemblyStateFromOverlaps(
destinationAssembly,
blockParamNames=None,
mapNumberDensities=True,
calcReactionRates=False,
):
"""
Set state data (i.e., number densities and block-level parameters) on a assembly based on a source
Expand Down Expand Up @@ -426,6 +425,10 @@ def setAssemblyStateFromOverlaps(
This can show up in specific instances with moving meshes (i.e., control rods) in some applications.
In those cases, the mapping of number densities can be treated independent of this more general
implementation.
calcReactionRates : bool, optional
If True, the neutron reaction rates will be calculated on each block within the destination
assembly. Note that this will skip the reaction rate calculations for a block if it does
not contain a valid multi-group flux.
See Also
--------
Expand Down Expand Up @@ -537,6 +540,22 @@ def setAssemblyStateFromOverlaps(
destBlock, blockParamNames, cachedParams
)

# If requested, the reaction rates will be calculated based on the
# mapped neutron flux and the XS library.
if calcReactionRates:
core = sourceAssembly.getAncestor(lambda c: isinstance(c, Core))
if core is not None:
UniformMeshGeometryConverter._calculateReactionRates(
lib=core.lib, keff=core.p.keff, assem=destinationAssembly
)
else:
runLog.warning(
f"Reaction rates requested for {destinationAssembly}, but no core object exists. This calculation "
"will be skipped.",
single=True,
label="Block reaction rate calculation skipped due to insufficient multi-group flux data.",
)

@staticmethod
def _applyCachedParamValues(destBlock, paramNames, cachedParams):
"""
Expand Down Expand Up @@ -732,6 +751,29 @@ def _mapStateFromReactorToOther(
"""
pass

@staticmethod
def _calculateReactionRates(lib, keff, assem):
"""
Calculates the neutron reaction rates on the given assembly.
Notes
-----
If a block in the assembly does not contain any multi-group flux
than the reaction rate calculation for this block will be skipped.
"""
from armi.physics.neutronics.globalFlux import globalFluxInterface

for b in assem:
# Checks if the block has a multi-group flux defined and if it
# does not then this will skip the reaction rate calculation. This
# is captured by the TypeError, due to a `NoneType` divide by float
# error.
try:
b.getMgFlux()
except TypeError:
continue
globalFluxInterface.calcReactionRates(b, keff, lib)


class NeutronicsUniformMeshConverter(UniformMeshGeometryConverter):
"""
Expand Down Expand Up @@ -780,34 +822,6 @@ def _setParamsToUpdate(self):
for category in self.BLOCK_PARAM_MAPPING_CATEGORIES:
self.blockParamNames.extend(b.p.paramDefs.inCategory(category).names)

def _checkConversion(self):
"""
Make sure both reactors have the same power and that it's equal to user-input.
On the initial neutronics run, of course source power will be zero.
"""
UniformMeshGeometryConverter._checkConversion(self)
sourcePow = self._sourceReactor.core.getTotalBlockParam("power")
convPow = self.convReactor.core.getTotalBlockParam("power")
if sourcePow > 0.0 and convPow > 0.0:
if abs(sourcePow - convPow) / sourcePow > 1e-5:
runLog.info(
f"Source reactor power ({sourcePow}) is too different from "
f"converted power ({convPow})."
)

if self._sourceReactor.p.timeNode != 0:
# only check on nodes other than BOC
expectedPow = (
self._sourceReactor.core.p.power
/ self._sourceReactor.core.powerMultiplier
)
if sourcePow and abs(sourcePow - expectedPow) / sourcePow > 1e-5:
raise ValueError(
f"Source reactor power ({sourcePow}) is too different from "
f"user-input power ({expectedPow})."
)

def _mapStateFromReactorToOther(
self, sourceReactor, destReactor, mapNumberDensities=True
):
Expand Down Expand Up @@ -840,24 +854,9 @@ def _mapStateFromReactorToOther(
aDest,
self.blockParamNames,
mapNumberDensities,
calcReactionRates=self.calcReactionRates,
)

# If requested, the reaction rates will be calculated based on the
# mapped neutron flux and the XS library.
if self.calcReactionRates:
for b in aDest:
# Checks if the block has a multi-group flux defined and if it
# does not then this will skip the reaction rate calculation. This
# is captured by the TypeError, due to a `NoneType` divide by float
# error.
try:
b.getMgFlux()
except TypeError:
continue
globalFluxInterface.calcReactionRates(
b, destReactor.core.p.keff, destReactor.core.lib
)

# Clear the cached data after it has been mapped to prevent issues with
# holding on to block data long-term.
self._cachedReactorCoreParamData = {}
Expand Down

0 comments on commit c090fb6

Please sign in to comment.