Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add octave and matlab to run module #45

Merged
merged 47 commits into from
Nov 14, 2022
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
a6ba455
add octave and matlab to run module
SarahAlidoost Oct 26, 2022
b31a910
refactor the code, remove env from subprocess because it overrides th…
SarahAlidoost Oct 27, 2022
c0b6878
add matlab and octave to the notebook
SarahAlidoost Oct 27, 2022
b57d77c
fix matlab command line
SarahAlidoost Oct 27, 2022
3f8a5d3
fix paths in the nb
SarahAlidoost Oct 27, 2022
604f493
fix octave command line
SarahAlidoost Oct 28, 2022
f3f6e3c
use oct2py instead of subprocess for octave
SarahAlidoost Oct 28, 2022
9cf8af8
refactor command lines in run function
SarahAlidoost Oct 28, 2022
554d262
replace _configs with _config
SarahAlidoost Oct 28, 2022
82a5665
change path to str for octave
SarahAlidoost Oct 28, 2022
22c816b
run notebook with octave on crib
SarahAlidoost Oct 28, 2022
c3c0ac1
remove run_model_in_notebook_dev
SarahAlidoost Oct 28, 2022
6b0fcd3
run isort
SarahAlidoost Oct 28, 2022
274af3e
fix pylint error
SarahAlidoost Oct 28, 2022
5a29ef3
capture log from octave, fix pylint error
SarahAlidoost Oct 28, 2022
e86d996
fix broken tests
SarahAlidoost Oct 28, 2022
54bda95
add some tests for init function
SarahAlidoost Oct 28, 2022
5cad9b0
fix tests of mocked_run_subprocess
SarahAlidoost Oct 28, 2022
ccefd49
add a test for matlab run
SarahAlidoost Oct 28, 2022
b996cb1
add draft test for octave
SarahAlidoost Oct 28, 2022
efc52b5
use subprocess instead of oct2py, use popen instead of run, add test …
SarahAlidoost Oct 31, 2022
c80e9cd
remove unused import
SarahAlidoost Oct 31, 2022
623b99e
disable pylint consider using with for popen
SarahAlidoost Oct 31, 2022
b7d089c
add python 3 m before pip install
SarahAlidoost Oct 31, 2022
8d90cfe
add markdown to doc config
SarahAlidoost Oct 31, 2022
7694025
move credits to fisrt page of doc
SarahAlidoost Oct 31, 2022
d4d1d34
update docs
SarahAlidoost Oct 31, 2022
2a42f4e
merge two snellius bash script
SarahAlidoost Oct 31, 2022
f783e75
fix check links
SarahAlidoost Oct 31, 2022
8d43fe5
don't split long lines in docs for link checker
SarahAlidoost Oct 31, 2022
96fef08
remove env file with python 3.8
SarahAlidoost Oct 31, 2022
f4946e9
replace double backslash for windows
SarahAlidoost Nov 3, 2022
6e32c89
replace eval_code with eval_command
SarahAlidoost Nov 3, 2022
784fdf9
refactor octave commands
SarahAlidoost Nov 3, 2022
cd25849
replace _octave with _exe in tests
SarahAlidoost Nov 3, 2022
b1b3532
add decode to stdout
SarahAlidoost Nov 3, 2022
6b6e2bd
add a comment: use python instead of python3 for windows
SarahAlidoost Nov 3, 2022
e26de91
fix octave commands
SarahAlidoost Nov 4, 2022
84b0cd3
refactor matlab command
SarahAlidoost Nov 4, 2022
72caa50
fix doc
SarahAlidoost Nov 4, 2022
4e5fa93
join args using shlex for linux
SarahAlidoost Nov 4, 2022
07ded4b
fix tests for new args
SarahAlidoost Nov 4, 2022
f349c80
replace sub_process with interpreter
SarahAlidoost Nov 4, 2022
4cd43f3
fix docstring
SarahAlidoost Nov 4, 2022
3a68ac0
remove some of todo, add docstring and links to doc
SarahAlidoost Nov 4, 2022
a363d22
Version pin netcdf4 to <=1.5.8
BSchilperoort Nov 7, 2022
6a0d024
Update tests to account for Win FileNotFoundError
BSchilperoort Nov 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 7 additions & 88 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ This repository includes the python package `PyStemmusScope` for running the STE

## Configure the python package for development and testing

To contribute to development of the python package, we recommend installing the package in development mode.
To contribute to the development of the python package, we recommend installing
the package in development mode.


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

Then install the package:
Then install the package (On Windows, use `python` instead of `python3`):

```sh
cd STEMMUS_SCOPE_Processing
pip install -e .
```

or

```sh
python setup.py develop
python3 -m install -e .[dev]
SarahAlidoost marked this conversation as resolved.
Show resolved Hide resolved
```

