1. Framework Architecture
What follows is a discussion of the high-level elements of the ARMI framework. Throughout, links to the API docs will be provided for additional details.
1.1. The Reactor Data Model
The ARMI framework represents a nuclear reactor via a reactor data model, which is defined in the
reactor
package. Each physical piece of the nuclear reactor is defined by a Python
object, called an ArmiObject
. Each ArmiObject
has associated data like: shape, material, or other physical values. The physical values can be
nearly anything, and are attached to the data model via ARMI’s
Parameter
system. Example parameters might be: keff
,
flow rates
, power
, flux
, etc.
The reactor data model is a hierarchical model, following the Composite Design Pattern. The top of the data model is the
Reactor
, which contains one
Core
object and a collection of zero or more
ExcoreStructures
. An example
ExcoreStructure
might be a SpentFuelPool
.
For now, the Core
object in ARMI assumes it contains Assembly objects, which are in turn
made up as a collection of Block objects. The leaves of the the Composite Model in the ARMI
framework are called Component
.
Time-evolving the parameters on the reactor composite hierarchy is what most modelers and analysts will want from the ARMI framework.
Review the data model Tutorials section for examples exploring a populated instance of
the Reactor
data model.
1.1.1. Finding objects in a model
Under most circumstances a armi.reactor.reactors.Reactor
instance will have a
.core
attribute, which is an instance of armi.reactor.reactors.Core
. While the
Composite pattern discussed above can be used very generally, the Core
class
enforces a couple of constraints that can be very useful:
A
Core
is a 2-D arrangement ofarmi.reactor.assemblies.Assembly
objects.Each
Assembly
is a 1-D arrangement ofarmi.reactor.blocks.Block
objects.Blocks are
armi.reactor.composites.Composite
objects with some extra parameter bindings, utility functions, and other implementation details that let them play nicely with their containingAssembly
.
In many scenarios, one wants to access specific assemblies or blocks from a core. There are a few ways to get the objects that you’re interested in:
The r.core.childrenByLocator dictionary maps
armi.reactor.grids.IndexLocation
objects to whichever assembly is at that location. For example>>> loc = r.core.spatialGrid[i, j, 0] >>> a = r.core.childrenByLocator[loc]
To access the
k
-th block in an assembly, try:>>> b = a[k]
r.core.getAssemblies() loops through all assemblies in the core for when you need to do something to all assemblies.
1.1.2. Parameters
One of the main benefits to ARMI is that it enables simple interfaces to extract data from the reactor, do something with it, and add new results to the reactor. This enables specialized developers to write code that uses ARMI as input and output.
Most data is stored in ARMI as parameters
. Most parameters will
become persistent, meaning they will be saved to the database during database
interactions, and therefore it will also be loaded when a database is loaded.
Details of the use and design can be found at parameters
.
1.1.3. Converters
The converters
subpackage contains a variety of utilities that
can convert a reactor model in various ways. Some converters change designs at the block
level, adjusting pin dimensions or fuel composition. Others adjust the reactor geometry
at large, changing a 1/3-symmetric model to a full core, or changing a hexagonal
geometry to a R-Z geometry. Converters are used for parameter sweeps as well as during
various physics operations.
For example, some lattice physics routines convert the full core to a 2D R-Z model and compute flux with thousands of energy groups to properly capture the spectral-spatial coupling in a core/reflector interface. The converters are used heavily in these operations.
1.1.4. Blueprints
As seen in the User Guide, blueprints
are how reactor models are
defined. During a run, they can be used to create new instances of reactor model pieces,
such as when a new assembly is fabricated during a fuel management operation in a later
cycle.
1.2. Operators
Operators conduct the execution sequence of an ARMI run. They basically contain the main loop. When any operator is instantiated, several actions occur:
Some environmental detail is printed out,
A Reactor object is instantiated
Loading and geometry input files are processed and the reactor object is populated with assemblies,
The interfaces are instantiated and placed in the Interface Stack during the
createInterfaces method
call,The
interactInit
method is called on all interfaces, andRestart information is processed (if this is a restart run).
After that, depending on the type of Operator at hand, one of several operational loops
will begin via operate()
. Operator types are chosen by the runType
setting,
which is featured on the first tab of the ARMI GUI.
1.2.1. The Standard Operator
The two primary types of operators are the Standard Operator (along with its parallel
version, the OperatorMPI
), and the
OperatorSnapshots
. The former runs a
typical operational loop, which calls all the interfaces through their interaction hooks
in a sequential manner, marching from beginning-of-life through the number of cycles
requested. This is how most quasistatic fuel cycle calculations are performed, which
inform much of the analysis done during reactor design. The main code for this loop is
found in the mainOperate method
. This
operator supports restart/continuation of past runs from an arbitrary time step.
1.2.2. The Snapshots Operator
Alternatively, OperatorSnapshots is designed to allow for additional analyses at
specific time steps. It simply loops through all snapshots that have been requested via
the Snapshot Request functionality (Lists -> Edit snapshot requests in the GUI). At each
snapshot request, the state is loaded from a previous case, as determined by the
reloadDBName
setting and then the BOC, EveryNode, and EOC interaction hooks are
executed from all the interfaces. Snapshots are intended to analyze an exact reactor
configuration. Therefore, interfaces which would significantly change the reactor
configuration (such as Fuel management, and depletion) are disabled.
1.2.3. The Interface Stack
Interfaces (armi.interfaces.Interface
) operate upon the Reactor Model to
do analysis. They’re designed to allow expansion of the code in a natural and
well-organized manner. Interfaces are useful to link external codes to ARMI as well for
adding new internal physics into the rest of the system. As a result, very many aspects
of ARMI are contained within interfaces.
The flow of any ARMI calculation depends on the order of the interfaces, which is set at
initialization according to the user settings and the corresponding ORDER
attributes
in interface modules. The collection of the interfaces is known as the Interface
Stack and is prominently featured at the beginning of the standard output of each run,
like this:
[R 0 ] -------------------------------------------------------------------------------
[R 0 ] *** Interface Stack Report ***
[R 0 ] NUM TYPE NAME ENABLED BOL FORCE EOL ORDER
[R 0 ] -------------------------------------------------------------------------------
[R 0 ] 00 Main "main" Yes No Reversed
[R 0 ] 01 Software Testing "softwareTests" Yes No Reversed
[R 0 ] 02 ReportInterface "report" Yes No Reversed
[R 0 ] 03 FuelHandler "fuelHandler" Yes No Normal
[R 0 ] 04 Depletion "depletion" Yes Yes Normal
[R 0 ] 05 MC2-2 "mc2" Yes No Normal
[R 0 ] 06 DIF3D "dif3d" Yes No Normal
[R 0 ] 07 Thermo "thermo" Yes No Normal
[R 0 ] 08 OrificedOptimized "orificer" Yes Yes Normal
[R 0 ] 09 AlchemyLite "alchemyLite" Yes No Normal
[R 0 ] 10 Alchemy "alchemy" Yes No Normal
[R 0 ] 11 Economics "economics" Yes No Normal
[R 0 ] 12 History "history" Yes No Normal
[R 0 ] 13 Database "database" Yes Yes Normal
[R 0 ] -------------------------------------------------------------------------------
Any interface that exists on the interface stack is accessible from the operator
or
from any other interface object through the getInterface method
.
1.2.4. Interface Interaction Hooks
Various interfaces need to interact with ARMI at various times. The point at which routines are called during a run set by developers in interface hooks, as seen below. At each point in the flow chart, interfaces are interacted with one-by-one as the interface stack is traversed in order.
For example, input checking routines would run at beginning-of-life (BOL), calculation modules might run at every time node, etc. To accommodate these various needs, interface hooks include:
interactInit
occurs right after all interfaces are initialized.interactBOL
– Beginning of life. Happens once as the run is starting up.interactBOC
– Beginning of cycle. Happens once per cycle.interactEveryNode
– Happens after every node step/flux calculation.interactEOC
– End of cycle.interactEOL
– End of life.interactError
– When an error occurs, this can run to clean up or print debugging info.interactCoupled
– Happens after every node step/flux calculation, if tight physics coupling is active.
These interaction points are optional in every interface, and you may override one or more of them to suit your needs. You should not change the arguments to the hooks, which are integers.
Each interface has a enabled
flag. If this is set to False
, then the interface’s
hook code will not be called even though the interface exists in the problem. This is
useful for interfaces that use code from other interfaces. For example, if subchan
is activated, it still uses some code in the thermo
module to compute the fuel
temperatures, so the thermo
interface must be available in a getInterface
call.
1.2.5. Adding a new interface
When using the Operators that come with ARMI, Interfaces are discovered using the
Plugin API
and inserted into the interface stack during the
createInterfaces
method.
1.2.6. How interfaces get called
The hooks of interfaces are called during the main loop in
armi.operators.Operator.mainOperate()
. There are a few special operator calls
in there to methods like armi.operators.Operator.interactAllBOL()
that loop
through the interface stack and call each enabled interface’s interactBOL()
method.
If you override mainOperate
in a custom operator, you will need to add these calls
as deemed necessary to have the interfaces work properly.
To use interfaces in parallel, please refer to armi.mpiActions
.
1.3. Plugins
Plugins are higher-level objects that can add things to the simulations like Interfaces, settings
definitions, parameters, validations, etc. They are documented in Making ARMI-based Apps and
armi.plugins
.
1.3.1. Entry Points
ARMI has a set of Entry Points
that can run
cases, launch the GUI, and perform various testing and utility operations. When you
invoke ARMI with python -m armi run
, the __main__.py
file is loaded and all
valid Entry Points are dynamically loaded. The proper entry point (in this case,
armi.cli.run.RunEntryPoint
) is invoked. As ARMI initializes itself, settings
are loaded into a Settings
object. From those settings, an Operator
subclass is built by a factory and its operate
method is called. This fires up the
main ARMI analysis loop and its interface stack is looped over as indicated by user input.