Skip to content

Commit beb341f

Browse files
Merge pull request #45 from EcoExtreML/add_octave
Add octave and matlab to run module
2 parents d34f8f9 + 6a0d024 commit beb341f

12 files changed

Lines changed: 649 additions & 738 deletions

CONTRIBUTING.md

Lines changed: 7 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ This repository includes the python package `PyStemmusScope` for running the STE
44

55
## Configure the python package for development and testing
66

7-
To contribute to development of the python package, we recommend installing the package in development mode.
7+
To contribute to the development of the python package, we recommend installing
8+
the package in development mode.
89

910

1011
### Installation
@@ -15,26 +16,20 @@ First, clone this repository:
1516
git clone https://github.com/EcoExtreML/STEMMUS_SCOPE_Processing.git
1617
```
1718

18-
Then install the package:
19+
Then install the package (On Windows, use `python` instead of `python3`):
1920

2021
```sh
2122
cd STEMMUS_SCOPE_Processing
22-
pip install -e .
23-
```
24-
25-
or
26-
27-
```sh
28-
python setup.py develop
23+
python3 -m install -e .[dev]
2924
```
3025

3126
### Run tests
3227

3328
The testing framework used here is [PyTest](https://pytest.org). You can run
34-
tests as:
29+
tests as (On Windows, use `python` instead of `python3`):
3530

3631
```sh
37-
pytest
32+
python3 -m pytest
3833
```
3934

4035
### Build documentation
@@ -65,80 +60,4 @@ isort
6560
## Development of STEMMUS_SCOPE model
6661

6762
<!-- markdown-link-check-disable-next-line -->
68-
To contribute to the STEMMUS_SCOPE model, you need access to the model source code that is stored in the repository [STEMMUS_SCOPE](https://github.com/EcoExtreML/STEMMUS_SCOPE). You also need a MATLAB license.
69-
70-
### Development on Snellius using MATLAB
71-
72-
[Snellius](https://servicedesk.surfsara.nl/wiki/display/WIKI/Snellius) is the
73-
Dutch National supercomputer hosted at SURF. MATLAB `2021a` is installed on
74-
Snellius, see the script
75-
[`run_jupyter_lab_snellius_dev.sh`](https://github.com/EcoExtreML/STEMMUS_SCOPE_Processing/blob/main/run_jupyter_lab_snellius_dev.sh)
76-
on how to load the module.
77-
78-
The script `run_jupyter_lab_snellius_dev.sh` activates the conda environment `pystemmusscope` and creates a jupyter lab server on Snellius for running the notebook
79-
interactively. Make sure that you create the `pystemmusscope` conda environment before submitting the the bash script. See **Create pystemmusscope environment** below.
80-
81-
<details>
82-
<summary>Create pystemmusscope environment</summary>
83-
84-
Run the commands below in a terminal:
85-
86-
```sh
87-
# Download and install Mamba on linux
88-
wget https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-pypy3-Linux-x86_64.sh
89-
bash Mambaforge-pypy3-Linux-x86_64.sh -b -p ~/mamba
90-
91-
# Update base environment
92-
. ~/mamba/bin/activate
93-
mamba update --name base mamba
94-
95-
# Download environment file
96-
wget https://raw.githubusercontent.com/EcoExtreML/STEMMUS_SCOPE_Processing/main/environment.yml
97-
98-
# Create a conda environment called 'pystemmusscope' with all required dependencies
99-
mamba env create -f environment.yml
100-
101-
# The environment can be activated with
102-
. ~/mamba/bin/activate pystemmusscope
103-
104-
```
105-
</details>
106-
107-
108-
### Development on CRIB using MATLAB
109-
110-
[CRIB](https://crib.utwente.nl/) is the ITC Geospatial Computing Platform.
111-
112-
MATLAB `2021a` is installed on CRIB, and supports Python `3.8`, see [Versions of Python Compatible with MATLAB Products](https://www.mathworks.com/content/dam/mathworks/mathworks-dot-com/support/sysreq/files/python-compatibility.pdf).
113-
114-
1. Log in CRIB with your username and password and select a proper compute unit.
115-
2. Install `PyStemmusScope` package. This step needs to be done once.
116-
<details>
117-
<summary>Install pystemmusscope with python 3.8</summary>
118-
119-
Run the commands below in a terminal:
120-
121-
```sh
122-
# Download and install Mamba on linux
123-
wget https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-pypy3-Linux-x86_64.sh
124-
bash Mambaforge-pypy3-Linux-x86_64.sh -b -p ~/mamba
125-
126-
# Update base environment
127-
. ~/mamba/bin/activate
128-
mamba update --name base mamba
129-
130-
# Download environment file
131-
wget https://raw.githubusercontent.com/EcoExtreML/STEMMUS_SCOPE_Processing/main/environment_3.8.yml
132-
133-
# Create a conda environment called 'pystemmusscope' with all required dependencies
134-
mamba env create -f environment_3.8.yml
135-
```
136-
</details>
137-
138-
3. click on the `Remote Desktop` in the
139-
Launcher. Click on the `Applications`. You will find the 'MATLAB' software under
140-
the `Research`.
141-
4. After clicking on 'MATLAB', it asks for your account information that is
142-
connected to a MATLAB license.
143-
5. Open the file `STEMMUS_SCOPE_run.m` and set the path of `config_file` to `../config_file_crib.txt` and change `WorkDir` and other configurations in `model.setup()`.
144-
6. Then, run the main script `STEMMUS_SCOPE_run.m`.
63+
To contribute to the STEMMUS_SCOPE model, you need access to the model source code that is stored in the repository [STEMMUS_SCOPE](https://github.com/EcoExtreML/STEMMUS_SCOPE).

PyStemmusScope/stemmus_scope.py

Lines changed: 113 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import logging
44
import os
5+
import shlex
56
import subprocess
7+
from pathlib import Path
68
from typing import Dict
79
from . import config_io
810
from . import forcing_io
@@ -12,32 +14,99 @@
1214

1315
logger = logging.getLogger(__name__)
1416

17+
def _is_model_src_exe(model_src_path: Path):
18+
"""Check if input exists. Returns True if input is a file and False if it is
19+
a directory.
20+
21+
Args:
22+
model_src_path(Path): path to Stemmus_Scope executable file or to a
23+
directory containing model source codes.
24+
"""
25+
if model_src_path.is_file():
26+
msg = ("The model executable file can be used on a Unix system "
27+
"where MCR is installed, see the "
28+
"`documentaion<https://pystemmusscope.readthedocs.io/>`_.")
29+
logger.info("%s", msg)
30+
return True
31+
if model_src_path.is_dir():
32+
return False
33+
msg = (
34+
"Provide a valid path to an executable file or "
35+
"to a directory containing model source codes, "
36+
"see the `documentaion<https://pystemmusscope.readthedocs.io/>`_.")
37+
raise ValueError(msg)
38+
39+
40+
def _check_interpreter(interpreter: str):
41+
if interpreter not in {"Octave" , "Matlab"}:
42+
msg = (
43+
"Set `interpreter` as Octave or Matlab to run the model using source codes."
44+
"Otherwise set `model_src_path` to the model executable file, "
45+
"see the `documentaion<https://pystemmusscope.readthedocs.io/>`_.")
46+
raise ValueError(msg)
47+
48+
49+
def _run_sub_process(args: list, cwd):
50+
# pylint: disable=consider-using-with
51+
result = subprocess.Popen(
52+
args, cwd=cwd,
53+
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
54+
shell=True,
55+
)
56+
exit_code = result.wait()
57+
stdout, stderr = result.communicate()
58+
#TODO handle stderr properly
59+
# when using octave, exit_code might be 139
60+
# see issue STEMMUS_SCOPE_Processing/issues/46
61+
if exit_code not in [0, 139]:
62+
raise subprocess.CalledProcessError(
63+
returncode=exit_code, cmd=args, stderr=stderr, output=stdout
64+
)
65+
if exit_code == 139:
66+
logger.warning(stderr)
67+
68+
# TODO return log info line by line!
69+
logger.info(stdout)
70+
return stdout.decode('utf-8')
71+
1572

