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
4 changes: 2 additions & 2 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
name: Build and push Docker image
name: Docker

on:
push:
Expand Down Expand Up @@ -31,7 +31,7 @@ env:

jobs:
build_and_push:
name: 📦
name: 📦 (build and push docker image)
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/governance.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
name: Governance checks
name: Governance

permissions:
contents: read
Expand All @@ -13,7 +13,7 @@ concurrency:

jobs:
governance:
name: 🩺
name: 🩺 (run governance checks)
runs-on: ubuntu-latest
timeout-minutes: 5
defaults:
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test_bash_infra.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
name: Test Bash infrastructure
name: Bash

on:
push:
Expand All @@ -18,7 +18,7 @@ concurrency:

jobs:
run_natively:
name: 🛠 (natively)
name: 🛠 (native bash infra tests)
runs-on: ubuntu-latest
timeout-minutes: 25 # something is off if we need more than this
defaults:
Expand Down Expand Up @@ -60,7 +60,7 @@ jobs:
CI=1 bash 02_generate_output_from_code.bash

run_dockerised:
name: 🐳 (dockerised)
name: 🐳 (dockerised bash infra tests)
runs-on: ubuntu-latest
timeout-minutes: 25 # something is off if we need more than this
defaults:
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test_figure_generation.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
name: Test figure generation
name: Figures

on:
push:
Expand All @@ -18,7 +18,7 @@ concurrency:

jobs:
run_natively:
name: 🛠 (natively)
name: 🛠 (native figure generation tests)
runs-on: ubuntu-latest
timeout-minutes: 20 # something is off if we need more than this
defaults:
Expand Down Expand Up @@ -114,7 +114,7 @@ jobs:
delete-on-status: success

run_dockerised:
name: 🐳 (dockerised)
name: 🐳 (dockerised figure generation tests)
runs-on: ubuntu-latest
timeout-minutes: 20 # something is off if we need more than this
defaults:
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test_python_code.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
name: Run Python Tests
name: Code

on:
push:
Expand All @@ -18,7 +18,7 @@ concurrency:

jobs:
run_natively:
name: 🛠 (natively)
name: 🛠 (native Python tests)
runs-on: ubuntu-latest
timeout-minutes: 25 # something is off if we need more than this
defaults:
Expand Down Expand Up @@ -76,7 +76,7 @@ jobs:
delete-on-status: success

run_dockerised:
name: 🐳 (dockerised)
name: 🐳 (dockerised Python tests)
runs-on: ubuntu-latest
timeout-minutes: 25 # something is off if we need more than this
defaults:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/zenodo-release.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
name: Zenodo release
name: Zenodo

# Publish a release of this repository to Zenodo.
#
Expand Down Expand Up @@ -68,7 +68,7 @@ concurrency:

jobs:
release:
name: Archive and deposit to Zenodo
name: 🏛 (archive and deposit to Zenodo)
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ __pycache__
*.ipynb
.ipynb_checkpoints
.secrets
02_generate_output_from_code.log
3 changes: 1 addition & 2 deletions .linkspector.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ useGitIgnore: true
ignorePatterns:
- pattern: '^mailto:'
- pattern: '^meta/index'
- pattern: '^https://github\.com/pymor/2025_pymor_paper/.*' # Site not publicly available
- pattern: '^https://github\.com/pymor/2026_paper_code.*' # Site not publicly available
- pattern: '^https://handle\.test\.datacite\.org/10.5072/zenodo\.568476' # gives a 404
timeout: 10000
retryCount: 3
87 changes: 77 additions & 10 deletions 02_generate_output_from_code.bash
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,38 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "${SCRIPT_DIR}"

# Full output of every experiment script is always captured here, so a second
# terminal can `tail -f 02_generate_output_from_code.log` regardless of VERBOSE.
LOGFILE="02_generate_output_from_code.log"
LOGFILE_PATH="${SCRIPT_DIR}/${LOGFILE}"
: > "${LOGFILE_PATH}"

# VERBOSE!=0 also streams each script's output to this console.
# VERBOSE=0 (default) keeps the console clean and instead prints a
# per-script wall-time and peak-memory summary; the full output is still in the
# LOGFILE_PATH.
VERBOSE="${VERBOSE:-0}"

if [[ -t 1 ]]; then
_green=$'\033[32m' _bold=$'\033[1m' _red=$'\033[31m' _reset=$'\033[0m'
_green=$'\033[32m' _bold=$'\033[1m' _red=$'\033[31m' _blue=$'\033[34m' _reset=$'\033[0m'
else
_green="" _bold="" _red="" _reset=""
_green="" _bold="" _red="" _blue="" _reset=""
fi

