Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .zenodo.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
],
"license": "GPL-3.0",
"keywords": [
"research data management", "simulation", "reproducibility", "containerization"
"research data management", "simulation", "reproducibility"
],
"version": "1.1.2",
"access_right": "open",
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,11 @@ Detailed descriptions of commands and APIs are provided in the dedicated interfa
5. Push project and output repositories to their remotes
6. Reuse or reference results via their output branches

The links between the project and output repositories are kept up to date by every commit made through CADET-RDM.
For commits made with plain git, an optional [pre-commit](https://pre-commit.com/) hook, `rdm-check`, does the same; see the [command line interface](https://cadet-rdm.readthedocs.io/en/latest/user_guide/command-line-interface.html) documentation.

Results are referenced by unique output branch names that encode the timestamp, active project branch, and project commit hash. CADET-RDM provides a local cache mechanism that allows results from previous runs or from other CADET-RDM projects to be reused as input data while preserving provenance information.

Results are referenced by unique output branch names that encode the timestamp, active project branch, and project commit hash, followed by a random suffix. CADET-RDM provides a local cache mechanism that allows results from previous runs or from other CADET-RDM projects to be reused as input data while preserving provenance information.


## Getting started
Expand All @@ -90,5 +93,5 @@ It includes installation instructions, usage guides for the different interfaces
## Project information

- **License:** see [LICENSE](LICENSE)
- **Contributing:** see [CONTRIBUTING](CONTRIBUTING.md)
- **Authors and contributors:** see [AUTHORS](AUTHORS.md)
- **Contributing:** see [CONTRIBUTING](https://github.com/cadet/CADET-RDM/blob/main/CONTRIBUTING.md)
- **Authors and contributors:** see [AUTHORS](https://github.com/cadet/CADET-RDM/blob/main/AUTHORS.md)
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@

# The theme to use for HTML and HTML Help pages.
html_theme = "sphinx_book_theme"
html_logo = "_static/logo.png"
html_logo = "_static/figures/logo.png"

html_theme_options = {
"show_toc_level": 2,
Expand Down
1 change: 1 addition & 0 deletions docs/source/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ user_guide/installation
user_guide/getting-started
user_guide/command-line-interface
user_guide/python-interface
user_guide/cases
user_guide/jupyter-interface
```

Expand Down
156 changes: 156 additions & 0 deletions docs/source/user_guide/cases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
(cases)=
# Cases: options, environments and reproducible results

CADET-RDM identifies every result by the combination of three things:

- the **project**: the commit of the project repository that produced the result,
- the **options**: the configuration the project code was run with,
- the **environment**: the conda and pip packages installed during the run.

A `Case` bundles a project repository with a set of `Options` and, optionally, `Environment` requirements.
It runs the project code for this combination, or reuses existing results if this combination was already run.
This makes parameter studies and comparisons reproducible without manual bookkeeping of which result belongs to which configuration.

## Options

`Options` is a nested dictionary that also supports attribute access.
It holds the configuration of a run and is stored as `options.json` with the results.

```python
from cadetrdm import Options

options = Options()
options.commit_message = "Parameter study, high flow rate"
options.debug = False
options.push = True
options.flow_rate = 2.5e-7
options.optimizer_options = {"optimizer": "U_NSGA3", "pop_size": 16}
```

Values must be JSON-serializable: Python built-ins, numpy arrays and `pathlib.Path` objects are supported.
Options can be saved and loaded with `options.dump_json_file(path)` and `Options.load_json_file(path)`.

### The options hash

Each set of options has a hash, `options.get_hash()`, which identifies the configuration in the output log.
Not every key contributes to the hash:

- The run control keys `commit_message`, `debug`, `push`, `force` and `branch_prefix` are excluded, since they do not change the result.
- Keys starting with an underscore (`_`) or containing a double underscore (`__`) are excluded at every nesting level.
Use them for machine-specific settings that should not affect the identity of a result, e.g. a temporary directory:

```python
options._tmp_directory = "/dev/shm"
```

### Reusing options

Because the hash depends only on the configuration, the same `Options` can be reused across projects, e.g. to run the same parameter set with different models, or copied and modified for a parameter study:

```python
cases = []
for flow_rate in [1.0e-7, 2.5e-7, 5.0e-7]:
case_options = options.copy()
case_options.flow_rate = flow_rate
cases.append(case_options)
```

## Environments

An `Environment` describes package requirements.
Package versions are given as exact versions or as [semantic version](https://semver.org/) specifications:

```python
from cadetrdm import Environment

environment = Environment(
conda_packages={"cadet": ">=5.0.3"},
pip_packages={"cadet-process": "~0.12.0", "numpy": "2.0.2"},
)
```

- `"2.0.2"` requires exactly this version.
- `">=5.0.3"`, `"<6"` and similar require a version in this range.
- `"~0.12.0"` requires an approximately matching version, i.e. `0.12.x`.

For every run, CADET-RDM records the complete environment (`conda env export` and `pip freeze`) in the `run_history` directory on the main branch of the output repository.
An `Environment` given to a `Case` is checked against these records, so only results created in a matching environment are reused.
CADET-RDM does not install packages: if the active environment does not fulfil the requirements, the case is not run.

## Preparing the project for cases

A `Case` imports the main package of the project repository and calls its `main` function, or the function given with `Case(..., run_method=...)`.
By default, the package is the directory named like the project repository, e.g. `my_project/my_project/__init__.py`, and a different directory can be set with `ProjectRepo(package_dir=...)`.
The function is decorated with `tracks_results`, which runs it in the tracking context and passes the project repository and the options:

```python
# my_project/my_project/main.py
from cadetrdm import tracks_results


@tracks_results
def main(repo, options):
results = simulate(flow_rate=options.flow_rate)
results.save(repo.output_path / "results.h5")
```

```python
# my_project/my_project/__init__.py
from .main import main
```

The decorated function can also be called directly with options, a dictionary or the path to an options JSON file.
It requires the keys `commit_message` and `debug`, writes the options to `options.json` in the output, pushes the results if `options.push` is set, and returns the name of the new output branch together with the return value of the function.
With `options.debug = True`, the function runs without tracking the results.

## Running and loading cases

```python
from cadetrdm import Case, ProjectRepo

project_repo = ProjectRepo("path/to/my_project")
case = Case(project_repo=project_repo, options=options, environment=environment)

results_path = case.run_study()
```

`run_study()`

1. updates the project and output repositories from their remotes (skipped in debug mode),
2. looks for existing results of this project commit, these options and this environment,
3. returns the path to these results if they exist,
4. otherwise runs the project code, if the active environment fulfils the environment requirements, and returns the path to the new results.

Use `run_study(force=True)` to run the code even if results exist.
If the code fails or the environment does not match, `run_study()` returns `None`.
The execution status is stored in a `<project>.status` file next to the project repository, so a case that is already running elsewhere on the same file system is skipped.

To only look up existing results without running anything, use `load()`:

```python
results_path = case.load()

case.has_results_for_this_run
case.results_branch
```

`load()` does not modify the project or output repository.
It reads the output log from the local main branch of the output repository, so results that were pushed from another machine are only found after updating, e.g. with `run_study()` or `case.output_repo.update_main()`.

By default, results must match the project commit, the options hash and the environment.
Each requirement can be relaxed, e.g. to reuse results from an earlier commit whose changes do not affect them:

```python
results_path = case.load(
allow_commit_hash_mismatch=True,
allow_options_hash_mismatch=False,
allow_environment_mismatch=False,
)
```

## Results are unique and read-only

Every run creates its own output branch, named after the time of the run, the project branch and commit, plus a random suffix (see {ref}`Python interface <python_interface>`).
A result therefore always points to exactly one project commit, one set of options and one recorded environment, and can be reproduced by checking out that commit and running it again with the stored `options.json`.

Loaded results are copied into the cache directory `<output>_cached/<branch name>` of the project repository and made read-only, so a result that is used as input for further work cannot be modified by accident.
7 changes: 5 additions & 2 deletions docs/source/user_guide/command-line-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,12 @@ rdm push --output-only
Each run is stored in an output branch named:

```
<timestamp>_<active_project_branch>_<project_repo_hash[:7]>
[<branch_prefix>_]<timestamp>_<active_project_branch>_<project_repo_hash[:7]>_<suffix>
```

The random six-character suffix keeps runs that start at the same time apart.
The prefix is optional and set with `options.branch_prefix`.

Cache results locally:

```bash
Expand Down Expand Up @@ -156,7 +159,7 @@ rdm remote create <url> <namespace> <name> <username>
Example:

```bash
rdm remote create https://github.com/ githubusers_workproject Workproject githubuser
rdm remote create https://api.github.com githubusers_workproject Workproject githubuser
```

The output repository name is derived automatically by appending `_output` to the project repository name.
Expand Down
22 changes: 16 additions & 6 deletions docs/source/user_guide/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ CLI (check consistency and stage changes):
rdm check
```

Commits made through CADET-RDM keep the links between the project and output repositories up to date.
For commits made with plain git, the optional `rdm-check` pre-commit hook does the same, see [Command line interface](command-line-interface.md).

CLI (commit staged changes):

```bash
Expand All @@ -137,6 +140,8 @@ CLI (push project and output repositories):
rdm push
```

To push only the output repository, e.g. to publish results without unfinished project changes, use `rdm push --output-only`.

Python (push both repositories):

```python
Expand All @@ -148,9 +153,12 @@ repo.push()
Results are referenced by a unique output branch name:

```
<timestamp>_<active_project_branch>_<project_repo_hash[:7]>
[<branch_prefix>_]<timestamp>_<active_project_branch>_<project_repo_hash[:7]>_<suffix>
```

The random six-character suffix keeps runs that start at the same time apart.
The prefix is optional and set with `options.branch_prefix`.

CLI:

```bash
Expand Down Expand Up @@ -272,21 +280,23 @@ repo.output_repo.add_remote("git@<my_git_server.foo>:<project>_output.git")

CADET-RDM can create both remotes automatically if a Personal Access Token is available in the Python keyring.

The URL must match the GitHub or GitLab instance used for remote creation, for example:
Store the token under the URL of the host: `https://github.com/` for GitHub, or the URL of the GitLab instance, e.g. `https://gitlab.com/`.

Store a token (Python):

```python
import keyring
keyring.set_password("https://jugit.fz-juelich.de/", "username", "token")
keyring.set_password("https://github.com/", "githubuser", "token")
```

Store a token (CLI):

```bash
keyring set "https://jugit.fz-juelich.de/" <username>
keyring set "https://github.com/" githubuser
```

When creating the remotes, pass the API URL `https://api.github.com` for GitHub, or the URL of the GitLab instance.

Create remotes (Python):

```python
Expand All @@ -296,7 +306,7 @@ repo = ProjectRepo()
repo.create_remotes(
name="Workproject",
namespace="githubusers_workproject",
url="https://github.com/",
url="https://api.github.com",
username="githubuser"
)
```
Expand All @@ -310,7 +320,7 @@ rdm remote create <url> <namespace> <name> <username>
Example:

```bash
rdm remote create https://github.com/ githubusers_workproject Workproject githubuser
rdm remote create https://api.github.com githubusers_workproject Workproject githubuser
```

The output repository name is derived automatically by appending `_output` to the project repository name.
Expand Down
9 changes: 5 additions & 4 deletions docs/source/user_guide/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ CADET-RDM projects are structured into two distinct repositories.
:width: 700
:alt: RDM structure

CADET-RDM repository architechture
CADET-RDM repository architecture
:::

Both the **project** and the **output** repository are their own git repositories. The commit architecture of CADET-RDM allows for easy tracking and reproducing of results and their respective project code.

## RDM commit architecture

Every run of the project code creates a new output branch (*result branch*) in the **output directory**. The repository on this new branch uniquely contains the files created by the execution of the project code. <br> At the same time, for every run of the project code the `run_history` directory on the master branch of the output repository is updated. This directory is unique to the master branch and contains the metadata and software specifications for every branch in the output repository. This directory also links the results in the output branch to the corresponding commit in the project repository used to create them. For transparency and easy accessibility, the most important specifications for every result branch are also documented in the `log.tsv` on the master branch of the output repository.
Every run of the project code creates a new output branch (*result branch*) in the **output directory**. The repository on this new branch uniquely contains the files created by the execution of the project code. <br> At the same time, for every run of the project code the `run_history` directory on the main branch of the output repository is updated. This directory is unique to the main branch and contains the metadata and software specifications for every branch in the output repository. This directory also links the results in the output branch to the corresponding commit in the project repository used to create them. For transparency and easy accessibility, the most important specifications for every result branch are also documented in the `log.tsv` on the main branch of the output repository.

```{eval-rst}
.. subfigure:: AB
Expand All @@ -55,14 +55,15 @@ Every run of the project code creates a new output branch (*result branch*) in t
:alt: Output Repository
:width: 420px

CADET-RDM commit architechture.
CADET-RDM commit architecture.
```


Because of this simultanious log of the metadata and the environment used to create a specific output, results can be reproduced easily.
Because of this simultaneous log of the metadata and the environment used to create a specific output, results can be reproduced easily.

## User function

The tools of CADET-RDM can be used through the command line interface (CLI), via context tracking in Python scripts or within [Jupyter Lab](https://jupyterlab.readthedocs.io/en/latest/).

The following documentation contains an installation guide, a user guide to quickly start using CADET-RDM and more detailed descriptions on using the command line interface, python interface and jupyter interface.
The page on [cases](cases.md) explains how projects, options and environments together identify a result, and how results are reused instead of recomputed.
14 changes: 12 additions & 2 deletions docs/source/user_guide/python-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,25 @@ Project and output repositories can be pushed together using a single command.
repo.push()
```

To push only the output repository:

```python
repo.output_repo.push()
```

Consistency checks and staging are handled automatically by the Python interface before pushing.

### Reusing results from earlier runs

Each run is stored in an output branch named:

```
<timestamp>_<active_project_branch>_<project_repo_hash[:7]>
[<branch_prefix>_]<timestamp>_<active_project_branch>_<project_repo_hash[:7]>_<suffix>
```

The random six-character suffix keeps runs that start at the same time apart.
The prefix is optional and set with `options.branch_prefix`.

Reuse results from a previous run by loading them into the local cache:

```python
Expand Down Expand Up @@ -142,6 +151,7 @@ repo.output_repo.add_remote("git@<my_git_server.foo>:<project>_output.git")
### Creating remotes automatically

Remote repositories can be created automatically using the GitHub or GitLab APIs if a Personal Access Token is available in the Python keyring.
The token is stored under `https://github.com/` for GitHub, or the URL of the GitLab instance (see [getting started](getting-started.md)), and `url` is the API URL `https://api.github.com` for GitHub, or the URL of the GitLab instance.

```python
from cadetrdm import ProjectRepo
Expand All @@ -150,7 +160,7 @@ repo = ProjectRepo()
repo.create_remotes(
name="Workproject",
namespace="githubusers_workproject",
url="https://github.com/",
url="https://api.github.com",
username="githubuser"
)
```
Expand Down
Loading
Loading