1673
class StemmusScope():
1774
"""PyStemmusScope wrapper around Stemmus_Scope model.
1875
see https://gmd.copernicus.org/articles/14/1379/2021/
1976
20-
It sets the model with a configuration file and executable file.
21-
It also prepares forcing and soil data for model run.
77+
Configures the model and prepares forcing and soil data for the model run.
2278
2379
Args:
2480
config_file(str): path to Stemmus_Scope configuration file. An example
2581
config_file can be found in tests/test_data in `STEMMUS_SCOPE_Processing
2682
repository <https://github.com/EcoExtreML/STEMMUS_SCOPE_Processing>`_
27-
exe_file(str): path to Stemmus_Scope executable file.
83+
model_src_path(str): path to Stemmus_Scope executable file or to a
84+
directory containing model source codes.
85+
interpreter(str, optional): use `Matlab` or `Octave`. Only required if
86+
`model_src_path` is a path to model source codes.
2887
2988
Example:
3089
See notebooks/run_model_in_notebook.ipynb in `STEMMUS_SCOPE_Processing
3190
repository <https://github.com/EcoExtreML/STEMMUS_SCOPE_Processing>`_
3291
"""
3392

34-
def __init__(self, config_file: str, exe_file: str):
93+
def __init__(self, config_file: str, model_src_path: str, interpreter: str = None):
3594
# make sure paths are abolute and path objects
3695
config_file = utils.to_absolute_path(config_file)
37-
self.exe_file = utils.to_absolute_path(exe_file)
96+
model_src_path = utils.to_absolute_path(model_src_path)
97+
98+
# check the path to model source
99+
self.exe_file = None
100+
if _is_model_src_exe(model_src_path):
101+
self.exe_file = model_src_path
102+
else:
103+
_check_interpreter(interpreter)
104+
105+
self.model_src = model_src_path
106+
self.interpreter = interpreter
38107

