Skip to content

Commit

Permalink
Update docs for Simulation and add inheritence
Browse files Browse the repository at this point in the history
  • Loading branch information
arbennett committed May 8, 2019
1 parent 2a99d31 commit acbae23
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 32 deletions.
18 changes: 10 additions & 8 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,46 +13,48 @@ Simulation
=======
.. automodule:: pysumma.simulation
:members:
:show-inheritance:

Ensemble
=======
.. automodule:: pysumma.ensemble
:members:
:show-inheritance:

File Manager
=========
.. automodule:: pysumma.file_manager
:members:
:show-inheritance:
:members:
:inherited-members:

Decisions
=========
.. automodule:: pysumma.decisions
:members:
:show-inheritance:
:members:
:inherited-members:

Output Control
=========
.. automodule:: pysumma.output_control
:members:
:show-inheritance:
:members:
:inherited-members:

Local Param Info
=========
.. automodule:: pysumma.local_param_info
:members:
:show-inheritance:
:members:
:inherited-members:

Basin Param Info
=========
.. automodule:: pysumma.basin_param_info
:members:
:show-inheritance:
:members:
:inherited-members:

Option
========
.. automodule:: pysumma.option
:members:
:show-inheritance:
157 changes: 133 additions & 24 deletions pysumma/simulation.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import os
import subprocess
import shlex
import xarray as xr
import glob

from .decisions import Decisions
from .file_manager import FileManager
Expand All @@ -11,8 +9,31 @@
from .force_file_list import ForceFileList


class Simulation(object):
"""The simulation object provides a wrapper for SUMMA simulations"""
class Simulation():
"""
The Simulation object provides a wrapper for SUMMA simulations.
It can be used to set up, modify, and run SUMMA. The Simulation
object consists of information about how to run SUMMA (the
location of the executable, various command line flags and options)
as well as configuration details which describe the domain to
be simulated.
A standard workflow for running a simulation locally is:
$ import pysumma as ps
$ s = ps.Simulation('summa.exe', 'file_manager.txt')
$ s.start('local')
$ s.monitor()
$ assert s._status == 'Success'
$ print(s.output)
A standard workflow for running a simulation through docker is:
$ import pysumma as ps
$ s = ps.Simulation('summa:develop', 'file_manager.txt')
$ s.start('docker')
$ s.monitor()
$ assert s._status == 'Success'
$ print(s.output)
"""
library_path = None
process = None

Expand All @@ -26,7 +47,19 @@ class Simulation(object):
parameter_trial: xr.Dataset = None

def __init__(self, executable, filemanager):
"""Initialize a new simulation object"""
"""
Initialize a new simulation object
Parameters
----------
executable:
Path to locally compiled SUMMA executable
or the name of the docker image to run
filemanager:
Path to the file manager for the desired
simulation setup. Can be specified as
relative path.
"""
self.executable = executable
self.manager_path = filemanager
self.manager = FileManager(filemanager)
Expand All @@ -40,9 +73,20 @@ def __init__(self, executable, filemanager):
self.local_attributes = self.manager.local_attributes
self._status = 'Initialized'

def gen_summa_cmd(self, run_suffix, processes=1, prerun_cmds=[],
startGRU=None, countGRU=None, iHRU=None, freq_restart=None,
progress='m'):
def _gen_summa_cmd(self, run_suffix, processes=1, prerun_cmds=None,
startGRU=None, countGRU=None, iHRU=None,
freq_restart=None, progress='m'):
"""
Generate the text of the SUMMA run commmand based on the desired
command line arguments.
Returns
-------
run_cmd (string):
A string representation of the SUMMA run command
"""
if prerun_cmds is None:
prerun_cmds = []
prerun_cmds.append('export OMP_NUM_THREADS={}'.format(processes))

