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
120 changes: 120 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this repository is

A collection of **standalone Python scripts** for MIKE SHE (DHI's integrated hydrological modelling
system): engine plugins, data-processing utilities and MShePy API examples. There is no package, no
build step, no test suite and no dependency manifest — each `.py` file is self-contained and is either
executed directly or referenced as a plugin from a MIKE SHE `.she` setup.

Three directories, three different execution models:

| Directory | How the code runs |
|---|---|
| `Plugins/` | Loaded *by* the MIKE SHE engine during a simulation; the engine calls named module-level hook functions |
| `SimulationExecution/` | Standalone scripts that *drive* the MIKE SHE engine through `MShePy` |
| `DataProcessing/` | Standalone CLI/GUI tools that read or rewrite MIKE SHE files; no engine involved |

## Dependencies and environment

- **`MShePy`** ships with MIKE Zero, not pip. For standalone scripts it must be importable: add
`<MIKE Zero install>/bin/x64` to `PYTHONPATH` (preferred) or `sys.path.append(...)`.
Inside a plugin the engine has already made it available.
- Python version must be one supported by the installed MIKE release (MIKE 2026: Python 3.13).
For plugins, the `.she` file points at the **`pythonXY.dll`**, not `python.exe`.
- pip packages used across scripts: `mikeio` (high-level dfs/pfs), `mikecore` (low-level dfs writing,
used where mikeio is not granular enough), `numpy`, `scipy` (`openBoreHoles.py`), `pyshp`
(`PtPathline*.py`), `tkinter` (optional, for `pfs_pack.py`'s file dialog).
- mikeio makes breaking API changes between majors and these scripts track the current release
(3.0.1 at time of writing). Notably `Dataset` takes only DataArrays — the old
`Dataset(data=…, time=…, items=…)` constructor is gone. Check signatures against the installed
version rather than assuming, and record what you tested against in the file header.

## Running things

```powershell
# MShePy examples — cwd must be the script's own directory (DEMO_MODEL is a relative path)
cd SimulationExecution
python run_all_examples.py # runAll: single, serial variants, parallel, pooled
python execute_stepwise_examples.py # initialize + performTimeStep + runToTime, with an inline plugin

# Pack a .she setup and everything it references
python DataProcessing/pfs_pack.py <setup.she> [-o OUT] [-a|-m] [-d] [-f]
# no args -> interactive (tkinter dialog); -d writes a staging dir instead of a zip
```

There is no lint config and no automated tests. Verification means running an actual MIKE SHE model:
preprocess, then run, then inspect the produced dfs file. The 3-cell demo setup in
`SimulationExecution/Data/3x3_Box/` is the fast smoke-test model.

The preprocessor has no Python API — run `MShe_Preprocessor.exe` as a subprocess, locating it via
`os.path.dirname(ms.__file__)` so it comes from the same MIKE installation as the loaded `MShePy`
(see `pp()` in both `SimulationExecution` scripts).

## Plugin architecture

A plugin is a module defining any subset of hook functions the engine calls at fixed points. Hooks
used in this repo, in call order:

- `postEnterSimulator()` — after engine init. Grid geometry and static parameters are available;
result files are not. This is where setups read config, build state, and capture
`ms.wm.getSheFilePath()` for later.
- `preTimeStep()` / `postTimeStep()` — per water-movement time step.
- `preLeaveSimulator()` — output files still **open**. Use for closing files you opened yourself
(`write_sz_bnd_flow.py`) or writing dfs0 results accumulated in memory (`openBoreHoles.py`).
- `leaveSimulator()` — output files **closed**. This is the only safe place to read the engine's own
result files with mikeio; all the post-processing plugins do their work here.

Cross-cutting conventions the plugins rely on:

- **Result-file paths** are derived, not configured: the folder is `<full path to .she> + " - Result Files"`
(note: the `.she` extension stays in the folder name) and files are `<she stem>_3DSZ.dfs3`,
`_2DSZ.dfs2`, `_overland.dfs2`, `_2DUZ_UzCells.dfs2`, `_PreProcessed_3DSZ.dfs3` (preprocessed static
data such as layer bottoms). Custom result folders are **not** supported by these plugins.
- **Axis order differs between the two data sources.** MShePy datasets are `(x, y, z)`; dfs files read
through mikeio are `(time, z, y, x)` and `z` runs bottom-up. Mixing them requires an explicit
transpose/flip — see `read_layer_bottoms()` in `openBoreHoles.py`.
- **Inactive cells.** MIKE SHE writes NaN outside the model domain. The reduction plugins capture a
`nan_mask` from the first time step, reduce with `np.nan*` functions, then write NaN back into the
masked cells so inactive area does not become 0. Use `ms.wm.gridIsInModel()` / `gridIsInternal()`
when working on the MShePy side instead.
- **Output.** Post-processing plugins emit a single-timestep dfs2 built from `mikeio.DataArray` /
`mikeio.Dataset.to_dfs()`, inheriting `itemtype`, `unit` and `geometry` from the source item.
- **Feedback into the simulation** goes through `ms.wm.setValues(dataset)` after filling an
`ms.dataset(ms.paramTypes...)` — `openBoreHoles.py` writes `SZ_SOURCE` this way.
- **Logging**: `ms.wm.log()` for messages the user should see in the MIKE SHE log; `ms.wm.print()` for
verbose output to the print log file. Do not use bare `print()` in a plugin.

The `Plugins/` post-processing scripts (`OL_*`, `SZ_*`, `UZ_*`) are deliberate near-duplicates of one
another: same skeleton, different item names and reductions. When changing that skeleton, expect to
change it in several files rather than refactoring them into a shared module.

## Editing `.she` / pfs files programmatically

Use `mikeio.PfsDocument(path, unique_keywords=False)` — `unique_keywords=False` matters, MIKE SHE pfs
files legitimately repeat keywords. File-valued keywords must be written wrapped in pipes:
`f"|{path}|"`. `enable_plugin()` in `execute_stepwise_examples.py` shows the full set of keys needed to
turn plugins on (`SimSpec.ModelComp.Plugins`, `Plugins.PyResolve`, `Plugins.PyPath`,
`Plugins.PluginFileList.PluginFile_1.FILE_NAME`).

## Concurrency constraint

**MIKE SHE is not reentrant.** Two simulations cannot share a process, and a process cannot be reused
for a second simulation — which rules out `multiprocessing.Pool`. The working pattern is a
`ThreadPoolExecutor` bounding concurrency, with each thread spawning a fresh `Process` per simulation
(`run_variants_parallel_pool` in `run_all_examples.py`). Parallel variants also need distinct `.she`
copies; the same setup file cannot be run twice concurrently.

## Style

- New/maintained code uses **2-space indentation**; the older `DataProcessing/PtPathline*.py` and
`ReadPtBin.py` use 4. Match the file you are in.
- Every script starts with a header comment block: `Subject`, `Usage`, `Dependencies`/`Requires`,
optionally `Guarantee`/`Limitations`, then author and date. Keep it and keep it accurate — for
plugins it is the only user documentation, and several files document known limitations there
(e.g. `openBoreHoles.py` on explicit coupling lag, static exchange area and time-varying K).
- Comments in this codebase carry hydrological and numerical reasoning (why a formulation is a
simplification, what a diagnostic means physically). That density is intentional; preserve it.
- `snake_case` for new code; some older files and MShePy call sites are `camelCase`.
13 changes: 7 additions & 6 deletions Plugins/openBoreHoles.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Subject: Simulate open bore holes, calculate exchange flows between SZ layers and the bore hole and the resulting head in the bore hole
# Usage: Create a config file, attach to a MIKE SHE model as a plugin, adapt the few hard coded global variables (after imports)
# Dependencies: mikeio (which requires: numpy, scipy - these are also used here)
# Last tested with mikeio 3.0.1 on python 3.14.
Comment thread
watermain marked this conversation as resolved.
# author: uha@dhigroup.com
# date: 10/2022
#
Expand Down Expand Up @@ -397,14 +398,14 @@ def preTimeStep():
def preLeaveSimulator():
preTimeStep() # capture end of last time step
title = "Open bore hole heads"
items = [mikeio.ItemInfo(f"Head \"{bh.name}\"", itemtype=mikeio.EUMType.Water_Level) for bh in bhs]
fname = os.path.join(setup_dir, f"{setup_name}.she - Result Files/{setup_name}_BoreHoleHeads.dfs0")

ds = mikeio.Dataset(
data = [bh.heads for bh in bhs],
time = times,
items = items
)
items = [mikeio.DataArray(
np.array(bh.heads),
time=times,
item=mikeio.ItemInfo(f"Head \"{bh.name}\"", itemtype=mikeio.EUMType.Water_Level)
) for bh in bhs]
ds = mikeio.Dataset(items)
ds.to_dfs(fname, title=title)

ms.wm.print("\r\nBore Hole Convergence Report\r\n============================")
Expand Down