# Copyright 2019 TerraPower, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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.
"""
Definitions of top-level reactor arrangements like the Core (default), SFP, etc.
See documentation of blueprints in :doc:`/user/inputs/blueprints` for more context. See example in
:py:mod:`armi.reactor.blueprints.tests.test_reactorBlueprints`.
This was built to replace the old system that loaded the core geometry from the ``cs['geometry']``
setting. Until the geom file-based input is completely removed, this system will attempt to migrate
the core layout from geom files. When geom files are used, explicitly specifying a ``core`` system
will result in an error.
System Blueprints are a big step in the right direction to generalize user input, but was still
mostly adapted from the old Core layout input. As such, they still only really support Core-like
systems. Future work should generalize the concept of "system" to more varied scenarios.
See Also
--------
armi.reactor.blueprints.gridBlueprints : Method for storing system assembly layouts.
armi.reactor.systemLayoutInput.SystemLayoutInput : Deprecated method for reading the individual
face-map xml files.
"""
import tabulate
import yamlize
from armi import context
from armi import getPluginManagerOrFail
from armi import runLog
from armi.reactor import geometry
from armi.reactor import grids
from armi.reactor.blueprints.gridBlueprint import Triplet
[docs]class SystemBlueprint(yamlize.Object):
"""
The reactor-level structure input blueprint.
.. impl:: Build core and spent fuel pool from blueprints
:id: I_ARMI_BP_SYSTEMS
:implements: R_ARMI_BP_SYSTEMS, R_ARMI_BP_CORE
This class creates a yaml interface for the user to define systems with
grids, such as cores or spent fuel pools, each having their own name,
type, grid, and position in space. It is incorporated into the "systems"
section of a blueprints file by being included as key-value pairs within
the :py:class:`~armi.reactor.blueprints.reactorBlueprint.Systems` class,
which is in turn included into the overall blueprints within
:py:class:`~armi.reactor.blueprints.Blueprints`.
This class includes a
:py:meth:`~armi.reactor.blueprints.reactorBlueprint.SystemBlueprint.construct` method, which
is typically called from within :py:func:`~armi.reactor.reactors.factory` during the
initialization of the reactor object to instantiate the core and/or spent fuel pool objects.
During that process, a spatial grid is constructed based on the grid blueprints specified in
the "grids" section of the blueprints (see :need:`I_ARMI_BP_GRID`) and the assemblies needed
to fill the lattice are built from blueprints using
:py:meth:`~armi.reactor.blueprints.Blueprints.constructAssem`.
Notes
-----
We use string keys to link grids to objects that use them. This differs from how blocks/
assembies are specified, which use YAML anchors. YAML anchors have proven to be problematic and
difficult to work with.
"""
name = yamlize.Attribute(key="name", type=str)
typ = yamlize.Attribute(key="type", type=str, default="core")
gridName = yamlize.Attribute(key="grid name", type=str)
origin = yamlize.Attribute(key="origin", type=Triplet, default=None)
def __init__(self, name=None, gridName=None, origin=None):
"""
A Reactor Level Structure like a core or SFP.
Notes
-----
yamlize does not call an __init__ method, instead it uses __new__ and setattr
this is only needed for when you want to make this object from a non-YAML source.
"""
self.name = name
self.gridName = gridName
self.origin = origin
@staticmethod
def _resolveSystemType(typ: str):
# Loop over all plugins that could be attached and determine if any
# tell us how to build a specific systems attribute. Sub-optimial
# as this check is called for each system (e.g., core, spent fuel pool).
# It is assumed that the number of systems is currently low enough to justify
# this structure.
manager = getPluginManagerOrFail()
# Only need this to handle the case we don't find the system we expect
seen = set()
for options in manager.hook.defineSystemBuilders():
for key, builder in options.items():
# Take the first match we find. This would allow other plugins to
# define a new core builder before finding those defined by the
# ReactorPlugin
if key == typ:
return builder
seen.add(key)
raise ValueError(
"Could not determine an appropriate class for handling a "
"system of type `{}`. Supported types are {}.".format(typ, sorted(seen))
)
[docs] def construct(self, cs, bp, reactor, geom=None, loadAssems=True):
"""Build a core/IVS/EVST/whatever and fill it with children.
Parameters
----------
cs : :py:class:`Settings <armi.settings.Settings>` object.
armi settings to apply
bp : :py:class:`Reactor <armi.reactor.blueprints.Blueprints>` object.
armi blueprints to apply
reactor : :py:class:`Reactor <armi.reactor.reactors.Reactor>` object.
reactor to fill
geom : optional
loadAssems : bool, optional
whether to fill reactor with assemblies, as defined in blueprints, or not. Is False in
:py:class:`UniformMeshGeometryConverter <armi.reactor.converters.uniformMesh.UniformMeshGeometryConverter>`
within the initNewReactor() method.
Raises
------
ValueError
input error, no grid design provided
ValueError
for 1/3 core maps, assemblies are defined outside the expected 1/3 core region
"""
from armi.reactor import reactors # avoid circular import
runLog.info("Constructing the `{}`".format(self.name))
if geom is not None and self.name == "core":
gridDesign = geom.toGridBlueprints("core")[0]
else:
if not bp.gridDesigns:
raise ValueError(
"The input must define grids to construct a reactor, but "
"does not. Update input."
)
gridDesign = bp.gridDesigns.get(self.gridName, None)
system = self._resolveSystemType(self.typ)(self.name)
# Some systems may not require a prescribed grid design. Only try to use one if
# it was provided
if gridDesign is not None:
spatialGrid = gridDesign.construct()
system.spatialGrid = spatialGrid
system.spatialGrid.armiObject = system
reactor.add(system) # need parent before loading assemblies
spatialLocator = grids.CoordinateLocation(
self.origin.x, self.origin.y, self.origin.z, None
)
system.spatialLocator = spatialLocator
if context.MPI_RANK != 0:
# on non-primary nodes we don't bother building up the assemblies
# because they will be populated with DistributeState.
return None
# TODO: This is also pretty specific to Core-like things. We envision systems
# with non-Core-like structure. Again, probably only doable with subclassing of
# Blueprints
if loadAssems and gridDesign is not None:
self._loadAssemblies(cs, system, gridDesign.gridContents, bp)
# TODO: This post-construction work is specific to Cores for now. We need to
# generalize this. Things to consider:
# - Should the Core be able to do geom modifications itself, since it already
# has the grid constructed from the grid design?
# - Should the summary be so specifically Material data? Should this be done for
# non-Cores? Like geom modifications, could this just be done in processLoading?
# Should it be invoked higher up, by whatever code is requesting the Reactor be
# built from Blueprints?
if isinstance(system, reactors.Core):
summarizeMaterialData(system)
self._modifyGeometry(system, gridDesign)
system.processLoading(cs)
return system
def _loadAssemblies(self, cs, container, gridContents, bp):
runLog.header(
"=========== Adding Assemblies to {} ===========".format(container)
)
badLocations = set()
for locationInfo, aTypeID in gridContents.items():
newAssembly = bp.constructAssem(cs, specifier=aTypeID)
i, j = locationInfo
loc = container.spatialGrid[i, j, 0]
try:
container.add(newAssembly, loc)
except LookupError:
badLocations.add(loc)
if badLocations:
raise ValueError(
"Geometry core map xml had assemblies outside the "
"first third core, but had third core symmetry. \n"
"Please update symmetry to be `full core` or "
"remove assemblies outside the first third. \n"
"The locations outside the first third are {}".format(badLocations)
)
def _modifyGeometry(self, container, gridDesign):
"""Perform post-load geometry conversions like full core, edge assems."""
# all cases should have no edge assemblies. They are added ephemerally when needed
from armi.reactor.converters import geometryConverters # circular imports
runLog.header("=========== Applying Geometry Modifications ===========")
converter = geometryConverters.EdgeAssemblyChanger()
converter.removeEdgeAssemblies(container)
# now update the spatial grid dimensions based on the populated children
# (unless specified on input)
if not gridDesign.latticeDimensions:
runLog.info(
"Updating spatial grid pitch data for {} geometry".format(
container.geomType
)
)
if container.geomType == geometry.GeomType.HEX:
container.spatialGrid.changePitch(container[0][0].getPitch())
elif container.geomType == geometry.GeomType.CARTESIAN:
xw, yw = container[0][0].getPitch()
container.spatialGrid.changePitch(xw, yw)
[docs]class Systems(yamlize.KeyedList):
item_type = SystemBlueprint
key_attr = SystemBlueprint.name
[docs]def summarizeMaterialData(container):
"""
Create a summary of the material objects and source data for a reactor container.
Parameters
----------
container : Core object
Any Core object with Blocks and Components defined.
"""
runLog.header(
"=========== Summarizing Source of Material Data for {} ===========".format(
container
)
)
materialNames = set()
materialData = []
for c in container.iterComponents():
if c.material.name in materialNames:
continue
materialData.append((c.material.name, c.material.DATA_SOURCE, False))
materialNames.add(c.material.name)
materialData = sorted(materialData)
runLog.info(
tabulate.tabulate(
tabular_data=materialData,
headers=[
"Material Name",
"Source Location",
"Property Data was Modified\nfrom the Source?",
],
tablefmt="armi",
)
)
return materialData