2. Making ARMI-based Apps

Loading a reactor into the ARMI Framework is just the first step in pushing the envelope of reactor design and analysis. Activating a powerful collection of plugins and interfaces to automate your work is the next step to unlocking ARMI’s potential.

To really make ARMI your own, you will need to understand a couple of concepts that enable developers to adapt and extend ARMI to their liking:

  • Plugins: An ARMI plugin is a collection of code that registers new functionality with the ARMI Framework. This can include new Interfaces, Settings, Parameter definitions, custom Components, Materials, Operators, and others. For a more complete reference, see the Plugin API documentation. It is typical for a plugin to provide related components to some specific type of physics or a specific external physics code or the like. Keeping the scope of a plugin limited helps users to understand where all of their settings and interfaces and parameters are coming from.

  • ARMI-Based Applications: A collection of plugins, along with application-specific customizations, working together with the ARMI Framework constitutes an “ARMI-Based Application”. As an example, the TerraPower proprietary tool for modeling and analyzing sodium-cooled fast reactors is just such an application. It is from an Application that ARMI gets its collection of active plugins, which in turn dictate much of the ARMI Framework’s behavior.

Both of these concepts are discussed in depth below.

2.1. ARMI Plugins

An ARMI Plugin is the primary means by which a developer or qualified analyst can go about building specific capability on top of the ARMI Framework. Even some of the functionalities that ship with the Framework are implemented internally using the Plugin system! The armi.plugins module contains all of the plugin “hook” definitions and their associated documentation. It is recommended to peruse those docs before getting started to get an idea of what is available.

2.1.1. Some implementation details

One can just monkey-see-monkey-do their own plugins without fully understanding the following. However, having a deeper understanding of what is going on may be useful. Feel free to skip this section.

The plugin system is built on top of a Python library called pluggy. Unless you plan on doing development within the ARMI Framework itself, it is unlikely that you will need to be overly familiar with it, but understanding how it works may be beneficial.

Looking at the code in armi.plugins.ArmiPlugin, you might notice that all of the methods are decorated with @HOOKSPEC (short for “hook specification”); this is how the Framework itself defines the interfaces that a plugin implementation can provide. This is a feature of pluggy. You might also notice that all of the methods are static methods. This is because we do not actually expect an instance of an ArmiPlugin; rather, we currently only use the class as a namespace to collect whatever hook implementations a Plugin provides. While pluggy is happy with any Python namespace containing hook implementations (e.g. module, class, object, function, etc.), we chose to make a base ArmiPlugin class for a couple of reasons:

  • Wrapping the specifications in a class allows you to implement them in a subclass, which enables tools like ruff to check your work and complain early if you do certain things wrong.

  • While we assume all plugins are stateless (hence all @staticmethods), we may introduce stateful/configurable plugins later on. Starting out with a base class will make this transition easier.

2.1.2. Making your own Plugin

To get started on your own plugin you will want to subclass the armi.plugins.ArmiPlugin class, and implement whichever Plugin APIs that you want your Plugin to provide. Mark each of your implementations with an @armi.plugins.HOOKIMPL decorator. Take a look at armi.physics.neutronics.NeutronicsPlugin for an example. Make sure that in your implementation, you follow any rules or guidelines that are provided in the docstring for that Plugin API method. Failure to do so will lead to bugs and crashes in any ARMI-based Application that might use your plugin.

Important

We do not actually instantiate Plugin classes. Plugins are currently assumed to be stateless (notice that all of the @staticmethods on all of the hook specifications). See the above section for why.

It is likely that your Plugin class itself is only the tip of the iceberg that is the functionality provided by it. All of the various Interfaces, Settings, Parameters, etc. that your Plugin exposes to the Framework will likely live in other modules, which are imported and returned through your hook implementations. Again, see the Neutronics Plugin as an example. All of the other code will need to accompany your Plugin class somehow in a cohesive package. Packaging Python projects is beyond the scope of this document, but see this page for some guidance.

Once you have a plugin together, continue reading to see how to plug it into the ARMI Framework as part of an Application.

2.2. ARMI-Based Applications

On its own, ARMI doesn’t do much. Plugins provide more functionality, but even they aren’t particularly useful on their own either. The magic really happens when you collect a handful of Plugins and plug them into the ARMI Framework. Such a collection is called an ARMI-Based Application.

Once you have a collection of Plugins that you want to use, creating an ARMI-based Application is very easy. Start by creating a subclass of the armi.apps.App class, and write its __init__() function to register whichever plugins you need with the app’s _pm PluginManager object. Calling the base armi.apps.App will start you out with the default Framework Plugins, but you are free to discard any of these that you wish. Optionally, you can implement the armi.apps.App.splashText() property to render a custom header to be printed whenever your application is used.

Example:

>>> class MyApp(armi.apps.App):
...     def __init__(self):
...         # Adopt the base Framework Plugins. After calling __init__(), they are in
...         # self._pm.
...         armi.apps.App.__init__(self)
...
...         # Register our own plugins
...         from myapp.pluginA import PluginA
...         from myapp.pluginB import PluginB
...
...         self._pm.register(PluginA)
...         self._pm.register(PluginB)
...
...     @property
...     def splashText(self):
...         return """
...     ===============================
...     == My First ARMI Application ==
...     ===============================
... """

Once you have defined your App class, you need to configure the ARMI Framework to use it. To do this, call the armi.configure() function, passing an instance of your App class as the only argument. It is usually best to do this in your application’s __init__.py or __main__.py. Notice that in armi.__main__, ARMI configures itself with the base armi.apps.App class!

Example:

>>> import armi
>>> armi.configure(MyApp())