Skip to content

Commit

Permalink
Merge pull request #9 from paulromano/generalize-extra-inputs
Browse files Browse the repository at this point in the history
Generalize ability to specify extra inputs
  • Loading branch information
nstauff authored Feb 18, 2022
2 parents 41724db + 7b13c27 commit 7ad7ff2
Show file tree
Hide file tree
Showing 12 changed files with 114 additions and 59 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# WATTS

[![License](https://img.shields.io/badge/license-MIT-green)](https://opensource.org/licenses/MIT)
[![GitHub Actions build status (Linux)](https://github.com/watts-dev/watts/workflows/CI/badge.svg?branch=development)](https://github.com/watts-dev/watts/actions?query=workflow%3ACI)


WATTS (Workflow and Template Toolkit for Simulation) provides a set of Python
classes that can manage simulation workflows for multiple codes where
Expand Down
32 changes: 17 additions & 15 deletions doc/source/user/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,16 @@ added to WATTS and are available for your use.
MOOSE Plugin
~~~~~~~~~~

The :class:`~watts.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 <https://jinja.palletsprojects.com>`_ 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:
The :class:`~watts.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
<https://jinja.palletsprojects.com>`_ 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:

.. code-block:: jinja
Expand All @@ -81,8 +82,9 @@ If the templated input file is ``sam_template.inp``, the SAM code will rely the

moose_plugin = watts.PluginMOOSE('sam_template.inp')

The MOOSE plugin provides the option to specify supplementary input files (in `supp_inputs` option) that
will be copied together with the templated input file (mesh or cross-section files).
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
:envvar:`PATH`) but can also be specified explicitly with the
Expand Down Expand Up @@ -163,7 +165,7 @@ PyARC Plugin
~~~~~~~~~~~~~

The :class:`~watts.PluginPyARC` class handles PyARC execution in a similar
manner to the :class:`~watts.PluginSAM` class for SAM. PyARC use text-based
manner to the :class:`~watts.PluginSAM` class for SAM. PyARC use text-based
input files which can be templated as follows:

.. code-block:: jinja
Expand All @@ -176,15 +178,15 @@ input files which can be templated as follows:
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, supp_inputs=['lumped_test5.son'])
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
:attr:`~watts.PluginPyARC.pyarc_exec` attribute::

pyarc_plugin.pyarc_exec = "/path/to/PyARC"

To execute PyARC, the :meth:`~watts.PluginPyARC.workflow` method is called
the same way as other Plugins.
To execute PyARC, the :meth:`~watts.PluginPyARC.workflow` method is called
the same way as other Plugins.

Results
+++++++
Expand Down
2 changes: 1 addition & 1 deletion examples/example1c_PyARC/example1c.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

# PyARC Workflow

pyarc_plugin = watts.PluginPyARC('pyarc_template', show_stdout=True, supp_inputs=['lumped_test5.son']) # show all the output
pyarc_plugin = watts.PluginPyARC('pyarc_template', show_stdout=True, extra_inputs=['lumped_test5.son']) # show all the output
pyarc_result = pyarc_plugin.workflow(params)
for key in pyarc_result.results_data:
print(key, pyarc_result.results_data[key])
Expand Down
2 changes: 1 addition & 1 deletion examples/example5_multiapps/example5a.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

moose_app_type = "bison"
app_dir = os.environ[moose_app_type.upper() + "_DIR"]
moose_plugin = watts.PluginMOOSE('main.tmpl', supp_inputs=['main_in.e', 'sub.i'])
moose_plugin = watts.PluginMOOSE('main.tmpl', extra_inputs=['main_in.e', 'sub.i'])
moose_plugin.moose_exec = app_dir + "/" + moose_app_type.lower() + "-opt"
moose_result = moose_plugin.workflow(params)
for key in moose_result.csv_data:
Expand Down
6 changes: 3 additions & 3 deletions examples/example6_adv_multiapps/example6.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

# MOOSE Workflow for steady state
print("Steady-state calculation")
moose_plugin_ss = watts.PluginMOOSE('MP_ss_griffin.tmpl', n_cpu=40, supp_inputs=['MP_ss_moose.i', 'MP_ss_sockeye.i', '3D_unit_cell_FY21_level-1_bison.e', '3D_unit_cell_FY21_supersimple.e', 'unitcell_nogap_hom_xml_G11_df_MP.xml'])
moose_plugin_ss = watts.PluginMOOSE('MP_ss_griffin.tmpl', n_cpu=40, extra_inputs=['MP_ss_moose.i', 'MP_ss_sockeye.i', '3D_unit_cell_FY21_level-1_bison.e', '3D_unit_cell_FY21_supersimple.e', 'unitcell_nogap_hom_xml_G11_df_MP.xml'])
moose_plugin_ss.moose_exec = app_dir + "/" + moose_app_type.lower() + "-opt"
moose_result_ss = moose_plugin_ss.workflow(params_ss)
for key in moose_result_ss.csv_data:
Expand All @@ -55,7 +55,7 @@

# MOOSE Workflow for Null transient
print("Null transient calculation")
moose_plugin_trN = watts.PluginMOOSE('MP_trN_griffin.tmpl', n_cpu=40, show_stdout=False, supp_inputs=['MP_trN_moose.i', 'MP_trN_sockeye.i', 'unitcell_nogap_hom_xml_G11_df_MP.xml'])
moose_plugin_trN = watts.PluginMOOSE('MP_trN_griffin.tmpl', n_cpu=40, show_stdout=False, extra_inputs=['MP_trN_moose.i', 'MP_trN_sockeye.i', 'unitcell_nogap_hom_xml_G11_df_MP.xml'])
moose_plugin_trN.moose_exec = app_dir + "/" + moose_app_type.lower() + "-opt"
moose_result_trN = moose_plugin_trN.workflow(params_trN)
for key in moose_result_trN.csv_data:
Expand All @@ -75,7 +75,7 @@

# MOOSE Workflow for transient
print("Transient calculation")
moose_plugin_tr = watts.PluginMOOSE('MP_tr_griffin.tmpl', n_cpu=40, show_stdout=True, supp_inputs=['MP_tr_moose.i', 'MP_tr_sockeye.i', 'unitcell_nogap_hom_xml_G11_df_MP.xml'])
moose_plugin_tr = watts.PluginMOOSE('MP_tr_griffin.tmpl', n_cpu=40, show_stdout=True, extra_inputs=['MP_tr_moose.i', 'MP_tr_sockeye.i', 'unitcell_nogap_hom_xml_G11_df_MP.xml'])
moose_plugin_tr.moose_exec = app_dir + "/" + moose_app_type.lower() + "-opt"
moose_result_tr = moose_plugin_tr.workflow(params_tr)
for key in moose_result_tr.csv_data:
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = watts
version = 0.0.1
author = UChicago Argonne, LLC
author_email = nstauff@anl.gov
author_email = watts@anl.gov
description = Workflow and Template Toolkit for Simulation
long_description = file: README.md
long_description_content_type = text/markdown
Expand Down
35 changes: 25 additions & 10 deletions src/watts/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,32 @@
# SPDX-License-Identifier: MIT

from abc import ABC, abstractmethod
import os
from pathlib import Path
import shutil
from typing import Optional
from typing import Optional, List
from astropy import units as u
import copy

from .database import Database
from .fileutils import cd_tmpdir
from .fileutils import cd_tmpdir, PathLike
from .parameters import Parameters
from .results import Results
from .template import TemplateModelBuilder


class Plugin(ABC):
"""Class defining the Plugin interface"""
"""Class defining the Plugin interface
Parameters
----------
extra_inputs
Extra (non-templated) input files
"""

def __init__(self, extra_inputs: Optional[List[PathLike]] = None):
self.extra_inputs = []
if extra_inputs is not None:
self.extra_inputs = [Path(f).resolve() for f in extra_inputs]

@abstractmethod
def prerun(self, params):
Expand Down Expand Up @@ -61,11 +71,12 @@ def workflow(self, params: Parameters, name='Workflow') -> Results:
db = Database()

with cd_tmpdir():
# Copy extra inputs to temporary directory
cwd = Path.cwd()
for path in self.extra_inputs:
shutil.copy(str(path), str(cwd)) # Remove str() for Python 3.8+

# Run workflow in temporary directory
if hasattr(self, 'supp_inputs'):
des = os.getcwd()
for sifp in self.supp_inputs:
shutil.copy(str(sifp), des)
self.prerun(params)
self.run()
result = self.postrun(params)
Expand Down Expand Up @@ -112,7 +123,7 @@ def convert_unit(self, params: Parameters, unit_system: str, unit_temperature: s

if isinstance(params_copy[key], u.quantity.Quantity):

# Unit conversion for temperature needs to be done separately because
# Unit conversion for temperature needs to be done separately because
# astropy uses a different method to convert temperature.
# Variables are converted to SI by default.

Expand All @@ -133,8 +144,12 @@ class TemplatePlugin(Plugin):
----------
template_file
Path to template file
extra_inputs
Extra (non-templated) input files
"""
def __init__(self, template_file: str):
def __init__(self, template_file: str, extra_inputs: Optional[List[PathLike]] = None):
super().__init__(extra_inputs)
self.model_builder = TemplateModelBuilder(template_file)

def prerun(self, params: Parameters, filename: Optional[str] = None):
Expand Down
20 changes: 10 additions & 10 deletions src/watts/plugin_moose.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@

from contextlib import redirect_stdout, redirect_stderr
from datetime import datetime
import os
from pathlib import Path
import shutil
import subprocess
import time
from typing import List
from typing import List, Optional

import h5py
import numpy as np
Expand Down Expand Up @@ -119,8 +117,8 @@ class PluginMOOSE(TemplatePlugin):
Whether to display output from stderr when MOOSE is run
n_cpu
Number of processors to be used to run MOOSE application
supp_inputs
List of supplementary input files that are needed for running the MOOSE application
extra_inputs
List of extra (non-templated) input files that are needed
Attributes
----------
Expand All @@ -130,16 +128,16 @@ class PluginMOOSE(TemplatePlugin):
"""

def __init__(self, template_file: str, show_stdout: bool = False,
show_stderr: bool = False, n_cpu: int = 1, supp_inputs: List[str] = []):
super().__init__(template_file)
show_stderr: bool = False, n_cpu: int = 1,
extra_inputs: Optional[List[str]] = None):
super().__init__(template_file, extra_inputs)
self._moose_exec = Path('moose-opt')
self.moose_inp_name = "MOOSE.i"
self.show_stdout = show_stdout
self.show_stderr = show_stderr
if n_cpu < 1:
raise RuntimeError("The CPU number used to run MOOSE app must be a natural number.")
self.n_cpu = n_cpu
self.supp_inputs = [Path(f).resolve() for f in supp_inputs]

@property
def moose_exec(self) -> Path:
Expand Down Expand Up @@ -206,6 +204,8 @@ def postrun(self, params: Parameters) -> ResultsMOOSE:
print("Post-run for MOOSE Plugin")

time = datetime.fromtimestamp(self._run_time * 1e-9)
inputs = ['MOOSE.i']
# Start with non-templated input files
inputs = [p.name for p in self.extra_inputs]
inputs.append('MOOSE.i')
outputs = [p for p in Path.cwd().iterdir() if p.name not in inputs]
return ResultsMOOSE(params, time, inputs, outputs)
return ResultsMOOSE(params, time, inputs, outputs)
24 changes: 17 additions & 7 deletions src/watts/plugin_openmc.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from functools import lru_cache
from pathlib import Path
import time
from typing import Callable, Mapping, List
from typing import Callable, Mapping, List, Optional

import h5py

Expand Down Expand Up @@ -103,15 +103,19 @@ class PluginOpenMC(Plugin):
----------
model_builder
Function that generates an OpenMC model
extra_inputs
Extra (non-templated) input files
show_stdout
Whether to display output from stdout when SAM is run
Whether to display output from stdout when OpenMC is run
show_stderr
Whether to display output from stderr when SAM is run
Whether to display output from stderr when OpenMC is run
"""

def __init__(self, model_builder: Callable[[Parameters], None],
def __init__(self, model_builder: Optional[Callable[[Parameters], None]] = None,
extra_inputs: Optional[List[PathLike]] = None,
show_stdout: bool = False, show_stderr: bool = False):
super().__init__(extra_inputs)
self.model_builder = model_builder
self.show_stdout = show_stdout
self.show_stderr = show_stderr
Expand All @@ -131,7 +135,8 @@ def prerun(self, params: Parameters) -> None:

print("Pre-run for OpenMC Plugin")
self._run_time = time.time_ns()
self.model_builder(params_copy)
if self.model_builder is not None:
self.model_builder(params_copy)

def run(self, **kwargs: Mapping):
"""Run OpenMC
Expand Down Expand Up @@ -174,8 +179,13 @@ def files_since(pattern, time):
matches.sort(key=lambda x: x.stat().st_mtime_ns)
return matches

# Start with non-templated input files
inputs = [Path.cwd() / p.name for p in self.extra_inputs]

# Get generated input files
inputs = files_since('*.xml', self._run_time)
for path in files_since('*.xml', self._run_time):
if path not in inputs:
inputs.append(path)

# Get list of all output files
outputs = ['OpenMC_log.txt']
Expand All @@ -185,4 +195,4 @@ def files_since(pattern, time):
outputs.extend(files_since('statepoint.*.h5', self._run_time))

time = datetime.fromtimestamp(self._run_time * 1e-9)
return ResultsOpenMC(params, time, inputs, outputs)
return ResultsOpenMC(params, time, inputs, outputs)
22 changes: 11 additions & 11 deletions src/watts/plugin_pyarc.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import os
import tempfile
import time
from typing import Mapping, List
from typing import Mapping, List, Optional

import h5py
import sys
Expand Down Expand Up @@ -62,7 +62,7 @@ def _save_PyARC(self, user_object) -> dict:
results_data["persent_sens"] = user_object.results_kuq_persent
results_data["gamsor"] = user_object.results_power_gamsor
results_data["dassh"] = user_object.results_dassh
# TODO - this is minimum information that can be brought back easily.
# TODO - this is minimum information that can be brought back easily.
# More should be returned but will require work on PyARC.
return results_data

Expand Down Expand Up @@ -98,12 +98,12 @@ class PluginPyARC(TemplatePlugin):
template_file
Templated PyARC input
show_stdout
Whether to display output from stdout when SAM is run
Whether to display output from stdout when PyARC is run
show_stderr
Whether to display output from stderr when SAM is run
supp_inputs
List of supplementary input files that are needed for running PyARC
Whether to display output from stderr when PyARC is run
extra_inputs
List of extra (non-templated) input files that are needed
Attributes
----------
pyarc_exec
Expand All @@ -112,13 +112,13 @@ class PluginPyARC(TemplatePlugin):
"""

def __init__(self, template_file: str, show_stdout: bool = False,
show_stderr: bool = False, supp_inputs: List[str] = []):
super().__init__(template_file)
show_stderr: bool = False,
extra_inputs: Optional[List[str]] = None):
super().__init__(template_file, extra_inputs)
self._pyarc_exec = Path(os.environ.get('PyARC_DIR', 'PyARC.py'))
self.pyarc_inp_name = "pyarc_input.son"
self.show_stdout = show_stdout
self.show_stderr = show_stderr
self.supp_inputs = [Path(f).resolve() for f in supp_inputs]

@property
def pyarc_exec(self) -> Path:
Expand Down Expand Up @@ -178,7 +178,7 @@ def postrun(self, params: Parameters) -> ResultsPyARC:
print("Post-run for PyARC Plugin")

time = datetime.fromtimestamp(self._run_time * 1e-9)
inputs = [p.name for p in self.supp_inputs]
inputs = [p.name for p in self.extra_inputs]
inputs.append(self.pyarc_inp_name)
outputs = [p for p in Path.cwd().iterdir() if p.name not in inputs]
return ResultsPyARC(params, time, inputs, outputs, self.pyarc.user_object)
Expand Down
3 changes: 3 additions & 0 deletions src/watts/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,6 @@ def from_hdf5(cls, filename: PathLike):
def open_folder(self):
"""Open folder containing results"""
open_file(self.base_path)

def __repr__(self):
return f"<Results{self.plugin}: {self.time}>"
Loading

0 comments on commit 7ad7ff2

Please sign in to comment.