1. Framework Architecture
Here we will discuss some big-picture elements of the ARMI architecture. Throughout, links to the API docs will lead to additional details.
1.1. The Reactor Model
The reactor
package is the central representation of a nuclear reactor
in ARMI. All modules can be expected to want access to some element of the state data
in a run, and should be enabled to find the data present somewhere in the reactor
package’s code during runtime.
An approximation of Composite Design Pattern is used to represent the Reactor
in ARMI. In this hierarchy the Reactor object has a child Core object, and
potentially many generic Composite child objects representing ex-core structures.
The Core is made of Assembly objects, which are in turn made up as a collection
of Block objects. State variables may be stored at any level
of this hierarchy using the armi.reactor.parameters
system to contain results
(e.g., keff
, flow rates
, power
, flux
, etc.). Within each block are
Components that define the pin-level geometry. Associated with each Component are
Material objects that contain material properties (density
, conductivity
,
heat capacity
, etc.) and isotopic mass fractions.
Note
Non-core structures (spent fuel pools, core restraint, heat exchangers, etc.) may be represented analogously to the Core, but this feature is new and under development. Historically, the Core and Reactor were the same thing, and some information in the documentation still reflects this.
Each level of the composite pattern hierarchy contains most of its state data in a collection of parameters detailing considerations of how the reactor has progressed through time to any given point. This information also constitutes the majority of what gets written to the database for evaluation and/or follow-on analysis.
Review the data model Tutorials section for examples exploring a populated instance of the Reactor 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 bring in one or more 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.