Basic Usage#
WATTS consists of a set of Python classes that can manage simulation workflows for multiple codes where information is exchanged at a coarse level. For each code, input files rely on placeholder values that are filled in based on a set of user-defined parameters.
Parameters#
The parameters that are used to “fill in” input files with placeholders are
managed by the Parameters
class. This class mostly behaves like
a Python dictionary but has a few extra capabilities. Setting parameters can be
done as follows:
params = watts.Parameters()
params['temperature'] = 550.0
params['option'] = True
params['values'] = [10.0, 20.0, 0.05]
Like a Python dictionary, key/value pairs can also be set when instantiating the object:
params = watts.Parameters(
temperature=550.0,
option=True,
values=[10.0, 20.0, 0.05]
)
Most native Python datatypes (int
, float
, bool
,
str
, list
, set
, tuple
, dict
) are
supported along with NumPy arrays
as well. Parameters
can be saved to a pickle file:
params.save('parameters.pkl')
and later re-created using the from_pickle()
method:
loaded_params = watts.Parameters.from_pickle('parameters.pkl')
By themselves, Parameters
are not very useful, but when
combined with plugin classes, they become building blocks for sophisticated
workflows.
Units#
To handle codes that use different unit systems, WATTS relies on the
Quantity
class from astropy.units
to perform unit
conversion on parameters to ensure that the correct units are used for each
code. For instance, MOOSE-based codes use the SI units while OpenMC uses the CGS
units. With the built-in unit-conversion capability, a parameter needs only to
be set once in any unit system and WATTS can automatically convert it to the
correct unit for different codes. To use the unit-conversion capability,
parameters need to be set using the Quantity
class as
follows:
from astropy.units import Quantity
params['radius'] = Quantity(9.9, "mm")
params['inlet_temperature'] = Quantity(600, "Celsius")
params['c_p'] = Quantity(4.9184126, "BTU/(kg*K)")
with the format of Quantity(value, unit)
.
Plugins#
Using a particular code within WATTS requires a “plugin” that controls input
file generation, execution, and post-processing. Three plugin classes,
PluginMOOSE
, PluginOpenMC
, and
PluginPyARC
, have already been added to WATTS and are available
for your use.
MOOSE Plugin#
The PluginMOOSE
class enables MOOSE simulations using a
templated input file. This is demonstrated here for a SAM application, but other
examples based on BISON are also available. For MOOSE codes such as SAM or BISON
that use text-based input files, WATTS relies on the Jinja templating engine for handling templated
variables and expressions. The templated input file looks like a normal MOOSE
input file where some values have been replaced with variables, which are
denoted by {{
and }}
pairs and get replaced with actual values when the
template is rendered. For example, a templated input file might look as
follows:
[GlobalParams]
global_init_P = {{ He_Pressure }}
global_init_V = {{ He_velocity }}
global_init_T = {{ He_inlet_temp }}
gravity = '-9.8 0 0'
scaling_factor_var = '1 1e-3 1e-6'
Tsolid_sf = 1e-3
[]
If the templated input file is sam_template.inp
, the SAM code will rely on
the general MOOSE plugin that can be created as:
moose_plugin = watts.PluginMOOSE('sam_template.inp')
The MOOSE plugin provides the option to specify non-templated input files (in extra_inputs option) that will be copied together with the templated input file (mesh or cross-section files).
The SAM executable defaults to sam-opt
(assumed to be present on your
PATH
) but can also be specified explicitly with the
moose_exec
attribute:
moose_plugin.moose_exec = "/path/to/sam-opt"
To execute SAM, the PluginMOOSE
instance is called as a function
and expects to receive an instance of Parameters
. For the above
template, the Parameters
instance should have He_Pressure
,
He_velocity
, and He_inlet_temp
parameters present. Thus, executing SAM
with this templated input file along with corresponding parameters might look as
follows:
params = watts.Parameters()
params['He_Pressure'] = 2.0
params['He_velocity'] = 1.0
params['He_inlet_temp'] = 600.0
results = moose_plugin(params)
Calling the PluginMOOSE
instance will render the templated input
file (replace variables with values from the Parameters
instance), execute SAM, and collect the output files.
Beyond simple variable substitution, Jinja has sophisticated capabilities for using logical control structures, filters, calling Python methods, and extensible templates; for advanced usage, please read through the Jinja template designer documentation.
OpenMC Plugin#
The PluginOpenMC
class handles OpenMC execution in a similar
manner to the PluginMOOSE
class for MOOSE. However, for OpenMC,
inputs are generated programmatically through the OpenMC Python API. Instead of
writing a text template, for the OpenMC plugin you need to write a function that
accepts an instance of Parameters
and generates the necessary
XML files. For example:
def godiva_model(params):
model = openmc.Model()
pu_metal = openmc.Material()
pu_metal.set_density('sum')
pu_metal.add_nuclide('Pu239', 3.7047e-02)
pu_metal.add_nuclide('Pu240', 1.7512e-03)
pu_metal.add_nuclide('Pu241', 1.1674e-04)
pu_metal.add_element('Ga', 1.3752e-03)
model.materials.append(pu_metal)
sph = openmc.Sphere(r=params['radius'], boundary_type='vacuum')
cell = openmc.Cell(fill=pu_metal, region=-sph)
model.geometry = openmc.Geometry([cell])
model.settings.batches = 50
model.settings.inactive = 10
model.settings.particles = 1000
model.export_to_xml()
With this function, the PluginOpenMC
class can be
instantiated:
openmc_plugin = watts.PluginOpenMC(godiva_model)
Note how the function object itself is passed to the plugin. When the
PluginOpenMC()
instance is called, the “template” function is
called and passed the user-specified Parameters
:
params = watts.Parameters(radius=6.0)
results = openmc_plugin(params)
This will generate the OpenMC input files using the template parameters, run OpenMC, and collect the results.
PyARC Plugin#
The PluginPyARC
class handles PyARC execution in a similar
manner to the PluginMOOSE
class for MOOSE. PyARC use text-based
input files which can be templated as follows:
surfaces{
hexagon ( hex ){ orientation=y normal = z pitch = {{ assembly_pitch }} }
plane ( z0 ) { z = 0.0 }
plane ( z10 ) { z = {{ assembly_length }} }
}
If the templated input file is pyarc_template, then the PyARC plugin can be instantiated with following command line:
pyarc_plugin = watts.PluginPyARC('pyarc_template', show_stdout=True, extra_inputs=['lumped_test5.son'])
The path to PyARC directory must be specified explicitly with the
pyarc_exec
attribute:
pyarc_plugin.pyarc_exec = "/path/to/PyARC"
To execute PyARC, the PluginPyARC()
instance is called directly the
same way as other plugins.
SAS4A/SASSY-1 Plugin#
The PluginSAS
class handles SAS4A/SASSY-1 execution in a similar
manner to the PluginMOOSE
class for MOOSE. SAS4A/SASSY-1 use text-based
input files which can be templated as follows:
47 1 {{ flow_per_pin }}
3 1 {{ total_reactor_power }}
7 1 {{ tmax }}
If the templated input file is sas_template, then the SAS4A/SASSY-1 plugin can be instantiated with the following command line:
sas_plugin = watts.PluginSAS('sas_template', show_stdout=True)
The SAS executable is OS-dependent. It defaults to sas.x
(assumed to be
present on your PATH
) for Linux and macOS, and sas.exe
for
Windows. However, the executable can also be specified explicitly with the
sas_exec
attribute:
sas_plugin.sas_exec = "/path/to/sas-exec"
Furthermore, the paths to the SAS utilities that convert the “.dat” files to
“.csv” files must be specified with the conv_channel
and conv_primar4
attributes:
sas_plugin.conv_channel = "/path/to/CHANNELtoCSV.x"
sas_plugin.conv_primar4 = "/path/to/PRIMAR4toCSV.x"
Similar to the SAS executable, the utilities are also OS dependent. To execute
SAS, the PluginSAS()
instance is called directly in the same way as
other plugins.
Results#
When you call a Plugin()
instance, an instance of the
Results
class specific to the plugin will be returned that
contains information about the results. Every Results
object
contains a list of input and output files that were generated:
>>> results = plugin_openmc(params)
>>> results.inputs
[PosixPath('geometry.xml'),
PosixPath('settings.xml'),
PosicPath('materials.xmll')]
>>> results.outputs
[PosixPath('OpenMC_log.txt'),
PosixPath('statepoint.250.h5')]
Results
objects also contain a copy of the
Parameters
that were used at the time the plugin was called:
>>> results.parameters
<watts.parameters.Parameters at 0x0x15549e5b8d60>
>>> results.parameters['radius']
6.0
Each plugin actually returns a subclass of Results
that extends
the basic functionality by adding methods/attributes that incorporate
post-processing logic. For example, the ResultsOpenMC
class
provides a keff
attribute that provides the
k-effective value at the end of the simulation:
>>> results.keff
1.0026170700986219+/-0.003342785895893627
For MOOSE, the ResultsMOOSE
class provides a
csv_data
attribute that gathers the results from
every CSV files generated by MOOSE applications (such as SAM or BISON):
moose_result = moose_plugin(params)
for key in moose_result.csv_data:
print(key, moose_result.csv_data[key])
For PyARC, the ResultsPyARC
class
provides a results_data
attribute that gathers the
results stored in PyARC.user_object:
pyarc_result = pyarc_plugin(params)
for key in pyarc_result.results_data:
print(key, pyarc_result.results_data[key])
Database#
When you call a Plugin
instance, the Results
object and all accompanying files are automatically added to a database on disk
for later retrieval. Interacting with this database can be done via the
Database
class:
>>> db = watts.Database()
>>> db.results
[<ResultsOpenMC: 2022-01-01 12:05:02.130384>,
<ResultsOpenMC: 2022-01-01 12:11:38.037813>,
<ResultsMOOSE: 2022-01-02 08:45:12.846409>]
By default, the database will be created in a user-specific data directory (on
Linux machines, this is normally within ~/.local/share
). However, the
location of the database can be specified:
db = watts.Database('/opt/watts_db/')
Creating a database this way doesn’t change the default path used when running
plugins. If you want to change the default database path used in plugins, the
set_default_path()
classmethod should be used:
>>> watts.Database.set_default_path('/opt/watts_db')
>>> db = watts.Database()
>>> db.path
PosixPath('/opt/watts_db')
To clear results from the database, simply use the
clear()
method:
>>> db.clear()
>>> db.results
[]
Be aware that clearing the database will delete all the corresponding results on disk, including input and output files from the workflow.