### Run tests

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

```sh
pytest
python3 -m pytest
```

### Build documentation
Expand Down Expand Up @@ -65,80 +60,4 @@ isort
## Development of STEMMUS_SCOPE model

<!-- markdown-link-check-disable-next-line -->
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.

### Development on Snellius using MATLAB

[Snellius](https://servicedesk.surfsara.nl/wiki/display/WIKI/Snellius) is the
Dutch National supercomputer hosted at SURF. MATLAB `2021a` is installed on
Snellius, see the script
[`run_jupyter_lab_snellius_dev.sh`](https://github.com/EcoExtreML/STEMMUS_SCOPE_Processing/blob/main/run_jupyter_lab_snellius_dev.sh)
on how to load the module.

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
interactively. Make sure that you create the `pystemmusscope` conda environment before submitting the the bash script. See **Create pystemmusscope environment** below.

<details>
<summary>Create pystemmusscope environment</summary>

Run the commands below in a terminal:

```sh
# Download and install Mamba on linux
wget https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-pypy3-Linux-x86_64.sh
bash Mambaforge-pypy3-Linux-x86_64.sh -b -p ~/mamba

# Update base environment
. ~/mamba/bin/activate
mamba update --name base mamba

# Download environment file
wget https://raw.githubusercontent.com/EcoExtreML/STEMMUS_SCOPE_Processing/main/environment.yml

# Create a conda environment called 'pystemmusscope' with all required dependencies
mamba env create -f environment.yml

# The environment can be activated with
. ~/mamba/bin/activate pystemmusscope

```
</details>


### Development on CRIB using MATLAB

[CRIB](https://crib.utwente.nl/) is the ITC Geospatial Computing Platform.

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).

1. Log in CRIB with your username and password and select a proper compute unit.
2. Install `PyStemmusScope` package. This step needs to be done once.
<details>
<summary>Install pystemmusscope with python 3.8</summary>

Run the commands below in a terminal:

```sh
# Download and install Mamba on linux
wget https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-pypy3-Linux-x86_64.sh
bash Mambaforge-pypy3-Linux-x86_64.sh -b -p ~/mamba

# Update base environment
. ~/mamba/bin/activate
mamba update --name base mamba

# Download environment file
wget https://raw.githubusercontent.com/EcoExtreML/STEMMUS_SCOPE_Processing/main/environment_3.8.yml

# Create a conda environment called 'pystemmusscope' with all required dependencies
mamba env create -f environment_3.8.yml
```
</details>

3. click on the `Remote Desktop` in the
Launcher. Click on the `Applications`. You will find the 'MATLAB' software under
the `Research`.
4. After clicking on 'MATLAB', it asks for your account information that is
connected to a MATLAB license.
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()`.
6. Then, run the main script `STEMMUS_SCOPE_run.m`.
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).
143 changes: 113 additions & 30 deletions PyStemmusScope/stemmus_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import logging
import os
import shlex
import subprocess
from pathlib import Path
from typing import Dict
from . import config_io
from . import forcing_io
Expand All @@ -12,32 +14,99 @@

logger = logging.getLogger(__name__)

def _is_model_src_exe(model_src_path: Path):
"""Check if input exists. Returns True if input is a file and False if it is
a directory.

Args:
model_src_path(Path): path to Stemmus_Scope executable file or to a
directory containing model source codes.
"""
if model_src_path.is_file():
msg = ("The model executable file can be used on a Unix system "
"where MCR is installed, see the "
"`documentaion<https://pystemmusscope.readthedocs.io/>`_.")
logger.info("%s", msg)
return True
if model_src_path.is_dir():
return False
msg = (
"Provide a valid path to an executable file or "
"to a directory containing model source codes, "
"see the `documentaion<https://pystemmusscope.readthedocs.io/>`_.")
raise ValueError(msg)


def _check_interpreter(interpreter: str):
if interpreter not in {"Octave" , "Matlab"}:
msg = (
"Set `interpreter` as Octave or Matlab to run the model using source codes."
"Otherwise set `model_src_path` to the model executable file, "
"see the `documentaion<https://pystemmusscope.readthedocs.io/>`_.")
raise ValueError(msg)


def _run_sub_process(args: list, cwd):
# pylint: disable=consider-using-with
result = subprocess.Popen(
args, cwd=cwd,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
shell=True,
)
exit_code = result.wait()
stdout, stderr = result.communicate()
#TODO handle stderr properly
# when using octave, exit_code might be 139
# see issue STEMMUS_SCOPE_Processing/issues/46
if exit_code not in [0, 139]:
raise subprocess.CalledProcessError(
returncode=exit_code, cmd=args, stderr=stderr, output=stdout
)
if exit_code == 139:
logger.warning(stderr)

# TODO return log info line by line!
logger.info(stdout)
return stdout.decode('utf-8')


class StemmusScope():
"""PyStemmusScope wrapper around Stemmus_Scope model.
see https://gmd.copernicus.org/articles/14/1379/2021/

It sets the model with a configuration file and executable file.
It also prepares forcing and soil data for model run.
Configures the model and prepares forcing and soil data for the model run.

Args:
config_file(str): path to Stemmus_Scope configuration file. An example
config_file can be found in tests/test_data in `STEMMUS_SCOPE_Processing
repository <https://github.com/EcoExtreML/STEMMUS_SCOPE_Processing>`_
exe_file(str): path to Stemmus_Scope executable file.
model_src_path(str): path to Stemmus_Scope executable file or to a
directory containing model source codes.
interpreter(str, optional): use `Matlab` or `Octave`. Only required if
`model_src_path` is a path to model source codes.

Example:
See notebooks/run_model_in_notebook.ipynb in `STEMMUS_SCOPE_Processing
repository <https://github.com/EcoExtreML/STEMMUS_SCOPE_Processing>`_
"""

def __init__(self, config_file: str, exe_file: str):
def __init__(self, config_file: str, model_src_path: str, interpreter: str = None):
# make sure paths are abolute and path objects
config_file = utils.to_absolute_path(config_file)
self.exe_file = utils.to_absolute_path(exe_file)
model_src_path = utils.to_absolute_path(model_src_path)

# check the path to model source
self.exe_file = None
if _is_model_src_exe(model_src_path):
self.exe_file = model_src_path
else:
_check_interpreter(interpreter)

self.model_src = model_src_path
self.interpreter = interpreter

# read config template
self._configs = config_io.read_config(config_file)
self._config = config_io.read_config(config_file)

def setup(
self,
Expand All @@ -61,30 +130,27 @@ def setup(
"""
# update config template if needed
if WorkDir:
self._configs["WorkDir"] = WorkDir
self._config["WorkDir"] = WorkDir

if ForcingFileName:
self._configs["ForcingFileName"] = ForcingFileName
self._config["ForcingFileName"] = ForcingFileName

if NumberOfTimeSteps:
self._configs["NumberOfTimeSteps"] = NumberOfTimeSteps
self._config["NumberOfTimeSteps"] = NumberOfTimeSteps

# create customized config file and input/output directories for model run
_, _, self.cfg_file = config_io.create_io_dir(
self._configs["ForcingFileName"], self._configs
self._config["ForcingFileName"], self._config
)

# read the run config file
self._configs = config_io.read_config(self.cfg_file)
self._config = config_io.read_config(self.cfg_file)

# prepare forcing data
forcing_io.prepare_forcing(self._configs)
forcing_io.prepare_forcing(self._config)

# prepare soil data
soil_io.prepare_soil_data(self._configs)

# set matlab log dir
os.environ['MATLAB_LOG_DIR'] = str(self._configs["InputPath"])
soil_io.prepare_soil_data(self._config)

return str(self.cfg_file)

Expand All @@ -94,23 +160,40 @@ def run(self) -> str:
Args:

Returns:
Tuple with stdout and stderr
string, the model log
"""

# run the model
args = [f"{self.exe_file} {self.cfg_file}"]
result = subprocess.run(
args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, check=True,
)
stdout = result.stdout

# TODO return log info line by line!
logger.info("%s", stdout)

return stdout
if self.exe_file:
# run using MCR
args = [f"{self.exe_file} {self.cfg_file}"]
# set matlab log dir
os.environ['MATLAB_LOG_DIR'] = str(self._config["InputPath"])
result = _run_sub_process(args, None)
if self.interpreter=="Matlab":
# set Matlab arguments
path_to_config = f"'{self.cfg_file}'"
eval_code= f'STEMMUS_SCOPE_exe({path_to_config});exit;'
args = ["matlab", "-r", eval_code, "-nodisplay", "-nosplash", "-nodesktop"]
# seperate args dont work on linux!
if utils.os_name() !="nt":
args = shlex.join(args)
result = _run_sub_process(args, self.model_src)
if self.interpreter=="Octave":
# set Octave arguments
# use subprocess instead of oct2py,
# see issue STEMMUS_SCOPE_Processing/issues/46
path_to_config = f"'{self.cfg_file}'"
# fix for windows
path_to_config = path_to_config.replace("\\", "/")
eval_code = f'STEMMUS_SCOPE_exe({path_to_config});exit;'
args = ["octave", "--eval", eval_code, "--no-gui", "--silent"]
# seperate args dont work on linux!
if utils.os_name() !="nt":
args = shlex.join(args)
result = _run_sub_process(args, self.model_src)
return result


@property
def config(self) -> Dict:
"""Return the configurations for this model."""
return self._configs
return self._config
Loading