Coverage for src/gwtransport/examples.py: 98%
117 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 21:17 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 21:17 +0000
1"""
2Example Data Generation for Groundwater Transport Modeling.
4This module provides utilities to generate synthetic datasets for demonstrating
5and testing groundwater transport models. It creates realistic flow patterns,
6concentration/temperature time series, and deposition events suitable for testing
7advection, diffusion, and deposition analysis functions.
9Available functions:
11- :func:`generate_example_data` - Generate comprehensive synthetic dataset with flow and
12 concentration time series. Creates seasonal flow patterns with optional spill events,
13 input concentration data via synthetic sinusoidal patterns, constant values, or real KNMI
14 soil temperature, and extracted concentration computed through gamma-distributed pore volume
15 transport. When diffusion parameters are provided, uses the diffusion module instead of
16 pure advection. Returns DataFrame with flow, cin, cout columns plus attrs containing
17 generation parameters and aquifer properties, and time edges (tedges).
19- :func:`generate_temperature_example_data` - Convenience wrapper around
20 :func:`generate_example_data` with sensible defaults for temperature transport including
21 thermal retardation, thermal diffusivity, and longitudinal dispersivity.
23- :func:`generate_ec_example_data` - Convenience wrapper around
24 :func:`generate_example_data` with sensible defaults for electrical conductivity (EC)
25 transport. EC is a conservative tracer (retardation factor 1.0) with negligible molecular
26 diffusivity compared to thermal transport.
28- :func:`generate_example_deposition_timeseries` - Generate synthetic deposition time series
29 for pathogen/contaminant deposition analysis. Combines baseline deposition, seasonal patterns,
30 random noise, and episodic contamination events with exponential decay. Returns Series with
31 deposition rates [ng/m²/day] and attrs containing generation parameters, and time edges
32 (tedges). Useful for testing extraction_to_deposition deconvolution and
33 deposition_to_extraction convolution functions.
35This file is part of gwtransport which is released under AGPL-3.0 license.
36See the ./LICENSE file or go to https://github.com/gwtransport/gwtransport/blob/main/LICENSE for full license details.
37"""
39import numpy as np
40import numpy.typing as npt
41import pandas as pd
43from gwtransport.advection import gamma_infiltration_to_extraction, infiltration_to_extraction
44from gwtransport.diffusion_fast import gamma_infiltration_to_extraction as diffusion_gamma_infiltration_to_extraction
45from gwtransport.diffusion_fast import infiltration_to_extraction as diffusion_infiltration_to_extraction
46from gwtransport.gamma import mean_std_loc_to_alpha_beta
47from gwtransport.utils import compute_time_edges
49_DEFAULT_GAMMA_MEAN = 1000.0 # m³
50_DEFAULT_GAMMA_STD = 200.0 # m³
51_DEFAULT_GAMMA_LOC = 0.0 # m³, minimum pore volume
52_DEFAULT_GAMMA_NBINS = 250
54# KNMI station 260 (De Bilt) daily-mean soil temperature at 20 cm depth (column TB3, degrees
55# Celsius), UTC, from 2020-01-01 onward. Stored inline -- rather than downloaded from KNMI via
56# gwtransport.utils.get_soil_temperature -- so the cin_method="soil_temperature" examples run
57# without network access (e.g. in the browser under Pyodide/JupyterLite). Rounded to 0.01 degrees.
58_SOIL_TEMPERATURE_TB3_START = "2020-01-01"
59_SOIL_TEMPERATURE_TB3 = (5.65, 5.62, 6.2, 6.43, 6.68, 6.8, 6.55, 7.07, 7.95, 8.4, 7.38, 7.07, 7.33, 7.65, 8.25, 7.7, 7.47, 7.28, 6.43, 5.8, 5.68, 5.68, 6.32, 5.98, 5.9, 5.8, 6.45, 6.82, 6.38, 6.45, 7.28, 8.05, 7.93, 8.0, 7.73, 6.85, 6.53, 6.32, 6.05, 6.78, 7.57, 6.93, 6.38, 6.03, 6.22, 6.68, 7.75, 8.1, 7.6, 7.18, 7.22, 7.18, 7.25, 7.8, 7.35, 7.47, 6.72, 6.2, 5.82, 6.1, 6.4, 6.38, 6.32, 6.2, 6.47, 6.57, 6.5, 6.82, 7.22, 7.65, 8.5, 8.85, 8.18, 7.75, 7.98, 8.07, 7.93, 8.0, 8.4, 8.38, 7.75, 7.12, 6.72, 6.53, 6.27, 6.43, 6.57, 7.12, 7.18, 6.53, 6.7, 6.6, 6.9, 7.43, 7.3, 7.5, 8.33, 9.07, 9.5, 10.18, 10.2, 10.18, 10.2, 10.35, 9.68, 9.4, 9.93, 10.43, 10.38, 10.55, 10.43, 10.45, 10.7, 11.1, 11.57, 11.55, 11.3, 11.43, 11.62, 11.2, 11.32, 11.2, 11.15, 11.18, 11.8, 11.73, 11.75, 12.07, 12.6, 13.0, 13.35, 12.65, 11.93, 11.6, 11.23, 11.25, 11.68, 11.73, 12.45, 13.38, 14.02, 14.38, 15.0, 14.55, 14.18, 13.93, 14.65, 15.38, 15.32, 15.18, 15.6, 15.82, 15.95, 16.4, 16.88, 16.52, 14.77, 13.9, 13.85, 14.3, 14.88, 14.6, 14.73, 15.5, 16.8, 17.42, 16.92, 17.15, 17.05, 17.4, 17.42, 17.17, 17.05, 17.27, 17.52, 18.33, 18.77, 19.27, 19.62, 18.67, 17.7, 17.02, 17.12, 17.33, 17.45, 17.17, 16.98, 16.8, 16.5, 16.7, 16.65, 16.7, 16.52, 16.58, 16.95, 17.55, 16.83, 17.2, 17.33, 18.0, 18.3, 18.15, 17.55, 17.17, 17.3, 17.62, 17.58, 17.73, 17.58, 17.55, 17.35, 17.33, 18.05, 18.85, 18.25, 17.67, 17.35, 17.73, 18.3, 19.08, 19.62, 20.15, 20.33, 20.45, 21.0, 20.95, 20.95, 20.95, 20.9, 20.38, 19.58, 19.1, 19.62, 19.95, 19.55, 18.73, 18.4, 17.75, 17.52, 17.15, 17.58, 17.15, 16.88, 16.98, 16.8, 16.52, 16.62, 17.12, 17.17, 16.62, 16.4, 16.67, 17.05, 16.65, 16.4, 16.12, 16.12, 16.42, 17.0, 17.5, 16.9, 16.1, 15.85, 15.77, 15.62, 15.6, 15.67, 15.82, 15.4, 14.93, 15.15, 15.38, 15.35, 15.57, 15.68, 14.85, 14.85, 14.52, 14.18, 14.18, 14.18, 14.18, 14.25, 13.65, 12.95, 12.82, 12.9, 12.75, 12.57, 12.45, 12.32, 12.02, 12.12, 11.98, 12.55, 13.2, 12.82, 12.95, 13.2, 12.65, 12.27, 12.18, 11.9, 12.32, 12.95, 12.83, 13.55, 13.1, 11.95, 10.98, 11.05, 10.73, 10.32, 10.73, 11.32, 11.9, 11.8, 11.15, 11.27, 11.62, 11.5, 11.43, 11.68, 11.55, 10.68, 10.05, 10.48, 9.93, 9.47, 9.48, 9.6, 9.92, 9.5, 9.1, 7.82, 8.02, 8.62, 8.6, 8.38, 7.5, 6.75, 6.93, 7.0, 6.7, 6.47, 6.12, 6.53, 7.3, 7.7, 8.38, 8.3, 8.38, 8.32, 8.1, 8.25, 8.07, 8.73, 9.2, 9.23, 8.07, 7.22, 7.2, 7.0, 6.88, 6.72, 6.55, 6.05, 6.18, 6.4, 6.18, 5.93, 5.9, 5.8, 5.75, 4.97, 4.28, 4.7, 5.6, 5.3, 5.12, 4.65, 4.53, 4.45, 4.65, 5.22, 6.03, 6.33, 6.05, 5.47, 5.3, 4.9, 4.88, 5.03, 5.53, 5.95, 5.95, 4.68, 4.1, 4.45, 5.53, 6.15, 6.25, 6.4, 5.4, 4.53, 4.07, 3.75, 3.47, 3.15, 2.85, 2.58, 2.43, 2.55, 4.18, 5.35, 5.55, 6.0, 6.65, 6.62, 7.15, 7.82, 7.97, 8.03, 7.2, 6.65, 6.57, 6.45, 6.32, 6.57, 6.18, 5.3, 4.88, 5.25, 5.97, 5.97, 6.43, 6.68, 6.65, 6.45, 6.75, 6.88, 6.9, 6.68, 6.57, 6.4, 6.85, 6.7, 6.88, 6.82, 7.15, 7.4, 7.62, 7.4, 7.88, 8.15, 8.52, 9.25, 8.52, 8.07, 8.18, 8.02, 7.4, 6.88, 6.8, 7.1, 7.6, 7.2, 6.88, 6.72, 6.72, 6.93, 7.03, 7.38, 7.73, 8.35, 8.65, 8.88, 8.57, 8.33, 8.45, 8.45, 8.48, 8.7, 8.93, 9.07, 8.77, 8.93, 8.93, 8.73, 9.05, 8.85, 8.88, 8.8, 8.73, 9.68, 11.3, 11.5, 11.6, 11.35, 11.77, 11.88, 11.83, 11.9, 11.85, 11.88, 11.93, 12.12, 11.95, 11.8, 12.12, 12.35, 12.1, 12.07, 12.02, 12.77, 13.2, 13.85, 14.05, 14.43, 15.15, 15.5, 15.6, 15.23, 14.95, 15.18, 15.75, 15.93, 16.17, 16.45, 15.9, 16.12, 16.65, 16.62, 17.45, 18.05, 18.12, 17.42, 17.1, 16.33, 16.15, 16.2, 16.1, 16.28, 16.52, 16.4, 16.62, 16.08, 15.62, 15.6, 16.08, 16.48, 16.65, 16.5, 16.52, 16.73, 16.85, 17.08, 17.12, 17.22, 17.4, 17.27, 17.33, 16.98, 17.52, 18.3, 18.5, 18.2, 18.45, 18.77, 18.33, 17.9, 18.1, 18.42, 18.27, 18.02, 17.75, 17.52, 17.52, 17.52, 17.48, 17.22, 17.08, 17.73, 18.2, 17.95, 17.85, 17.67, 17.8, 17.92, 18.33, 18.45, 18.1, 18.1, 18.28, 17.23, 17.15, 17.33, 17.33, 17.75, 18.92, 19.02, 18.65, 18.33, 18.4, 17.92, 17.77, 17.6, 17.62, 17.65, 17.67, 17.75, 17.6, 17.62, 17.38, 17.25, 17.58, 17.77, 17.83, 18.1, 18.12, 18.08, 17.48, 17.25, 17.58, 17.38, 17.2, 17.0, 16.92, 16.7, 16.0, 15.85, 15.8, 16.1, 16.52, 16.52, 16.65, 16.12, 15.75, 15.02, 14.98, 15.1, 15.1, 14.9, 14.7, 14.07, 14.2, 14.07, 13.8, 13.4, 13.68, 13.7, 13.18, 13.48, 13.88, 13.32, 13.27, 13.17, 13.52, 14.1, 13.55, 12.7, 12.07, 11.75, 11.57, 12.15, 12.7, 12.62, 12.32, 12.48, 12.45, 11.88, 11.5, 11.12, 11.1, 10.55, 10.68, 11.05, 10.77, 10.5, 10.73, 10.95, 11.02, 10.62, 11.1, 11.05, 10.52, 10.27, 10.15, 10.68, 11.0, 10.93, 10.0, 9.27, 9.4, 9.43, 9.07, 7.62, 7.03, 6.93, 6.97, 8.05, 7.62, 6.85, 6.97, 7.15, 7.22, 6.28, 6.38, 6.38, 6.45, 6.35, 6.72, 7.7, 8.12, 8.48, 8.48, 8.18, 8.42, 8.45, 8.38, 7.18, 5.78, 5.17, 6.05, 6.55, 5.28, 5.07, 6.25, 7.18, 7.85, 8.73, 9.02, 9.23, 9.27, 8.98, 7.95, 7.25, 6.88, 6.45, 5.57, 5.5, 5.38, 5.6, 6.12, 6.35, 6.55, 6.32, 6.5, 6.43, 6.25, 6.1, 5.65, 5.9, 6.4, 6.57, 6.47, 6.22, 6.35, 6.45, 6.72, 6.93, 6.4, 6.28, 6.72, 7.12, 7.28, 6.88, 6.95, 6.43, 6.7, 7.35, 7.7, 7.35, 6.3, 5.82, 6.43, 6.85, 7.35, 8.3, 7.75, 7.35, 6.9, 7.1, 6.7, 6.78, 6.95, 6.65, 6.2, 5.75, 5.55, 5.5, 5.9, 5.68, 5.45, 5.28, 5.18, 4.93, 4.85, 4.88, 5.05, 5.6, 6.25, 6.98, 7.53, 7.38, 7.15, 7.62, 7.2, 7.32, 7.2, 6.72, 7.2, 7.5, 7.62, 7.57, 7.68, 8.25, 8.35, 8.55, 8.45, 8.07, 7.2, 6.53, 6.25, 6.47, 7.5, 7.93, 8.12, 7.78, 7.65, 7.43, 7.62, 8.18, 9.05, 9.5, 9.7, 9.73, 9.48, 9.52, 9.62, 9.82, 9.9, 10.18, 10.3, 10.38, 10.18, 10.0, 9.88, 9.77, 9.98, 9.8, 9.75, 10.1, 10.27, 10.43, 10.68, 11.15, 11.65, 11.85, 11.73, 12.25, 12.52, 12.77, 12.6, 12.65, 12.93, 13.35, 13.48, 13.95, 15.48, 15.95, 15.65, 14.93, 15.55, 15.85, 15.0, 14.85, 15.1, 14.85, 14.48, 14.05, 13.98, 14.15, 14.32, 14.7, 14.98, 15.67, 16.75, 16.17, 16.1, 16.0, 16.1, 16.3, 16.35, 16.27, 15.88, 15.98, 16.27, 16.55, 17.17, 17.03, 16.42, 16.3, 16.5, 16.95, 18.27, 18.1, 17.98, 17.7, 17.4, 17.75, 18.02, 18.05, 17.83, 17.95, 17.73, 17.55, 17.27, 17.38, 17.12, 17.45, 17.17, 17.3, 17.5, 18.15, 18.15, 17.8, 17.73, 17.62, 17.92, 18.52, 19.05, 19.15, 18.55, 18.45, 18.7, 18.98, 18.67, 18.08, 17.75, 18.0, 18.4, 18.52, 18.4, 18.33, 18.83, 19.3, 19.25, 18.48, 18.4, 18.33, 18.17, 18.6, 18.95, 19.25, 19.33, 19.38, 19.75, 19.48, 19.38, 19.58, 19.3, 19.27, 18.8, 18.83, 19.1, 19.4, 19.73, 19.8, 19.1, 18.55, 18.25, 18.3, 18.25, 18.15, 18.12, 18.25, 18.33, 18.35, 18.7, 18.73, 18.4, 17.95, 17.8, 17.67, 17.55, 17.85, 17.83, 17.48, 17.02, 16.5, 16.08, 15.45, 15.6, 15.43, 15.12, 14.98, 15.38, 14.93, 14.77, 14.23, 13.88, 13.62, 13.43, 13.32, 13.88, 13.65, 13.77, 14.32, 14.3, 13.68, 13.82, 13.05, 12.68, 12.35, 12.3, 12.65, 13.0, 13.18, 13.38, 13.77, 13.85, 12.62, 12.25, 12.9, 13.32, 13.1, 13.57, 13.52, 13.23, 13.48, 13.55, 13.68, 13.75, 13.38, 13.27, 13.05, 12.65, 12.52, 11.58, 11.7, 11.77, 12.07, 12.02, 11.85, 11.85, 10.9, 10.23, 9.45, 9.7, 10.7, 10.65, 10.23, 9.73, 7.68, 6.82, 7.82, 8.45, 8.73, 8.9, 8.25, 8.32, 8.55, 9.12, 9.1, 8.1, 7.43, 7.07, 6.5, 6.57, 7.1, 6.82, 6.65, 6.07, 5.45, 5.47, 5.32, 4.43, 3.8, 3.22, 2.9, 2.8, 2.75, 2.7, 4.15, 6.07, 6.65, 7.25, 7.68, 7.4, 7.68, 6.6, 6.82, 7.7, 7.25, 8.23, 9.18, 9.2, 8.05, 8.2, 8.85, 9.02, 8.98, 8.65, 8.0, 7.6, 8.0, 8.35, 8.57, 7.93, 7.82, 6.98, 6.18, 4.9, 4.55, 4.38, 4.0, 3.75, 3.9, 4.33, 4.25, 4.12, 4.83, 4.8, 4.85, 5.28, 5.3, 5.38, 6.03, 6.7, 6.88, 7.1, 6.28, 5.28, 4.18, 3.5, 3.3, 4.53, 5.75, 6.15, 5.57, 5.0, 5.38, 6.8, 7.53, 7.95, 7.48, 7.65, 7.62, 7.75, 6.93, 6.4, 5.43, 4.75, 4.65, 4.38, 4.15, 4.7, 5.6, 5.88, 5.8, 5.8, 5.03, 4.78, 4.8, 4.43, 4.68, 6.38, 7.28, 6.53, 6.53, 6.95, 8.2, 8.65, 8.5, 8.62, 8.77, 9.18, 9.7, 9.42, 9.07, 8.25, 7.38, 7.68, 8.88, 9.43, 9.65, 9.38, 8.55, 7.88, 7.92, 7.97, 8.27, 8.98, 9.75, 9.8, 9.7, 9.9, 9.7, 9.35, 9.75, 9.77, 9.9, 10.02, 10.02, 9.88, 9.85, 10.3, 10.52, 10.82, 10.3, 9.8, 9.57, 10.23, 10.65, 10.93, 11.35, 11.52, 11.18, 11.48, 12.43, 12.65, 13.68, 13.98, 14.15, 13.85, 13.8, 13.9, 14.35, 14.93, 14.85, 13.98, 13.88, 13.88, 14.1, 14.57, 15.03, 15.85, 16.1, 14.95, 14.6, 14.57, 14.88, 15.48, 15.43, 15.18, 15.27, 15.68, 15.2, 15.3, 15.7, 15.98, 16.12, 16.45, 16.67, 17.08, 17.75, 18.2, 18.6, 18.58, 18.42, 18.33, 18.4, 18.45, 18.77, 18.58, 19.17, 19.48, 19.2, 18.7, 19.4, 20.1, 20.33, 19.45, 18.92, 18.75, 18.35, 18.25, 17.77, 17.73, 17.23, 17.02, 16.7, 17.45, 18.75, 19.83, 19.62, 19.9, 19.98, 19.27, 18.8, 19.23, 18.9, 18.75, 18.42, 18.85, 18.8, 18.73, 18.15, 17.67, 18.08, 17.6, 17.62, 17.67, 18.12, 18.52, 18.2, 18.0, 17.85, 17.75, 18.08, 18.0, 17.92, 17.48, 17.2, 17.35, 17.58, 17.92, 18.55, 19.22, 18.98, 18.65, 19.1, 18.77, 18.98, 19.12, 20.0, 19.78, 20.08, 19.9, 19.52, 19.67, 19.65, 19.1, 18.3, 17.88, 17.92, 17.3, 17.05, 17.2, 17.62, 17.65, 17.85, 18.33, 18.95, 19.23, 19.3, 19.58, 19.85, 20.2, 20.05, 19.67, 18.55, 17.98, 17.85, 18.23, 18.0, 17.67, 17.42, 17.35, 16.92, 16.65, 16.17, 15.98, 16.6, 16.62, 17.05, 16.92, 16.17, 16.52, 16.83, 17.1, 16.15, 15.93, 15.88, 16.25, 16.62, 16.4, 16.62, 16.55, 16.5, 16.48, 16.2, 14.57, 13.93, 13.5, 12.95, 13.43, 14.02, 13.6, 13.8, 13.3, 13.48, 12.92, 12.68, 12.88, 12.7, 12.85, 12.62, 12.62, 12.7, 12.55, 12.0, 11.65, 11.6, 11.55, 11.32, 11.23, 11.27, 10.93, 10.42, 10.2, 9.98, 10.68, 11.05, 10.93, 10.2, 9.5, 10.12, 10.82, 10.52, 9.73, 9.62, 9.98, 9.1, 8.45, 8.48, 7.85, 7.22, 6.9, 6.68, 5.98, 5.85, 5.83, 5.83, 6.05, 5.6, 5.6, 6.47, 7.22, 7.8, 7.9, 8.2, 8.25, 8.05, 8.1, 8.5, 8.35, 8.42, 8.32, 8.65, 8.5, 8.6, 9.0, 9.65, 9.7, 8.57, 8.57, 8.93, 8.65, 8.52, 8.42, 8.32, 8.92, 8.8, 8.5, 8.1, 7.15, 5.8, 4.82, 4.03, 3.47, 3.17, 3.38, 4.25, 4.43, 4.12, 3.88, 3.55, 3.2, 3.1, 3.07, 4.32, 5.22, 6.12, 6.18, 6.65, 6.05, 5.18, 4.93, 5.57, 6.05, 6.2, 6.1, 6.82, 7.47, 7.83, 7.97, 8.05, 7.05, 6.85, 7.78, 8.23, 8.02, 7.4, 7.68, 8.73, 9.12, 9.15, 9.27, 9.05, 8.68, 8.77, 9.0, 8.8, 7.97, 7.7, 7.68, 7.2, 6.97, 7.47, 7.9, 7.95, 8.25, 8.82, 8.23, 8.05, 8.07, 7.53, 7.32, 7.88, 8.1, 8.18, 8.73, 9.1, 9.75, 9.88, 9.32, 9.78, 10.07, 10.25, 10.6, 10.43, 10.05, 9.38, 9.0, 9.07, 9.4, 9.52, 9.43, 9.82, 9.77, 10.15, 9.95, 10.1, 10.43, 10.75, 11.12, 11.95, 12.38, 12.55, 12.07, 12.05, 12.3, 12.82, 13.1, 12.43, 11.32, 10.95, 10.73, 10.93, 10.9, 10.75, 10.5, 9.98, 10.35, 10.15, 10.25, 10.85, 11.52, 11.3, 12.43, 13.82, 15.18, 15.18, 13.68, 13.95, 14.0, 14.57, 14.7, 14.4, 14.7, 15.3, 15.77, 16.55, 16.8, 16.83, 16.75, 16.83, 16.72, 17.0, 16.8, 16.65, 16.77, 16.55, 16.33, 16.3, 16.33, 16.33, 16.25, 16.4, 16.43, 16.67, 16.62, 16.3, 16.27, 16.55, 16.92, 16.42, 16.05, 16.02, 15.83, 15.73, 15.25, 15.52, 15.52, 15.82, 15.88, 15.82, 15.98, 16.35, 16.33, 16.35, 16.6, 16.7, 16.9, 17.33, 18.02, 18.7, 19.3, 19.3, 18.65, 18.83, 18.55, 18.23, 17.58, 17.47, 17.45, 17.33, 17.15, 17.25, 18.17, 18.9, 18.83, 18.3, 17.65, 17.62, 18.1, 18.8, 18.67, 19.02, 19.67, 20.17, 20.5, 19.88, 20.07, 19.9, 19.58, 19.73, 19.6, 19.67, 19.65, 20.03, 20.38, 20.3, 20.15, 20.3, 19.98, 19.83, 19.95, 20.17, 19.88, 20.0, 19.65, 20.12, 20.52, 21.2, 21.38, 20.85, 20.55, 20.23, 20.12, 19.5, 19.45, 19.12, 18.62, 18.7, 18.7, 18.73, 18.27, 18.23, 18.58, 19.17, 19.05, 18.88, 19.0, 19.67, 19.98, 19.73, 19.48, 19.73, 19.92, 20.15, 19.38, 18.55, 16.95, 16.42, 16.0, 15.6, 15.6, 16.0, 16.17, 16.42, 16.83, 16.98, 16.9, 16.9, 17.2, 16.85, 16.67, 16.62, 16.25, 15.35, 14.93, 14.57, 14.7, 14.7, 14.2, 13.52, 13.1, 12.88, 13.52, 14.38, 14.68, 14.62, 14.05, 13.15, 13.1, 12.95, 12.32, 12.73, 13.82, 14.55, 14.73, 14.68, 14.73, 14.32, 13.77, 13.07, 13.02, 13.32, 13.75, 13.23, 13.52, 13.88, 14.07, 13.42, 13.27, 12.23, 11.2, 10.75, 10.93, 11.35, 10.88, 10.57, 10.85, 11.18, 11.1, 10.5, 10.9, 10.9, 10.88, 10.73, 9.95, 9.32, 8.45, 7.75, 7.12, 6.95, 7.75, 9.23, 9.25, 8.75, 8.7, 8.12, 7.28, 7.47, 8.05, 8.52, 7.72, 7.53, 8.08, 8.18, 8.25, 8.07, 7.88, 7.6, 7.35, 7.25, 6.82, 7.0, 7.98, 8.23, 8.35, 9.02, 8.1, 7.88, 8.07, 7.4, 7.05, 7.55, 8.2, 8.07, 7.35, 6.97, 7.15, 7.4, 7.32, 7.2, 6.28, 5.82, 5.55, 6.5, 6.53, 5.88, 5.45, 5.15, 4.68, 4.2, 4.22, 3.9, 4.75, 5.72, 5.57, 5.15, 4.8, 4.62, 4.5, 4.32, 4.78, 5.35, 5.93, 5.68, 5.78, 6.1, 6.47, 6.5, 5.8, 4.82, 4.1, 3.5, 3.22, 3.75, 4.72, 4.72, 4.6, 4.7, 4.6, 4.4, 4.77, 4.95, 4.3, 4.03, 4.05, 3.55, 3.05, 2.78, 2.85, 4.68, 6.2, 6.77, 7.25, 7.43, 7.47, 7.22, 7.05, 6.62, 6.0, 5.5, 5.22, 5.18, 5.35, 5.8, 6.2, 6.43, 6.5, 6.8, 6.55, 6.22, 5.8, 5.6, 5.57, 6.1, 5.95, 5.65, 6.23, 6.95, 7.97, 8.52, 8.85, 8.85, 8.88, 8.4, 8.15, 8.18, 8.45, 8.73, 8.62, 8.65, 9.07, 9.35, 9.65, 9.38, 9.0, 9.12, 9.27, 9.55, 9.68, 10.0, 10.7, 10.9, 11.18, 11.25, 10.92, 10.9, 10.85, 10.82, 11.25, 11.38, 11.57, 11.62, 11.62, 11.68, 11.88, 12.05, 12.2, 12.73, 13.27, 13.8, 13.77, 13.35, 12.55, 12.02, 12.07, 12.25, 12.27, 12.55, 13.07, 13.65, 13.65, 13.62, 13.7, 13.55, 13.88, 14.3, 14.1, 14.57, 14.73, 14.45, 14.02, 13.3, 13.68, 14.2, 14.27, 14.43, 14.4, 14.75, 15.65, 16.67, 16.12, 15.88, 15.75, 15.43, 15.5, 15.43, 15.4, 15.07, 15.38, 15.25, 16.3, 17.38, 18.67, 18.17, 17.8, 17.8, 18.02, 18.23, 18.23, 18.45, 18.77, 18.5, 18.02, 17.85, 18.55, 18.2, 18.35, 18.8, 19.55, 20.17, 20.92, 20.45, 19.58, 19.2, 18.73, 18.42, 17.98, 18.17, 18.73, 18.98, 19.2, 19.58, 19.48, 19.35, 18.95, 18.67, 18.95, 19.42, 19.7, 19.45, 19.3, 19.15, 19.25, 19.38, 19.17, 19.23, 18.73, 18.75, 18.22, 17.8, 17.45, 17.35, 17.12, 17.33, 17.48, 17.08, 17.17, 17.52, 17.8, 17.85, 18.12, 18.8, 19.12, 19.23, 18.8, 18.4, 17.3, 16.92, 17.23, 17.33, 16.83, 16.48, 16.17, 15.82, 15.7, 15.92, 16.3, 16.53, 16.12, 15.82, 16.08, 16.38, 16.77, 16.77, 17.22, 17.33, 17.12, 17.3, 17.83, 17.65, 16.45, 16.83, 16.4, 16.17, 15.93, 16.42, 16.12, 15.85, 16.17, 16.75, 17.0, 17.0, 15.85, 14.95, 14.7, 14.3, 14.32, 14.52, 14.57, 14.4, 14.32, 14.1, 13.93, 13.95, 13.98, 13.75, 13.7, 14.27, 14.8, 14.73, 14.62, 14.85, 14.95, 15.15, 15.07, 14.95, 14.65, 14.45, 14.23, 13.52, 13.65, 14.12, 14.12, 13.88, 13.28, 12.73, 12.25, 11.82, 11.52, 11.88, 12.1, 11.7, 11.73, 11.77, 11.57, 11.8, 12.12, 11.98, 11.58, 11.25, 11.57, 11.27, 11.32, 11.3, 11.25, 11.68, 11.65, 11.23, 10.2, 9.38, 9.18, 8.75, 8.25, 7.47, 7.03, 7.05, 7.45, 7.53, 7.22, 7.9, 8.62, 8.8, 8.27, 8.05, 8.27, 8.27, 8.23, 8.07, 8.6, 9.25, 9.77, 10.07, 9.88, 9.43, 9.4, 8.6, 8.68, 8.5, 8.57, 8.65, 9.15, 8.7, 8.48, 8.3, 7.72, 7.07, 5.9, 4.95, 4.4, 4.32, 4.4, 4.65, 4.4, 4.82, 4.97, 4.65, 4.6, 4.55, 4.5, 4.43, 4.3, 3.97, 3.78, 3.55, 3.4, 4.28, 5.3, 5.45, 6.07, 6.2, 5.7, 5.15, 4.85, 4.7, 4.6, 4.53, 4.78, 4.38, 3.8, 3.88, 4.32, 4.43, 4.17, 4.45, 5.07, 5.3, 4.65, 4.47, 4.82, 4.65, 5.22, 5.43, 5.55, 5.22, 5.75, 6.53, 6.57, 5.62, 4.55, 4.2, 4.7, 4.85, 4.5, 4.6, 5.47, 6.62, 7.22, 7.53, 8.1, 8.27, 8.4, 8.75, 8.18, 8.15, 7.83, 7.83, 7.72, 7.72, 8.2, 8.6, 8.43, 8.45, 8.62, 8.3, 8.42, 8.2, 7.65, 7.8, 8.05, 8.55, 8.3, 8.4, 8.27, 8.27, 8.52, 8.77, 8.98, 8.35, 7.7, 8.02, 8.02, 8.12, 8.0, 8.4, 8.88, 8.57, 9.15, 9.88, 9.52, 9.3, 9.38, 9.93, 10.52, 10.07, 9.98, 9.95, 9.82, 10.07, 10.73, 11.05, 11.38, 11.02, 10.77, 10.85, 10.73, 10.88, 11.12, 11.35, 11.52, 11.52, 11.68, 11.68, 11.75, 11.95, 12.23, 13.05, 13.25, 13.32, 12.77, 12.43, 12.57, 12.6, 12.73, 12.65, 12.0, 12.0, 12.12, 11.8, 11.7, 11.67, 12.23, 12.68, 13.05, 13.77, 14.57, 15.5, 16.25, 16.15, 16.42, 16.8, 16.35, 16.67, 16.9, 17.05, 16.67, 16.77, 16.48, 16.35, 16.2, 16.38, 16.2, 16.23, 16.0, 15.65, 15.4, 15.55, 16.1, 15.77, 15.82, 16.27, 17.35, 18.0, 19.4, 20.8) # fmt: skip # noqa: FURB152
62def generate_example_data(
63 *,
64 date_start: str = "2020-01-01",
65 date_end: str = "2021-12-31",
66 date_freq: str = "D",
67 flow_mean: float = 100.0, # m3/day
68 flow_amplitude: float = 30.0, # m3/day
69 flow_noise: float = 10.0, # m3/day
70 cin_method: str = "synthetic",
71 cin_mean: float = 12.0,
72 cin_amplitude: float = 8.0,
73 measurement_noise: float = 1.0,
74 aquifer_pore_volumes: npt.ArrayLike | None = None,
75 aquifer_pore_volume_gamma_mean: float | None = None,
76 aquifer_pore_volume_gamma_std: float | None = None,
77 aquifer_pore_volume_gamma_loc: float | None = None,
78 aquifer_pore_volume_gamma_nbins: int | None = None,
79 retardation_factor: float = 1.0,
80 molecular_diffusivity: float | None = None,
81 longitudinal_dispersivity: float | None = None,
82 streamline_length: float | None = None,
83 rng: np.random.Generator | int | None = None,
84) -> tuple[pd.DataFrame, pd.DatetimeIndex]:
85 """
86 Generate synthetic concentration/temperature and flow data for groundwater transport.
88 Creates a synthetic dataset with seasonal flow patterns, input concentration (cin),
89 and output concentration (cout) computed via gamma-distributed pore volume transport.
90 When ``molecular_diffusivity``, ``longitudinal_dispersivity``, and ``streamline_length``
91 are provided, the diffusion module is used instead of pure advection.
93 Parameters
94 ----------
95 date_start, date_end : str
96 Start and end dates for the generated time series (YYYY-MM-DD).
97 date_freq : str, default "D"
98 Frequency string for pandas.date_range.
99 flow_mean : float, default 100.0
100 Mean flow rate [m³/day].
101 flow_amplitude : float, default 30.0
102 Seasonal amplitude of flow rate [m³/day].
103 flow_noise : float, default 10.0
104 Random noise level for flow rate [m³/day].
105 cin_method : str, default "synthetic"
106 Method for generating infiltration concentration. Options:
108 - ``"synthetic"``: Seasonal sinusoidal pattern defined by ``cin_mean`` and
109 ``cin_amplitude``. Measurement noise is applied.
110 - ``"constant"``: Constant value equal to ``cin_mean``. Measurement noise
111 is still applied.
112 - ``"soil_temperature"``: Real soil temperature data from KNMI station 260.
113 cin_mean : float, default 12.0
114 Mean value of infiltrating concentration.
115 cin_amplitude : float, default 8.0
116 Seasonal amplitude of infiltration concentration (only used for
117 ``"synthetic"`` method).
118 measurement_noise : float, default 1.0
119 Standard deviation of the Gaussian measurement noise applied
120 independently to ``cin`` and ``cout``. Because the two noise draws are
121 independent, applying the forward operator to ``df['cin']`` does not
122 exactly reproduce ``df['cout']`` when ``measurement_noise > 0``; the
123 underlying noiseless signals remain consistent.
124 aquifer_pore_volumes : array-like or None, default None
125 Discrete aquifer pore volumes [m³] representing the distribution of
126 residence times. When provided, the gamma distribution is bypassed and
127 none of the ``aquifer_pore_volume_gamma_*`` parameters may be passed.
128 When ``None``, the pore volume distribution is built from the gamma
129 parameters below.
130 aquifer_pore_volume_gamma_mean : float or None, default None
131 Mean pore volume of the aquifer gamma distribution [m³] (default 1000.0
132 when unset). Must be strictly greater than
133 ``aquifer_pore_volume_gamma_loc``. Mutually exclusive with
134 ``aquifer_pore_volumes``.
135 aquifer_pore_volume_gamma_std : float or None, default None
136 Standard deviation of aquifer pore volume gamma distribution [m³]
137 (default 200.0 when unset; invariant under the ``loc`` shift).
138 Mutually exclusive with ``aquifer_pore_volumes``.
139 aquifer_pore_volume_gamma_loc : float or None, default None
140 Location (minimum pore volume) of the aquifer gamma distribution [m³]
141 (default 0.0 when unset). Must satisfy ``0 <= loc < mean``. Mutually
142 exclusive with ``aquifer_pore_volumes``.
143 aquifer_pore_volume_gamma_nbins : int or None, default None
144 Number of bins to discretize the aquifer pore volume gamma distribution
145 (default 250 when unset). Mutually exclusive with
146 ``aquifer_pore_volumes``.
147 retardation_factor : float, default 1.0
148 Retardation factor for transport.
149 molecular_diffusivity : float or None, default None
150 Effective molecular diffusivity [m²/day]. When provided together with
151 ``longitudinal_dispersivity`` and ``streamline_length``, the diffusion
152 module is used instead of pure advection. For solutes, typically ~1e-5
153 m²/day (negligible). For heat, use thermal diffusivity ~0.01-0.1 m²/day.
154 longitudinal_dispersivity : float or None, default None
155 Longitudinal dispersivity [m]. Must be provided together with
156 ``molecular_diffusivity`` and ``streamline_length``.
157 streamline_length : float or None, default None
158 Travel distance along the streamline [m]. Must be provided together
159 with ``molecular_diffusivity`` and ``longitudinal_dispersivity``.
160 rng : numpy.random.Generator, int, or None, default None
161 Source of randomness for the synthetic flow noise, spill events, and
162 measurement noise. Accepted in any form supported by
163 :func:`numpy.random.default_rng`. Pass an integer (or
164 :class:`numpy.random.Generator`) for reproducible output; ``None``
165 draws fresh entropy each call.
167 Returns
168 -------
169 tuple
170 A tuple containing:
172 - pandas.DataFrame: DataFrame with columns ``'flow'``, ``'cin'``,
173 ``'cout'`` and metadata attributes for the aquifer parameters.
174 - pandas.DatetimeIndex: Time edges (tedges) used for the flow
175 calculations.
177 Raises
178 ------
179 ValueError
180 If ``cin_method`` is not one of the supported methods, if only some
181 of the diffusion parameters are provided, or if ``aquifer_pore_volumes``
182 is passed together with any ``aquifer_pore_volume_gamma_*`` parameter.
184 See Also
185 --------
186 generate_temperature_example_data : Wrapper with thermal transport defaults.
187 generate_ec_example_data : Wrapper with EC transport defaults.
188 """
189 rng = np.random.default_rng(rng)
191 dates = pd.date_range(start=date_start, end=date_end, freq=date_freq).tz_localize("UTC")
192 # Fractional elapsed days so the seasonal sinusoid resolves sub-daily sampling (integer .days
193 # would stair-step, holding the seasonal constant within each calendar day).
194 days = ((dates - dates[0]) / pd.Timedelta(days=1)).to_numpy()
196 # Generate flow data with seasonal pattern (higher in winter)
197 seasonal_flow = flow_mean + flow_amplitude * np.sin(2 * np.pi * days / 365 + np.pi)
198 flow = seasonal_flow + rng.normal(0, flow_noise, len(dates))
200 min_days_for_spills = 60
201 if len(dates) > min_days_for_spills: # Only add spills for longer time series
202 n_spills = int(rng.integers(6, 16))
203 for _ in range(n_spills):
204 spill_start = int(rng.integers(0, len(dates) - 30))
205 spill_duration = int(rng.integers(15, 45))
206 spill_magnitude = float(rng.uniform(2.0, 5.0))
208 flow[spill_start : spill_start + spill_duration] /= spill_magnitude
210 # Enforce a positive flow floor after spills so residence times remain finite.
211 flow = np.maximum(flow, 5.0)
213 # Generate infiltration concentration. nonoise is needed to compute cout.
214 if cin_method == "synthetic":
215 # Seasonal pattern with noise
216 cin_nonoise = cin_mean + cin_amplitude * np.sin(2 * np.pi * days / 365)
217 cin_values = cin_nonoise + rng.normal(0, measurement_noise, len(dates))
218 elif cin_method == "constant":
219 # Constant value
220 cin_nonoise = np.full(len(dates), cin_mean)
221 cin_values = cin_nonoise + rng.normal(0, measurement_noise, len(dates))
222 elif cin_method == "soil_temperature":
223 # Use the inline KNMI soil temperature data (already includes measurement noise).
224 soil_temperature = pd.Series(
225 _SOIL_TEMPERATURE_TB3,
226 index=pd.date_range(
227 start=_SOIL_TEMPERATURE_TB3_START, periods=len(_SOIL_TEMPERATURE_TB3), freq="D", tz="UTC"
228 ),
229 name="TB3",
230 )
231 cin_nonoise = cin_values = soil_temperature.resample(date_freq).mean()[dates].values
232 else:
233 msg = f"Unknown cin_method: {cin_method}"
234 raise ValueError(msg)
236 tedges = compute_time_edges(tedges=None, tstart=None, tend=dates, number_of_bins=len(dates))
238 # Validate pore volume parameterization: either discrete volumes or gamma parameters, not both.
239 gamma_set_by_user = [
240 name
241 for name, value in {
242 "aquifer_pore_volume_gamma_mean": aquifer_pore_volume_gamma_mean,
243 "aquifer_pore_volume_gamma_std": aquifer_pore_volume_gamma_std,
244 "aquifer_pore_volume_gamma_loc": aquifer_pore_volume_gamma_loc,
245 "aquifer_pore_volume_gamma_nbins": aquifer_pore_volume_gamma_nbins,
246 }.items()
247 if value is not None
248 ]
249 if aquifer_pore_volumes is not None and gamma_set_by_user:
250 msg = (
251 "aquifer_pore_volumes is mutually exclusive with the aquifer_pore_volume_gamma_* "
252 f"parameters; got both aquifer_pore_volumes and {gamma_set_by_user}."
253 )
254 raise ValueError(msg)
256 # Validate diffusion parameterization: all three parameters provided or none.
257 diffusion_provided = (molecular_diffusivity, longitudinal_dispersivity, streamline_length)
258 n_diffusion = sum(1 for p in diffusion_provided if p is not None)
259 if 0 < n_diffusion < len(diffusion_provided):
260 msg = "molecular_diffusivity, longitudinal_dispersivity, and streamline_length must all be provided together."
261 raise ValueError(msg)
262 # Validation above forbids partial-set states, so this conjunction is equivalent to any single check;
263 # writing it in full lets the type checker narrow all three params to non-None inside the branches below.
264 use_diffusion = (
265 molecular_diffusivity is not None and longitudinal_dispersivity is not None and streamline_length is not None
266 )
268 # Fill in gamma defaults so downstream callers see concrete values (not used when
269 # aquifer_pore_volumes is supplied, but kept in scope for the attrs block below).
270 gamma_mean = aquifer_pore_volume_gamma_mean if aquifer_pore_volume_gamma_mean is not None else _DEFAULT_GAMMA_MEAN
271 gamma_std = aquifer_pore_volume_gamma_std if aquifer_pore_volume_gamma_std is not None else _DEFAULT_GAMMA_STD
272 gamma_loc = aquifer_pore_volume_gamma_loc if aquifer_pore_volume_gamma_loc is not None else _DEFAULT_GAMMA_LOC
273 gamma_nbins = (
274 aquifer_pore_volume_gamma_nbins if aquifer_pore_volume_gamma_nbins is not None else _DEFAULT_GAMMA_NBINS
275 )
277 # Compute cout. Branch on pore volume parameterization, then on diffusion.
278 if aquifer_pore_volumes is not None:
279 aquifer_pore_volumes_array = np.asarray(aquifer_pore_volumes, dtype=float)
280 if use_diffusion:
281 cout_values = diffusion_infiltration_to_extraction(
282 cin=cin_nonoise,
283 flow=flow,
284 tedges=tedges,
285 cout_tedges=tedges,
286 aquifer_pore_volumes=aquifer_pore_volumes_array,
287 streamline_length=streamline_length,
288 molecular_diffusivity=molecular_diffusivity,
289 longitudinal_dispersivity=longitudinal_dispersivity,
290 retardation_factor=retardation_factor,
291 )
292 else:
293 cout_values = infiltration_to_extraction(
294 cin=cin_nonoise,
295 flow=flow,
296 tedges=tedges,
297 cout_tedges=tedges,
298 aquifer_pore_volumes=aquifer_pore_volumes_array,
299 retardation_factor=retardation_factor,
300 )
301 elif use_diffusion:
302 cout_values = diffusion_gamma_infiltration_to_extraction(
303 cin=cin_nonoise,
304 flow=flow,
305 tedges=tedges,
306 cout_tedges=tedges,
307 mean=gamma_mean,
308 std=gamma_std,
309 loc=gamma_loc,
310 n_bins=gamma_nbins,
311 streamline_length=streamline_length,
312 molecular_diffusivity=molecular_diffusivity,
313 longitudinal_dispersivity=longitudinal_dispersivity,
314 retardation_factor=retardation_factor,
315 )
316 else:
317 cout_values = gamma_infiltration_to_extraction(
318 cin=cin_nonoise,
319 flow=flow,
320 tedges=tedges,
321 cout_tedges=tedges,
322 mean=gamma_mean,
323 std=gamma_std,
324 loc=gamma_loc,
325 n_bins=gamma_nbins,
326 retardation_factor=retardation_factor,
327 )
329 # Add some noise to represent measurement errors
330 cout_values += rng.normal(0, measurement_noise, len(dates))
332 df = pd.DataFrame(
333 data={"flow": flow, "cin": cin_values, "cout": cout_values},
334 index=dates,
335 )
336 df.attrs.update({
337 "description": "Example data for groundwater transport modeling",
338 "source": "Synthetic data generated by gwtransport.examples.generate_example_data",
339 "retardation_factor": retardation_factor,
340 "date_start": date_start,
341 "date_end": date_end,
342 "date_freq": date_freq,
343 "flow_mean": flow_mean,
344 "flow_amplitude": flow_amplitude,
345 "flow_noise": flow_noise,
346 "cin_method": cin_method,
347 "cin_mean": cin_mean,
348 "cin_amplitude": cin_amplitude,
349 "measurement_noise": measurement_noise,
350 })
351 if aquifer_pore_volumes is not None:
352 df.attrs["aquifer_pore_volume_parameterization"] = "discrete"
353 df.attrs["aquifer_pore_volumes"] = aquifer_pore_volumes_array
354 else:
355 alpha, beta = mean_std_loc_to_alpha_beta(mean=gamma_mean, std=gamma_std, loc=gamma_loc)
356 df.attrs.update({
357 "aquifer_pore_volume_parameterization": "gamma",
358 "aquifer_pore_volume_gamma_mean": gamma_mean,
359 "aquifer_pore_volume_gamma_std": gamma_std,
360 "aquifer_pore_volume_gamma_loc": gamma_loc,
361 "aquifer_pore_volume_gamma_alpha": alpha,
362 "aquifer_pore_volume_gamma_beta": beta,
363 "aquifer_pore_volume_gamma_nbins": gamma_nbins,
364 })
365 if molecular_diffusivity is not None:
366 df.attrs["molecular_diffusivity"] = molecular_diffusivity
367 df.attrs["longitudinal_dispersivity"] = longitudinal_dispersivity
368 df.attrs["streamline_length"] = streamline_length
370 return df, tedges
373def generate_temperature_example_data(
374 *,
375 date_start: str = "2020-01-01",
376 date_end: str = "2021-12-31",
377 date_freq: str = "D",
378 flow_mean: float = 100.0,
379 flow_amplitude: float = 30.0,
380 flow_noise: float = 10.0,
381 cin_method: str = "synthetic",
382 cin_mean: float = 12.0,
383 cin_amplitude: float = 8.0,
384 measurement_noise: float = 1.0,
385 aquifer_pore_volumes: npt.ArrayLike | None = None,
386 aquifer_pore_volume_gamma_mean: float | None = None,
387 aquifer_pore_volume_gamma_std: float | None = None,
388 aquifer_pore_volume_gamma_loc: float | None = None,
389 aquifer_pore_volume_gamma_nbins: int | None = None,
390 retardation_factor: float = 2.0,
391 molecular_diffusivity: float = 0.05,
392 longitudinal_dispersivity: float = 1.0,
393 streamline_length: float = 100.0,
394 rng: np.random.Generator | int | None = None,
395) -> tuple[pd.DataFrame, pd.DatetimeIndex]:
396 """
397 Generate synthetic temperature and flow data for groundwater transport examples.
399 Convenience wrapper around :func:`generate_example_data` with sensible
400 defaults for temperature transport: thermal retardation factor, thermal
401 diffusivity, longitudinal dispersivity, and streamline length.
403 Typical parameter values for temperature transport in various sand types:
405 +---------------------------------+------------+-------------+--------------------+
406 | Parameter | Fine sand | Medium sand | Coarse sand/gravel |
407 +=================================+============+=============+====================+
408 | retardation_factor R | 2.0--3.0 | 1.5--2.5 | 1.2--2.0 |
409 +---------------------------------+------------+-------------+--------------------+
410 | molecular_diffusivity (m²/day) | 0.03--0.06 | 0.05--0.08 | 0.08--0.12 |
411 +---------------------------------+------------+-------------+--------------------+
412 | longitudinal_dispersivity (m) | 0.1--1.0 | 0.5--5.0 | 1.0--10.0 |
413 +---------------------------------+------------+-------------+--------------------+
414 | streamline_length (m) | site-specific |
415 +---------------------------------+------------+-------------+--------------------+
417 Parameters
418 ----------
419 retardation_factor : float, default 2.0
420 Thermal retardation factor.
421 molecular_diffusivity : float, default 0.05
422 Thermal diffusivity [m²/day].
423 longitudinal_dispersivity : float, default 1.0
424 Longitudinal dispersivity [m].
425 streamline_length : float, default 100.0
426 Travel distance along the streamline [m].
428 Returns
429 -------
430 tuple
431 See :func:`generate_example_data`.
433 See Also
434 --------
435 generate_example_data : Generic version with full parameter control.
436 generate_ec_example_data : Wrapper with EC transport defaults.
438 Notes
439 -----
440 All other parameters are forwarded unchanged to :func:`generate_example_data`;
441 see that function for their descriptions.
442 """
443 return generate_example_data(
444 date_start=date_start,
445 date_end=date_end,
446 date_freq=date_freq,
447 flow_mean=flow_mean,
448 flow_amplitude=flow_amplitude,
449 flow_noise=flow_noise,
450 cin_method=cin_method,
451 cin_mean=cin_mean,
452 cin_amplitude=cin_amplitude,
453 measurement_noise=measurement_noise,
454 aquifer_pore_volumes=aquifer_pore_volumes,
455 aquifer_pore_volume_gamma_mean=aquifer_pore_volume_gamma_mean,
456 aquifer_pore_volume_gamma_std=aquifer_pore_volume_gamma_std,
457 aquifer_pore_volume_gamma_loc=aquifer_pore_volume_gamma_loc,
458 aquifer_pore_volume_gamma_nbins=aquifer_pore_volume_gamma_nbins,
459 retardation_factor=retardation_factor,
460 molecular_diffusivity=molecular_diffusivity,
461 longitudinal_dispersivity=longitudinal_dispersivity,
462 streamline_length=streamline_length,
463 rng=rng,
464 )
467def generate_ec_example_data(
468 *,
469 date_start: str = "2020-01-01",
470 date_end: str = "2021-12-31",
471 date_freq: str = "D",
472 flow_mean: float = 100.0,
473 flow_amplitude: float = 30.0,
474 flow_noise: float = 10.0,
475 cin_method: str = "synthetic",
476 cin_mean: float = 500.0,
477 cin_amplitude: float = 150.0,
478 measurement_noise: float = 10.0,
479 aquifer_pore_volumes: npt.ArrayLike | None = None,
480 aquifer_pore_volume_gamma_mean: float | None = None,
481 aquifer_pore_volume_gamma_std: float | None = None,
482 aquifer_pore_volume_gamma_loc: float | None = None,
483 aquifer_pore_volume_gamma_nbins: int | None = None,
484 retardation_factor: float = 1.0,
485 molecular_diffusivity: float = 5e-5,
486 longitudinal_dispersivity: float = 1.0,
487 streamline_length: float = 100.0,
488 rng: np.random.Generator | int | None = None,
489) -> tuple[pd.DataFrame, pd.DatetimeIndex]:
490 """
491 Generate synthetic electrical conductivity and flow data for groundwater transport examples.
493 Convenience wrapper around :func:`generate_example_data` with sensible
494 defaults for electrical conductivity (EC) transport. EC is a conservative
495 tracer: dissolved ions travel at water velocity without retardation.
497 Typical parameter values for EC (dissolved ion) transport in various sand
498 types. The molecular diffusivity represents effective ionic diffusion in
499 porous media (free-water D_0 reduced by porosity/tortuosity). It is
500 negligible compared to microdispersion at field scale.
502 +---------------------------------+----------------+----------------+--------------------+
503 | Parameter | Fine sand | Medium sand | Coarse sand/gravel |
504 +=================================+================+================+====================+
505 | retardation_factor R | 1.0 | 1.0 | 1.0 |
506 +---------------------------------+----------------+----------------+--------------------+
507 | molecular_diffusivity (m²/day) | 3e-5 -- 5e-5 | 4e-5 -- 8e-5 | 5e-5 -- 1e-4 |
508 +---------------------------------+----------------+----------------+--------------------+
509 | longitudinal_dispersivity (m) | 0.1--1.0 | 0.5--5.0 | 1.0--10.0 |
510 +---------------------------------+----------------+----------------+--------------------+
511 | streamline_length (m) | site-specific |
512 +---------------------------------+----------------+----------------+--------------------+
514 Parameters
515 ----------
516 cin_mean : float, default 500.0
517 Mean infiltration EC [uS/cm, typical surface water EC].
518 cin_amplitude : float, default 150.0
519 Seasonal amplitude of infiltration EC [uS/cm].
520 measurement_noise : float, default 10.0
521 Standard deviation of the Gaussian measurement noise [uS/cm].
522 retardation_factor : float, default 1.0
523 Retardation factor (1.0 for a conservative tracer).
524 molecular_diffusivity : float, default 5e-5
525 Effective ionic diffusion [m²/day].
526 longitudinal_dispersivity : float, default 1.0
527 Longitudinal dispersivity [m].
528 streamline_length : float, default 100.0
529 Travel distance along the streamline [m].
531 Returns
532 -------
533 tuple
534 See :func:`generate_example_data`.
536 See Also
537 --------
538 generate_example_data : Generic version with full parameter control.
539 generate_temperature_example_data : Wrapper with thermal transport defaults.
541 Notes
542 -----
543 All other parameters are forwarded unchanged to :func:`generate_example_data`;
544 see that function for their descriptions.
545 """
546 return generate_example_data(
547 date_start=date_start,
548 date_end=date_end,
549 date_freq=date_freq,
550 flow_mean=flow_mean,
551 flow_amplitude=flow_amplitude,
552 flow_noise=flow_noise,
553 cin_method=cin_method,
554 cin_mean=cin_mean,
555 cin_amplitude=cin_amplitude,
556 measurement_noise=measurement_noise,
557 aquifer_pore_volumes=aquifer_pore_volumes,
558 aquifer_pore_volume_gamma_mean=aquifer_pore_volume_gamma_mean,
559 aquifer_pore_volume_gamma_std=aquifer_pore_volume_gamma_std,
560 aquifer_pore_volume_gamma_loc=aquifer_pore_volume_gamma_loc,
561 aquifer_pore_volume_gamma_nbins=aquifer_pore_volume_gamma_nbins,
562 retardation_factor=retardation_factor,
563 molecular_diffusivity=molecular_diffusivity,
564 longitudinal_dispersivity=longitudinal_dispersivity,
565 streamline_length=streamline_length,
566 rng=rng,
567 )
570def generate_example_deposition_timeseries(
571 *,
572 date_start: str = "2018-01-01",
573 date_end: str = "2023-12-31",
574 freq: str = "D",
575 base: float = 0.8,
576 seasonal_amplitude: float = 0.3,
577 noise_scale: float = 0.1,
578 event_dates: npt.ArrayLike | pd.DatetimeIndex | None = None,
579 event_magnitude: float = 3.0,
580 event_duration: int = 30,
581 event_decay_scale: float = 10.0,
582 ensure_non_negative: bool = True,
583 rng: np.random.Generator | int | None = None,
584) -> tuple[pd.Series, pd.DatetimeIndex]:
585 """
586 Generate synthetic deposition timeseries for groundwater transport examples.
588 Parameters
589 ----------
590 date_start, date_end : str
591 Start and end dates for the generated time series (YYYY-MM-DD).
592 freq : str
593 Frequency string for pandas.date_range (default 'D').
594 base : float
595 Baseline deposition rate (ng/m²/day).
596 seasonal_amplitude : float
597 Amplitude of the annual seasonal sinusoidal pattern (ng/m²/day).
598 noise_scale : float
599 Standard deviation of Gaussian noise added to the signal (ng/m²/day).
600 event_dates : list-like or None
601 Dates (strings or pandas-compatible) at which to place episodic events.
602 Time-zone-naive entries are interpreted as UTC to match the generated
603 ``dates`` index. If None, a sensible default list is used.
604 event_magnitude : float
605 Peak deposition added at event onset (ng/m²/day). Decays exponentially
606 over ``event_duration`` days at rate ``event_decay_scale``.
607 event_duration : int
608 Duration of each event in days.
609 event_decay_scale : float
610 Decay scale used in the exponential decay for event time series.
611 ensure_non_negative : bool
612 If True, negative values are clipped to zero.
613 rng : numpy.random.Generator, int, or None, default None
614 Source of randomness for the additive Gaussian noise. Accepted in any
615 form supported by :func:`numpy.random.default_rng`. Pass an integer
616 (or :class:`numpy.random.Generator`) for reproducible output; ``None``
617 draws fresh entropy each call.
619 Returns
620 -------
621 tuple
622 A tuple containing:
624 - pandas.Series: Deposition time series (ng/m²/day) indexed by UTC
625 timestamps.
626 - pandas.DatetimeIndex: Time bin edges (n+1 edges for n values).
628 Raises
629 ------
630 ValueError
631 If ``event_decay_scale`` or ``event_duration`` is not positive, or if any
632 ``event_dates`` entry falls outside the generated ``dates`` range.
634 See Also
635 --------
636 gwtransport.deposition.deposition_to_extraction : Forward operator consuming this data.
637 gwtransport.deposition.extraction_to_deposition : Inverse operator.
638 """
639 if event_decay_scale <= 0:
640 msg = f"event_decay_scale must be positive, got {event_decay_scale}"
641 raise ValueError(msg)
642 if event_duration <= 0:
643 msg = f"event_duration must be positive, got {event_duration}"
644 raise ValueError(msg)
646 rng = np.random.default_rng(rng)
648 dates = pd.date_range(date_start, date_end, freq=freq).tz_localize("UTC")
649 n_dates = len(dates)
650 tedges = compute_time_edges(tedges=None, tstart=None, tend=dates, number_of_bins=n_dates)
652 # Base deposition rate with seasonal and event patterns. Use elapsed days (not the sample
653 # index) so the period stays one year for any ``freq``, not one year of samples.
654 days = ((dates - dates[0]) / pd.Timedelta(days=1)).to_numpy()
655 seasonal_pattern = seasonal_amplitude * np.sin(2 * np.pi * days / 365.25)
656 noise = noise_scale * rng.normal(0, 1, n_dates)
658 # Default event dates if not provided
659 if event_dates is None:
660 event_dates = ["2020-06-15", "2021-03-20", "2021-09-10", "2022-07-05"]
661 event_dates_index = pd.DatetimeIndex(pd.to_datetime(np.asarray(event_dates)))
662 # Match the timezone of `dates` so naive user input (and the string defaults)
663 # can be compared against the tz-aware index in `get_indexer`.
664 if event_dates_index.tz is None:
665 event_dates_index = event_dates_index.tz_localize(dates.tz)
666 else:
667 event_dates_index = event_dates_index.tz_convert(dates.tz)
669 out_of_range = (event_dates_index < dates[0]) | (event_dates_index > dates[-1])
670 if out_of_range.any():
671 msg = (
672 f"event_dates contains {out_of_range.sum()} date(s) outside the dates range "
673 f"[{dates[0]}, {dates[-1]}]: {event_dates_index[out_of_range].tolist()}"
674 )
675 raise ValueError(msg)
677 # Vectorized event accumulation. For each event start, scatter a ``(n_events, event_duration)``
678 # decay block into ``event`` via ``np.add.at`` so overlapping events sum correctly. The
679 # boundary mask drops indices that fall past the end of the series (preserves the loop's
680 # ``min(event_idx + event_duration, n_dates)`` clipping).
681 starts = dates.get_indexer(event_dates_index, method="nearest")
682 cols = np.arange(event_duration)
683 flat_indices = starts[:, None] + cols[None, :]
684 valid = flat_indices < n_dates
685 decay_block = np.broadcast_to(event_magnitude * np.exp(-cols / event_decay_scale), flat_indices.shape)
686 event = np.zeros(n_dates)
687 np.add.at(event, flat_indices[valid], decay_block[valid])
689 # Combine all components
690 total = base + seasonal_pattern + noise + event
691 if ensure_non_negative:
692 total = np.maximum(total, 0.0)
694 series = pd.Series(data=total, index=dates, name="deposition")
695 series.attrs.update({
696 "description": "Example deposition time series for groundwater transport modeling",
697 "source": "Synthetic data generated by gwtransport.examples.generate_example_deposition_timeseries",
698 "base": base,
699 "seasonal_amplitude": seasonal_amplitude,
700 "noise_scale": noise_scale,
701 "event_dates": [str(d.date()) for d in event_dates_index],
702 "event_magnitude": event_magnitude,
703 "event_duration": event_duration,
704 "event_decay_scale": event_decay_scale,
705 "date_start": date_start,
706 "date_end": date_end,
707 "date_freq": freq,
708 })
710 return series, tedges