Examples
This page provides practical examples demonstrating the main features of MOBIDICpy. All example scripts are available in the examples/01-event-Arno-basin/ directory of the repository.
Arno River basin (November 2023 flood event)
All examples use data from the Arno River basin for a flood event that occurred in November 2023. The dataset is located in examples/datasets/Arno_event_Nov_2023/ and includes raster data (DEM, flow direction, soil parameters), the river network shapefile, meteorological forcing (.mat), observed discharge, and reservoir data.
Configuration files for each workflow are in examples/01-event-Arno-basin/.
01a — Basic simulation
The complete MOBIDIC workflow from raw data to final outputs.
Script: examples/01-event-Arno-basin/01a_run_example_Arno.py
from pathlib import Path
from mobidic import load_config, run_preprocessing, save_gisdata, save_network, MeteoData, Simulation
config = load_config("examples/01-event-Arno-basin/Arno.yaml")
# Preprocessing
gisdata = run_preprocessing(config)
save_gisdata(gisdata, config.paths.gisdata)
save_network(gisdata.network, config.paths.network, format="parquet")
# Convert meteorological data from MATLAB to NetCDF
meteo_data = MeteoData.from_mat("examples/datasets/Arno_event_Nov_2023/meteodata/meteodata.mat")
meteo_data.to_netcdf(config.paths.meteodata)
# Load forcing and run simulation
forcing = MeteoData.from_netcdf(config.paths.meteodata)
sim = Simulation(gisdata, forcing, config)
results = sim.run(start_date=forcing.start_date, end_date=forcing.end_date)
What it demonstrates:
- Loading YAML configuration files
- GIS preprocessing (DEM, flow direction, soil parameters, river network)
- Meteorological data conversion from MATLAB
.matto CF-compliant NetCDF - Running a complete MOBIDIC simulation
- Saving discharge time series and model state
01b — Validation: Python vs MATLAB
Validates the Python implementation against MATLAB reference outputs.
Script: examples/01-event-Arno-basin/01b_run_example_Arno_plots.py
Compares discharge time series produced by MOBIDICpy against the MATLAB reference output at examples/datasets/Arno_event_Nov_2023/output/matlab/discharge.csv.
Requirements: Run 01a_run_example_Arno.py first to generate the Python output.
02 — Station vs raster forcing comparison
Compare station-based forcing (with spatial interpolation) against pre-interpolated raster forcing.
Script: examples/01-event-Arno-basin/02_run_example_Arno_raster_forcing.py
from mobidic import MeteoData, MeteoRaster, Simulation
# Run 1: Station-based with forcing output enabled
config.output_forcing_data.meteo_data = True
forcing_stations = MeteoData.from_netcdf(config.paths.meteodata)
sim1 = Simulation(gisdata, forcing_stations, config)
results1 = sim1.run(start_date, end_date)
# Interpolated grids saved to: output/meteo_forcing.nc
# Run 2: Raster-based (using exported interpolated data)
forcing_raster = MeteoRaster.from_netcdf("output/meteo_forcing.nc")
sim2 = Simulation(gisdata, forcing_raster, config)
results2 = sim2.run(start_date, end_date)
# Results are numerically identical (differences < 1e-6 m³/s)
What it demonstrates:
- Exporting interpolated meteorological data as raster forcing
- Using raster forcing for subsequent runs (skips interpolation, faster)
- Verifying that both approaches produce identical results
Use cases: Calibration runs, scenario analysis, large domains.
03 — Simulation restart capability
Run a simulation in two stages and compare against a continuous run.
Script: examples/01-event-Arno-basin/03_run_example_Arno_restart.py
from pathlib import Path
from mobidic import load_config, load_gisdata, MeteoData, Simulation
config = load_config("examples/01-event-Arno-basin/Arno.yaml")
gisdata = load_gisdata(config.paths.gisdata, config.paths.network)
forcing = MeteoData.from_netcdf(config.paths.meteodata)
# First run: simulate to midpoint
sim1 = Simulation(gisdata, forcing, config)
results_1 = sim1.run(start_date=start_date, end_date=restart_point)
# States automatically saved to config.paths.states
# Second run: restart from saved state
sim2 = Simulation(gisdata, forcing, config)
state_file = Path(config.paths.states) / "states_001.nc"
sim2.set_initial_state(state_file=state_file, time_index=-1)
results_2 = sim2.run(start_date=restart_point, end_date=end_date)
What it demonstrates:
- Saving intermediate simulation states to NetCDF files
- Loading states from file using
set_initial_state() - Continuing simulations from saved checkpoints
- Validating restart accuracy against a continuous run
Use cases: Long-term simulations, checkpoint recovery, multi-stage modeling, real-time forecast updates.
04a — Reservoir routing
Simulate reservoirs with time-varying regulation curves.
Script: examples/01-event-Arno-basin/04a_run_example_Arno_reservoirs.py
from mobidic import load_config, run_preprocessing, MeteoData, Simulation
config = load_config("examples/01-event-Arno-basin/Arno.reservoirs.yaml")
gisdata = run_preprocessing(config) # Automatically processes reservoir data
forcing = MeteoData.from_netcdf(config.paths.meteodata)
sim = Simulation(gisdata, forcing, config)
results = sim.run(start_date=forcing.start_date, end_date=forcing.end_date)
Configuration (Arno.reservoirs.yaml):
parameters:
reservoirs:
res_shape: reservoirs/reservoirs.shp
stage_storage: reservoirs/stage_storage.csv
regulation_curves: reservoirs/regulation_curves.csv
regulation_schedule: reservoirs/regulation_schedule.csv
output_states:
reservoir_states: true # Save volume, stage, discharge
What it demonstrates:
- Reservoir preprocessing (polygon rasterization, inlet/outlet reach detection)
- Time-varying regulation curves (seasonal winter/summer operations)
- Reservoir state output (volume, stage, regulated discharge) to NetCDF
04b — Reservoir validation plots
Validates reservoir results against MATLAB reference outputs.
Script: examples/01-event-Arno-basin/04b_run_example_Arno_reservoirs_plots.py
Requirements: Run 04a_run_example_Arno_reservoirs.py first.
05 — GLM calibration (PEST++)
Gradient-based model calibration using pestpp-glm (Gauss-Levenberg-Marquardt).
Script: examples/01-event-Arno-basin/05_calibrate_Arno_glm.py
Prerequisites:
make install-calib
# or: pip install "mobidicpy[calibration]" && get-pestpp :pyemu
# Ensure pestpp-glm is on PATH
from mobidic.calibration import PestSetup
from mobidic.calibration.config import load_calibration_config
# Read calibration configuration
calib_config = load_calibration_config("Arno.calibration.yaml")
# Set up PEST++ and generate all working files
pest = PestSetup(calib_config)
working_dir = pest.setup()
# Run pestpp-glm
results = pest.run()
# Extract results
optimal = results.get_optimal_parameters() # Optimal parameter values
phi_history = results.get_objective_function_history() # Convergence
sens = results.get_parameter_sensitivities() # Jacobian-based sensitivities
What it demonstrates:
- Setting up and running
pestpp-glmfrom a single configuration file - Parallel model runs with configurable number of workers
- Extracting optimal parameters, objective function history, and sensitivities
- Running a validation simulation with optimal parameters
- Plotting observed vs simulated discharge with NSE/KGE metrics
Output:
- Optimal parameter values (alpha, beta, gamma, kappa)
- Objective function convergence by iteration
- Parameter sensitivity from the Jacobian
- Discharge comparison plot and convergence curve
06 — IES ensemble calibration (PEST++)
Ensemble-based calibration using pestpp-ies (Iterative Ensemble Smoother).
Script: examples/01-event-Arno-basin/06_calibrate_Arno_ies.py
Settings at the top of the script:
noptmax = 3 # Maximum optimization iterations
ies_num_reals = 20 # Number of ensemble members (generated automatically)
from mobidic.calibration.config import CalibrationConfig, load_calibration_config
from mobidic.calibration import PestSetup
cc = load_calibration_config("Arno.calibration.yaml")
# Override tool to IES and set ensemble options
cc_dict = cc.model_dump()
cc_dict["pest_tool"] = "ies"
cc_dict["pest_options"] = {"noptmax": noptmax, "ies_num_reals": ies_num_reals}
cc_ies = CalibrationConfig(**cc_dict)
# Set up and run
pest = PestSetup(cc_ies, base_path=calib_config_path.parent)
pest.setup()
results = pest.run()
# Ensemble statistics
phi_history = results.get_objective_function_history()
# Returns DataFrame with columns: iteration, mean, std
What it demonstrates:
- Ensemble-based parameter estimation with automatically generated ensembles
- Ensemble statistics for the objective function (mean ± std per iteration)
- Plotting ensemble spread to visualise uncertainty reduction
Output:
- Optimal parameters from the ensemble mean
- Objective function history with ensemble spread (mean ± std per iteration)
- Discharge comparison plot and ensemble convergence curve
07 — Morris global sensitivity analysis (PEST++)
Global sensitivity analysis using pestpp-sen with the Morris OAT method.
Script: examples/01-event-Arno-basin/07_sensitivity_Arno_Morris.py
Settings at the top of the script:
from mobidic.calibration.config import CalibrationConfig, load_calibration_config
from mobidic.calibration import PestSetup
cc = load_calibration_config("Arno.calibration.yaml")
# Override tool to sensitivity analysis
cc_dict = cc.model_dump()
cc_dict["pest_tool"] = "sen"
cc_dict["pest_options"] = {
"gsa_method": "morris",
"gsa_morris_r": gsa_morris_r,
"gsa_morris_obs_sen": False
}
cc_dict["parallel"] = {"num_workers": None} # Use all available CPUs
cc_sen = CalibrationConfig(**cc_dict)
pest = PestSetup(cc_sen, base_path=calib_config_path.parent)
pest.setup()
results = pest.run()
# Read Morris sensitivity indices
sens = results.get_parameter_sensitivities()
# DataFrame with columns: parameter, mean, mean_abs, std_dev
What it demonstrates:
- Global sensitivity analysis with Morris OAT method
- Parallel model evaluation across all available CPUs
- Interpreting Morris indices (μ*, μ, σ)
Output:
- Table of Morris sensitivity indices per parameter (μ*, μ, σ)
- Higher μ* indicates greater influence on model output
PEST++ calibration configuration
Examples 05–07 all share a single calibration configuration file Arno.calibration.yaml. Key sections:
# PEST++ tool: glm | ies | sen | da | opt | mou | sqp
pest_tool: glm
# Full simulation window (includes warm-up period)
simulation_period:
start_date: "2023-10-31 00:15:00"
end_date: "2023-11-04 23:45:00"
# Only observations within this window contribute to the objective function
calibration_period:
start_date: "2023-11-01 00:00:00"
end_date: "2023-11-04 23:45:00"
# Rasterize forcing once and reuse for all model evaluations (faster)
use_raster_forcing: true
# Parameters (dot-notation keys into Arno.yaml)
parameters:
- name: alpha
parameter_key: parameters.soil.alpha
initial_value: 8.0e-06
lower_bound: 1.0e-08
upper_bound: 1.0e-04
transform: log # log | none | fixed
par_group: soil
# Observations with optional metric pseudo-observations
observations:
- name: Q_292
obs_file: "../datasets/Arno_event_Nov_2023/data/Q_Nave_di_Rosano_2023.csv"
reach_id: 292
weight: 1.0
time_column: time
value_column: Q_292
metrics:
- metric: nse
target: 1.0
weight: 10.0
- metric: peak_error
target: 0.0
weight: 8.0
# Parallel execution (null = all available CPUs)
parallel:
num_workers: 8
port: 4004
See examples/01-event-Arno-basin/Arno.calibration.yaml for the fully annotated configuration.
Design storm simulation with hyetograph generation
Generate synthetic design storm hyetographs from IDF parameters and run a design flood simulation.
from datetime import datetime
from pathlib import Path
from mobidic import load_config, load_gisdata, Simulation
from mobidic.preprocessing.hyetograph import HyetographGenerator
config_file = Path("Arno_hyetograph.yaml")
config = load_config(config_file)
gisdata = load_gisdata(config.paths.gisdata, config.paths.network)
# Generate hyetograph forcing — all parameters read from config
forcing = HyetographGenerator.from_config(
config=config,
base_path=config_file.parent,
start_time=datetime(2000, 1, 1)
)
sim = Simulation(gisdata, forcing, config)
results = sim.run(forcing.start_date, forcing.end_date)
Configuration (Arno_hyetograph.yaml):
paths:
hyetograph: output/design_storm.nc
hyetograph:
a_raster: idf/a.tif # IDF scale parameter
n_raster: idf/n.tif # IDF exponent
k_raster: idf/k30.tif # Return period factor (30-year event)
duration_hours: 48
timestep_hours: 1
hyetograph_type: chicago_decreasing
ka: 0.8 # Areal reduction factor
IDF formula:
What it demonstrates:
- Generating synthetic design storms from spatially distributed IDF parameters
- Automatic resampling of IDF rasters to match model grid
- Chicago decreasing hyetograph method
Additional resources
How to run the examples
# Basic simulation
python examples/01-event-Arno-basin/01a_run_example_Arno.py
# Validation plots (requires 01a output)
python examples/01-event-Arno-basin/01b_run_example_Arno_plots.py
# Station vs raster forcing
python examples/01-event-Arno-basin/02_run_example_Arno_raster_forcing.py
# Restart capability
python examples/01-event-Arno-basin/03_run_example_Arno_restart.py
# Reservoir model
python examples/01-event-Arno-basin/04a_run_example_Arno_reservoirs.py
# GLM calibration (requires PEST++ binaries)
python examples/01-event-Arno-basin/05_calibrate_Arno_glm.py
# IES ensemble calibration (requires PEST++ binaries)
python examples/01-event-Arno-basin/06_calibrate_Arno_ies.py
# Morris sensitivity analysis (requires PEST++ binaries)
python examples/01-event-Arno-basin/07_sensitivity_Arno_Morris.py
See also
- API Reference for detailed function documentation
- Introduction for model background and theory
- Development Guide for contributing