39108
# read config template
40-
self._configs = config_io.read_config(config_file)
109+
self._config = config_io.read_config(config_file)
41110

42111
def setup(
43112
self,
@@ -61,30 +130,27 @@ def setup(
61130
"""
62131
# update config template if needed
63132
if WorkDir:
64-
self._configs["WorkDir"] = WorkDir
133+
self._config["WorkDir"] = WorkDir
65134

66135
if ForcingFileName:
67-
self._configs["ForcingFileName"] = ForcingFileName
136+
self._config["ForcingFileName"] = ForcingFileName
68137

69138
if NumberOfTimeSteps:
70-
self._configs["NumberOfTimeSteps"] = NumberOfTimeSteps
139+
self._config["NumberOfTimeSteps"] = NumberOfTimeSteps
71140

72141
# create customized config file and input/output directories for model run
73142
_, _, self.cfg_file = config_io.create_io_dir(
74-
self._configs["ForcingFileName"], self._configs
143+
self._config["ForcingFileName"], self._config
75144
)
76145

77146
# read the run config file
78-
self._configs = config_io.read_config(self.cfg_file)
147+
self._config = config_io.read_config(self.cfg_file)
79148

80149
# prepare forcing data
81-
forcing_io.prepare_forcing(self._configs)
150+
forcing_io.prepare_forcing(self._config)
82151

83152
# prepare soil data
84-
soil_io.prepare_soil_data(self._configs)
85-
86-
# set matlab log dir
87-
os.environ['MATLAB_LOG_DIR'] = str(self._configs["InputPath"])
153+
soil_io.prepare_soil_data(self._config)
88154

89155
return str(self.cfg_file)
90156

@@ -94,23 +160,40 @@ def run(self) -> str:
94160
Args:
95161
96162
Returns:
97-
Tuple with stdout and stderr
163+
string, the model log
98164
"""
99-
100-
# run the model
101-
args = [f"{self.exe_file} {self.cfg_file}"]
102-
result = subprocess.run(
103-
args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, check=True,
104-
)
105-
stdout = result.stdout
106-
107-
# TODO return log info line by line!
108-
logger.info("%s", stdout)
109-
110-
return stdout
165+
if self.exe_file:
166+
# run using MCR
167+
args = [f"{self.exe_file} {self.cfg_file}"]
168+
# set matlab log dir
169+
os.environ['MATLAB_LOG_DIR'] = str(self._config["InputPath"])
170+
result = _run_sub_process(args, None)
171+
if self.interpreter=="Matlab":
172+
# set Matlab arguments
173+
path_to_config = f"'{self.cfg_file}'"
174+
eval_code= f'STEMMUS_SCOPE_exe({path_to_config});exit;'
175+
args = ["matlab", "-r", eval_code, "-nodisplay", "-nosplash", "-nodesktop"]
176+
# seperate args dont work on linux!
177+
if utils.os_name() !="nt":
178+
args = shlex.join(args)
179+
result = _run_sub_process(args, self.model_src)
180+
if self.interpreter=="Octave":
181+
# set Octave arguments
182+
# use subprocess instead of oct2py,
183+
# see issue STEMMUS_SCOPE_Processing/issues/46
184+
path_to_config = f"'{self.cfg_file}'"
185+
# fix for windows
186+
path_to_config = path_to_config.replace("\\", "/")
187+
eval_code = f'STEMMUS_SCOPE_exe({path_to_config});exit;'
188+
args = ["octave", "--eval", eval_code, "--no-gui", "--silent"]
189+
# seperate args dont work on linux!
190+
if utils.os_name() !="nt":
191+
args = shlex.join(args)
192+
result = _run_sub_process(args, self.model_src)
193+
return result
111194

112195

113196
@property
114197
def config(self) -> Dict:
115198
"""Return the configurations for this model."""
116-
return self._configs
199+
return self._config

0 commit comments

Comments
 (0)