log() { echo "${_green}:: $*${_reset}"; }
raise() { echo "${_red}:: [ERROR] $*${_reset}" >&2;}

if [[ "${VERBOSE}" == "0" ]]; then
log "Suppressing experiment output: tail -f ${LOGFILE} to follow, or run with VERBOSE=1."
step() { echo -n "${_blue}:: $* ... ${_reset}"; }
else
step() { echo "${_blue}:: $*:${_reset}"; }
fi

if [[ "${QUICKTEST:-0}" == "1" ]]; then
log "QUICKTEST=1 detected: only running coarse experiments."
fi

PYTHON_PATH="$(command -v python 2>/dev/null || true)"
EXPECTED_SUFFIX=".conda_env/bin/python"

Expand All @@ -27,30 +50,74 @@ else
exit 1
fi

# Memory is measured via Python's resource.getrusage, which is portable and needs
# no /usr/bin/time.
run_experiment() {
local exp="$1"
VERBOSE="${VERBOSE}" LOGFILE_PATH="${LOGFILE_PATH}" python - "${exp}" <<'PY'
import os, resource, subprocess, sys, time

script = sys.argv[1]
verbose = os.environ.get('VERBOSE', '0') != '0'
LOGFILE_PATH = os.environ['LOGFILE_PATH']

start = time.perf_counter()
with open(LOGFILE_PATH, 'a') as log:
log.write(f'\n===== {script} =====\n')
log.flush()
if verbose:
# Stream to both console and LOGFILE_PATH.
proc = subprocess.Popen([sys.executable, script],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
text=True, bufsize=1)
for line in proc.stdout:
sys.stdout.write(line)
log.write(line)
proc.wait()
else:
# LOGFILE_PATH only; console stays clean.
proc = subprocess.run([sys.executable, script], stdout=log, stderr=log)
wall = time.perf_counter() - start

# ru_maxrss is reported in KiB on Linux (the container platform).
maxrss_kib = resource.getrusage(resource.RUSAGE_CHILDREN).ru_maxrss
if sys.platform == 'darwin': # macOS reports bytes
maxrss_kib /= 1024
mem_mib = maxrss_kib / 1024
h, rem = divmod(int(wall), 3600)
m, s = divmod(rem, 60)
time_str=f"{s:02d}s"
if m > 0:
time_str = f"{m:02d}m{time_str}"
if h > 0:
time_str = f"{h:d}h{time_str}"
print(f" (took {time_str} and {mem_mib:.0f}MiB)")
sys.exit(proc.returncode)
PY
}

log "Setting environment variables for reproducability ..."
export OMP_NUM_THREADS=1
export OPENBLAS_NUM_THREADS=1
export MKL_NUM_THREADS=1
export MKL_CBWR=COMPATIBLE
export MPLBACKEND=Agg

log "Running Mass-spring-damper experiments..."
log "Running Mass-spring-damper experiments:"
cd "${SCRIPT_DIR}/code/msd_chain"
for exp in run_*.py
do
log "Running ${exp}"
python ./"${exp}"
echo ""
step "Running msd_chain/${exp}"
run_experiment "${exp}"
done
cd "${SCRIPT_DIR}"

log "Running Navier-Stokes experiments..."
log "Running Navier-Stokes experiments:"
cd "${SCRIPT_DIR}/code/navier_stokes"
for exp in run_*.py
do
log "Running ${exp}"
python ./"${exp}"
echo ""
step "Running navier_stokes/${exp}"
run_experiment "${exp}"
done
cd "${SCRIPT_DIR}"

Expand Down
4 changes: 4 additions & 0 deletions 03_generate_figures_from_output.bash
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ fi
FIGURES_DIR="${SCRIPT_DIR}/figures"
cd "${FIGURES_DIR}"

if [[ "${QUICKTEST:-0}" == "1" ]]; then
log "QUICKTEST=1 detected: figures will use the QUICKTEST output."
fi