summa_run_cmd = "{} -s {} -m {}".format(self.executable,
Expand All @@ -57,26 +101,28 @@ def gen_summa_cmd(self, run_suffix, processes=1, prerun_cmds=[],
summa_run_cmd += ' -r {}'.format(freq_restart)
if progress is not None:
summa_run_cmd += ' -p {}'.format(progress)
if len(prerun_cmds):
if prerun_cmds:
preprocess_cmd = " && ".join(prerun_cmds) + " && "
else:
preprocess_cmd = ""

return preprocess_cmd + summa_run_cmd

def run_local(self, run_suffix, processes=1, prerun_cmds=[],
startGRU=None, countGRU=None, iHRU=None, freq_restart=None,
progress=None):
def _run_local(self, run_suffix, processes=1, prerun_cmds=None,
startGRU=None, countGRU=None, iHRU=None, freq_restart=None,
progress=None):
"""Start a local simulation"""
run_cmd = self.gen_summa_cmd(run_suffix, processes, prerun_cmds,
startGRU, countGRU, iHRU, freq_restart,
progress)
self.process = subprocess.Popen(run_cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, shell=True)
self._status = 'Running'

def run_docker(self, run_suffix, processes=1,
prerun_cmds=[], startGRU=None, countGRU=None, iHRU=None,
freq_restart=None, progress=None):
def _run_docker(self, run_suffix, processes=1,
prerun_cmds=None, startGRU=None, countGRU=None, iHRU=None,
freq_restart=None, progress=None):
"""Start a docker simulation"""
run_cmd = self.gen_summa_cmd(run_suffix, processes, prerun_cmds,
startGRU, countGRU, iHRU,
freq_restart, progress)
Expand All @@ -95,10 +141,47 @@ def run_docker(self, run_suffix, processes=1,
stderr=subprocess.PIPE, shell=True)
self._status = 'Running'

def start(self, run_option, run_suffix='pysumma_run', processes=1,
prerun_cmds=[], startGRU=None, countGRU=None, iHRU=None,
def start(self, run_option, run_suffix='pysumma_run', processes=1,
prerun_cmds=None, startGRU=None, countGRU=None, iHRU=None,
freq_restart=None, progress=None):
"""Run a SUMMA simulation"""
"""
Start a SUMMA simulation. By default does not halt execution
of further python commands, and simply launches the SUMMA
process in the background. Progress can be halted by using
the `Simulation.monitor()` method.
Parameters
----------
run_suffix (string):
A unique identifier to include as part of the output
file names
processes (string):
The number of processors to use. Note: This only matters
if the SUMMA version being run is compiled with OpenMP
support. Defaults to 1.
prerun_cmds (List[string]):
A list of commands to be run before the SUMMA executable
is invoked. This can be used to set things like library
paths or creation of subdirectories.
startGRU (int):
The GRU index to start the simulation at. Used to run only
specified sections of the domain. Must be co-specified with
`countGRU` to do anything. Cannot be used with `iHRU`.
countGRU (int):
Number of GRU to run, starting at `startGRU`. Used to run
only the specified portion of the domain. Must be co-
specified with `startGRU` to do anything. Cannot be used
with `iHRU`.
iHRU (int):
Index of a single HRU to be run. Cannot be used with
`startGRU` and `countGRU`.
freq_restart (string):
How often to write restart files. Can be `d` for daily,
`m` for monthly, and `y` for yearly.
progress (string):
How often to record progress. Can be `d` for daily,
`m` for monthly, and `y` for yearly.
"""
#TODO: Implement running on hydroshare here
self.run_suffix=run_suffix
self._write_configuration()
Expand All @@ -113,6 +196,7 @@ def start(self, run_option, run_suffix='pysumma_run', processes=1,
'Valid options: local, docker')

def _write_configuration(self):
"""Write the configuration"""
#TODO: Still need to update for all netcdf writing
self.manager.write()
self.decisions.write()
Expand All @@ -122,6 +206,7 @@ def _write_configuration(self):
self.output_control.write()

def _get_output(self):
"""Find all output files and return a list of their paths"""
new_file_text = 'Created output file:'
assert self._status == 'Success'
out_files = []
Expand All @@ -141,6 +226,12 @@ def execute(self, run_option, run_suffix=None,
return result

def monitor(self):
"""
Watch a running simulation until it is done and
collect the run information in the simulation object.
This will halt execution of a started simulation
through the `Simulation.start()` method.
"""
if self.process is None:
raise RuntimeError('No simulation running! Use simulation.start '
'or simulation.execute to begin a simulation!')
Expand Down Expand Up @@ -171,6 +262,9 @@ def monitor(self):

@property
def result(self):
"""
Attribute describing whether a model run was a success or not
"""
if self.process is None:
raise RuntimeError('No simulation started! Use simulation.start '
'or simulation.execute to begin a simulation!')
Expand All @@ -181,6 +275,11 @@ def result(self):

@property
def stdout(self):
"""
The standard output. This contains a string representation of the
output that a SUMMA simulation would write to screen. Use `print`
to view this so that line breaks are rendered correctly.
"""
if self.process is None:
raise RuntimeError('No simulation started! Use simulation.start '
'or simulation.execute to begin a simulation!')
Expand All @@ -192,6 +291,12 @@ def stdout(self):

@property
def stderr(self):
"""
The standard error. This contains a string representation of the
output that a SUMMA simulation would write to screen if any
unforseen errors occurred. Use `print` to view this so that
line breaks are rendered correctly.
"""
if self.process is None:
raise RuntimeError('No simulation started! Use simulation.start '
'or simulation.execute to begin a simulation!')
Expand All @@ -203,6 +308,10 @@ def stderr(self):

@property
def output(self):
"""
An xarray dataset, or list of xarray datasets representing
the output that a simulation has written to disk.
"""
if self.process is None:
raise RuntimeError('No simulation started! Use simulation.start '
'or simulation.execute to begin a simulation!')
Expand All @@ -213,9 +322,9 @@ def output(self):
return self._output

def __repr__(self):
repr = []
repr.append("Executable path: {}".format(self.executable))
repr.append("Simulation status: {}".format(self._status))
repr.append("File manager configuration:")
repr.append(str(self.manager))
return '\n'.join(repr)
"""Show some information about the simulation setup"""
info = ["Executable path: {}".format(self.executable),
"Simulation status: {}".format(self._status),
"File manager configuration:",
str(self.manager)]
return '\n'.join(info)

0 comments on commit acbae23

Please sign in to comment.