# Collect the default figure targets. This mirrors @default_files in
# .latexmkrc: all fig_*.tex plus architecture_diagram.tex.
log "Collecting figure targets..."
Expand Down
39 changes: 38 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ by
- Stephan Rave (<https://orcid.org/0000-0003-0439-7212>)
- Felix Schindler (<https://orcid.org/0000-0003-1582-7118>)

[![DOI](https://sandbox.zenodo.org/badge/DOI/10.5072/zenodo.568476.svg)](https://handle.test.datacite.org/10.5072/zenodo.568476) [![REUSE status](https://api.reuse.software/badge/github.com/pymor/2026_paper_code)](https://api.reuse.software/info/github.com/pymor/2026_paper_code)

## Reproducing results from the paper

### Generating the figures and tables
Expand Down Expand Up @@ -46,7 +48,8 @@ bash 02_generate_output_from_code.bash
to re-generate new [output/](output/) data.

> Note, that we're running all experiments sequentially with MKL compatibility flags for numerical reproducability (see the [02_generate_output_from_code.bash](02_generate_output_from_code.bash) script).
> Running them required around 51GB of memory and 150h of time on our target hardware (see for instance [figures/NOTES.md](figures/NOTES.md))!
> Running them required around 52GB of memory and 150h of time on our target hardware (see for instance [figures/NOTES.md](figures/NOTES.md))!
> See [the section on quick tests](#quick-tests) on how to run coarse experiments with lower requirements.

## Getting started

Expand Down Expand Up @@ -147,6 +150,40 @@ docker compose -f docker/docker-compose.yml run --rm dev COMMAND
This will mount the projects directory inside the container, source the env and run `COMMAND`.
Sourcing the `01_activate_env.bash` is thus _not_ required in this execution mode.

## Quick tests

We support running coarse experiments with `QUICKTEST=1` to roughly reproduce the behaviour of the `code/` with lower compute requirements.

> Note: this required around 2GB of memory and 1.5h of time on a consumer Notebook with a 12th Gen Intel(R) Core(TM) i7-12800H CPU.

- In [native mode](#native-execution):

```bash
source 01_activate_env.bash
QUICKTEST=1 bash 02_generate_output_from_code.bash
QUICKTEST=1 bash 03_generate_figures_from_output.bash
```

- In [dockerised mode](#dockerised-execution):

```bash
QUICKTEST=1 docker compose -f docker/docker-compose.yml run --rm dev bash 02_generate_output_from_code.bash
QUICKTEST=1 docker compose -f docker/docker-compose.yml run --rm dev bash 03_generate_figures_from_output.bash
```

This should leave you with `output/` and `figures/` that display a similar behaviour to the published ones.

> Note that there are significant differences in some figures:
>
> - figures/fig_dmd_evals_errors.pdf might not reproduce the eigenvalues
> - figures/fig_solution_outputs.pdf suffers from the shorter `T=0.5` (as opposed to `T=8`)
>
> Also note that running the quick tests will change the existing `output/` and `figures/` and add new files (due to changed filenames).
> To discard these changes and revert back to the reference state, use:
>
> - `git restore output figures`
> - `git clean -xdf output figures`

## License

Different parts of this repository are released under different licenses,
Expand Down
10 changes: 10 additions & 0 deletions code/msd_chain/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os

CI = os.environ.get('CI', '0') == '1'
QUICKTEST = os.environ.get('QUICKTEST', '0') == '1'

if CI:
print("CI=1 detected: only running basic test cases!")
Expand All @@ -11,6 +12,15 @@
PARAM_GRID = 25


elif QUICKTEST:
print("QUICKTEST=1 detected: only running coarse experiments!")

DATA_AMOUNT_STEP = 20
ERA_NT_FACTOR = 51
ORDER_STEP = 1
PARAM_GRID = 100


else:
DATA_AMOUNT_STEP = 1
ERA_NT_FACTOR = 1020
Expand Down
26 changes: 26 additions & 0 deletions code/navier_stokes/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from pymor.parameters.base import Parameters

CI = os.environ.get('CI', '0') == '1'
QUICKTEST = os.environ.get('QUICKTEST', '0') == '1'

if CI:
print("CI=1 detected: only running basic test cases!")
Expand All @@ -29,6 +30,31 @@
ML_ONLY_FINAL = True
ML_NN_EPOCHS = 10

elif QUICKTEST:
print("QUICKTEST=1 detected: only running coarse experiments!")

T = 0.5
REFINE = False
DT = 0.0005

PARAMETER_RANGE = (0.01, 1.)

MAX_NUM_PODDEIM_TRAINING_MUS = 11
MAX_NUM_ML_TRAINING_MUS = 30
NUM_DETERMINISTIC_TRAINING_MUS = 6
NUM_RANDOM_TRAINING_MUS = MAX_NUM_ML_TRAINING_MUS - NUM_DETERMINISTIC_TRAINING_MUS
NUM_TEST_MUS = 20

POD_TOL = 1e-4
HAPOD_SLICES = 2

ML_INPUT_SCALING = True
ML_OUTPUT_SCALING = True
ML_NUM_ROM_TRAINING_MUS = 200
ML_TIME_SUBSAMPLE = 10
ML_ONLY_FINAL = False
ML_NN_EPOCHS = 10

else:
T = 8
REFINE = True
Expand Down
Loading
Loading