Causal machine-learning early warning for downstream contaminant arrival in the ADR1D benchmark.
A frozen one-hour classifier, complete decision evidence, and reproducible interfaces for training, inference, validation, and visualization.
- Overview
- Repository Structure
- Installation
- Quick Start
- Early-Warning Task
- Data and Feature Contract
- Model Development
- Validated Results
- Visual Results
- Inference Interface
- Validation and Reproducibility
- Data Provenance
- Limitations
- How to Cite
- Research Team
- Industry Partners
- License and Rights
- Acknowledgments
- Contact and Support
- FAQ
ADR1D-EWS predicts whether contaminant concentration at a protected downstream location will reach a defined threshold within the next hour. It was developed from the open ADR1D benchmark, which provides 300 reactive-transport scenarios observed every 30 minutes at six virtual sensors.
The warning system protects sensor S06 at 1000 m. It receives only measurements from upstream sensors S01-S05 available through the current decision time. The model never uses future observations, S06 measurements, source parameters, transport parameters, scenario identifiers, labels, or partition assignments as predictors.
The release separates three questions that are often reported as though they were interchangeable:
- Detection: has an upstream sensor already measured a concentration above its detection limit?
- Prediction: will the protected location reach
0.01 mg/Lwithin one hour? - Early warning: does the prediction become positive before arrival at S06, and how much valid warning time does it provide?
- ⌛ Physical warning horizon: a primary one-hour task with 30-minute and two-hour sensitivity labels retained in the decision table.
- 🔒 Causal predictors: 52 features computed from upstream histories through each decision time only.
- 🔀 Scenario-independent partitions: complete scenarios remain in train, validation, or test; temporal windows never cross those boundaries.
- ⚖️ Comparable references: two interpretable alert rules and three supervised classifiers use the same evaluation units.
- ❄️ Frozen deployment artifact: the selected estimator and threshold were serialized before the locked test evaluation.
- 🔬 Independent inference check: the saved artifact reproduces all 200 test alerts and rejects missing or non-finite features.
- 📊 Operational metrics: pointwise discrimination is reported together with event detection, warning time, and false-alert episodes.
| Item | Value |
|---|---|
| ADR1D scenarios | 300 |
| Train / validation / test scenarios | 210 / 45 / 45 |
| Decision rows | 1,346 |
| Causal input features | 52 |
| Sampling interval | 30 min |
| Primary warning horizon | 1 h |
| Protected sensor and threshold | S06 at 1000 m; 0.01 mg/L |
| Selected estimator | Histogram gradient boosting |
| Frozen probability threshold | 0.368151 |
| Test events detected | 45 of 45 |
| Median valid test warning time | 1.0 h |
| Scheduled development period | August-September 2025 |
| Initial code release | August 2025 |
| Validation and documentation update | July 2026 |
| Package version | 1.0.0 |
The scheduled period records the project plan. The current execution, validation, and public-release preparation were completed in July 2026 and retain their actual dates in the machine-readable evidence.
.
|-- README.md
|-- CITATION.cff
|-- LICENSE
|-- LICENSE-DATA
|-- requirements.txt
|-- data/
| |-- synthetic_adr1d_sensor_observations.csv
| `-- adr1d_early_warning_decision_points.csv
|-- models/
| `-- adr1d_early_warning_model.joblib
|-- scripts/
| |-- build_decision_table.py
| |-- train_early_warning_models.py
| |-- predict_early_warning.py
| |-- validate_inference_system.py
| `-- plot_early_warning_results.py
|-- results/
| |-- decision_table_validation.json
| |-- model_selection_validation.json
| |-- test_evaluation.json
| |-- test_predictions.csv
| |-- inference_system_validation.json
| |-- figure_manifest.json
| `-- figures/
| |-- model_comparison.png
| |-- selected_model_diagnostics.png
| `-- alert_timelines.png
`-- docs/
|-- team/
`-- partners/
The repository uses one authoritative README. Schemas, protocols, results, and limitations are documented here, while exact validation values remain in JSON and CSV files suitable for automated inspection.
| Component | Supported configuration |
|---|---|
| Python | 3.12 |
| Operating system | Linux, macOS, or Windows |
| RAM | 2 GB for inference; 4 GB recommended for reconstruction and plotting |
| Storage | Approximately 400 MB including the virtual environment |
| Accelerator | Not required; all documented workflows run on CPU |
The canonical environment used Python 3.12.2 on Apple arm64. The model relies on scikit-learn serialization and should be loaded with the pinned package versions whenever exact reproduction is required.
| Package | Version | Role |
|---|---|---|
| NumPy | 2.0.2 | Numeric arrays and validation |
| pandas | 2.2.3 | Sensor, feature, and prediction tables |
| SciPy | 1.15.2 | Numerical dependency used by scikit-learn |
| scikit-learn | 1.7.0 | Classifiers, metrics, and model inference |
| Joblib | 1.4.2 | Serialized model bundle |
| Matplotlib | 3.10.0 | Diagnostic figures |
git clone https://github.com/gstinoco/ADR1D-EWS.git
cd ADR1D-EWS
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install --upgrade pip
python3 -m pip install -r requirements.txtOn Windows PowerShell, activate the environment with:
.venv\Scripts\Activate.ps1python3 scripts/validate_inference_system.pyExpected final message:
Validation status: ok (200 rows)
python3 scripts/predict_early_warning.py \
--input-csv data/adr1d_early_warning_decision_points.csv \
--split test \
--output-csv test_alerts.csvThe output contains the preserved identifiers, estimator name, model version, probability of arrival within one hour, frozen decision threshold, and binary alert.
python3 scripts/build_decision_table.pyThis command reads the bundled six-sensor histories, rebuilds all pre-arrival decision rows, checks labels and partitions, and perturbs future observations to confirm that prior features remain unchanged.
python3 scripts/train_early_warning_models.py
python3 scripts/validate_inference_system.py
python3 scripts/plot_early_warning_results.pyThe training command recreates the published comparison under the locked protocol. It must not be used to choose a different model or threshold from the already reported test results.
For scenario (s), let (C_{s,6}(t)) denote the true concentration at S06. The event time is the first sampled time at which:
At an eligible decision time (t<t_s^{\mathrm{event}}), the primary label is:
This definition asks whether arrival is imminent. It does not classify an unknown chemical species and does not treat hydrologic context as a contamination label.
Only noisy, detection-limit-aware observations from S01-S05 at times (\tau\leq t) enter the feature vector:
S06 is used only to determine event timing and labels during benchmark construction. The model input contains no target-sensor concentration, time-to-event value, future sample, physical parameter, or split identifier.
The scenario is the independent unit. All decision times from one scenario remain in a single partition:
| Partition | Scenarios | Decision rows | Role |
|---|---|---|---|
| Train | 210 | 903 | Fit supervised estimators |
| Validation | 45 | 243 | Select estimator and threshold |
| Test | 45 | 200 | One locked final evaluation |
Each row receives the reciprocal of its scenario's decision count so that every scenario contributes total weight one, regardless of its travel time.
data/synthetic_adr1d_sensor_observations.csv contains 88,200 observations:
300 scenarios, six sensors, and 49 half-hour samples per sensor.
| Column | Meaning | Unit |
|---|---|---|
observation_id |
Stable observation identifier | none |
scenario_id |
Independent ADR1D scenario | none |
split |
Frozen train, validation, or test assignment | none |
sensor_id |
Sensor S01-S06 | none |
x_m |
Sensor position | m |
time_s |
Elapsed time | s |
concentration_true_mg_L |
Analytical concentration | mg/L |
noise_std_mg_L |
Observation-noise standard deviation | mg/L |
concentration_observed_mg_L |
Non-negative noisy observation | mg/L |
detection_limit_mg_L |
Reporting limit | mg/L |
is_below_detection_limit |
Censoring indicator | Boolean |
data/adr1d_early_warning_decision_points.csv contains one row for every
eligible scenario-time decision before physical arrival at S06.
| Column family | Purpose | Used by model? |
|---|---|---|
| Identifiers and split | Traceability and independent partitioning | No |
| Decision and event times | Evaluation and warning-time calculation | No |
| Horizon labels | Supervised targets at 0.5, 1, and 2 h | No |
| Scenario weight | Equal total contribution per scenario | Fit only |
evaluation_* |
Observable lead-time audit | No |
feature_* |
Causal upstream predictors | Yes |
| Group | Construction | Count |
|---|---|---|
| Per-sensor history | Current value, 30-min lag and change, 1-h mean and maximum, current detection, 1-h detection fraction, and prior detection | 40 |
| Network state | Mean and maximum concentration, current and historical detection counts, farthest detected sensor, concentration centroid, and largest positive change | 8 |
| Spatial gradients | Current concentration gradient between adjacent upstream sensors | 4 |
All concentration features use mg/L; positions use m; gradients use
mg/L/m. Missing feature columns, non-finite values, duplicated sensor-time
rows, unexpected geometry, invalid split assignments, and asynchronous sample
grids are rejected with explicit errors.
- Persistent upstream detection: alert from the first upstream detection onward.
- Current S05 detection: alert only while the nearest upstream sensor is currently above its detection limit.
The first rule favors sensitivity and tends to remain active too early. The second favors specificity but often detects arrival too late.
- logistic regression with standardization and deterministic
liblinearoptimization; - random forest with 400 trees and a single-thread deterministic execution policy;
- histogram gradient boosting with 250 boosting iterations, learning rate
0.05, at most 15 leaf nodes, minimum leaf size 10, and L2 regularization1.0.
All candidates use random seed 20250801, the same 52 predictors, the same
scenario weights, and the same train and validation scenarios. The comparison
is deliberately limited to three model families rather than a broad
hyperparameter search.
For each supervised candidate, the validation threshold maximizes equal-scenario balanced accuracy. Ties are resolved by F2, event detection, false-alert episode rate, and the higher threshold. Model families are ranked by validation balanced accuracy, F2, average precision, event detection, and false-alert episode rate.
Histogram gradient boosting achieved the largest validation objective and was
frozen at threshold 0.3681513624958759. The fitted estimator was trained only
on the training partition. The threshold was selected only on validation. The
test partition was opened after both choices were serialized.
Metrics below assign equal total weight to every validation scenario.
| Method | Balanced accuracy | Sensitivity | Specificity | Event detection | False-alert episodes/h |
|---|---|---|---|---|---|
| Persistent upstream | 0.750 | 0.874 | 0.627 | 1.000 | 0.235 |
| Current S05 | 0.637 | 0.299 | 0.974 | 0.644 | 0.078 |
| Logistic regression | 0.837 | 0.792 | 0.882 | 1.000 | 0.183 |
| Random forest | 0.839 | 0.807 | 0.870 | 0.978 | 0.196 |
| Histogram gradient boosting | 0.849 | 0.796 | 0.901 | 0.978 | 0.170 |
| Metric | Result |
|---|---|
| Equal-scenario balanced accuracy | 0.818 |
| Equal-scenario sensitivity | 0.738 |
| Equal-scenario specificity | 0.898 |
| Equal-scenario precision | 0.897 |
| Equal-scenario F1 / F2 | 0.810 / 0.765 |
| ROC-AUC / average precision | 0.848 / 0.891 |
| Unweighted decision accuracy | 0.840 |
| Events detected | 45 of 45 |
| Missed events | 0 |
| Median valid warning time | 1.0 h |
| Valid warning-time range | 0.5-1.0 h |
| False-alert points | 14 |
| False-alert episodes | 13 |
| Negative exposure | 55 scenario-hours |
| False-alert episodes per scenario-hour | 0.236 |
| Scenarios with at least one false alert | 12 of 45 |
Event detection and pointwise sensitivity answer different questions. An event may have up to two eligible decisions within the one-hour window; detecting at least one produces a detected event even if another eligible decision is negative. For this reason, 45 of 45 detected events is compatible with pointwise sensitivity of 0.738.
The selected model improves specificity and precision relative to the persistent upstream rule while retaining event-level detection on this test partition. False alerts remain visible: 12 test scenarios contain at least one false-alert episode.
Exact validation candidates, thresholds, test baselines, confusion components,
probability metrics, and warning-time summaries are stored in
results/model_selection_validation.json and
results/test_evaluation.json.
The panels separate balanced accuracy, sensitivity, specificity, event detection, and false-alert episode rates. The test comparison is an audit after selection and was not used to rerank models.
Probability distributions overlap near the frozen threshold, which explains the remaining false positives and false negatives at individual decision times. The confusion matrix therefore accompanies, rather than replaces, the event-level analysis.
The timelines show upstream measurements, physical arrival at S06, the valid
one-hour warning window, probability threshold crossings, correct alerts, and
false alerts. Scenario identifiers used in the figure are recorded in
results/figure_manifest.json.
python3 scripts/predict_early_warning.py \
--model models/adr1d_early_warning_model.joblib \
--input-csv my_feature_rows.csv \
--output-csv my_alerts.csvThe input may include metadata, but it must contain all 52 frozen feature_*
columns. Their order in the CSV is not significant because the model bundle
stores and applies its own feature order.
The output contract is:
| Column | Meaning |
|---|---|
| Preserved metadata | Any available decision, scenario, split, and timing fields |
estimator_name |
hist_gradient_boosting |
model_version |
1.0.0 |
alert_probability |
Predicted probability of threshold arrival within one hour |
alert_threshold |
Frozen threshold 0.3681513624958759 |
alert |
Binary thresholded alert |
from pathlib import Path
import pandas as pd
from scripts.predict_early_warning import load_model_payload, predict_alerts
payload = load_model_payload(Path("models/adr1d_early_warning_model.joblib"))
feature_rows = pd.read_csv("my_feature_rows.csv")
alerts = predict_alerts(feature_rows, payload)Joblib artifacts can execute code during loading. Use the bundled model or another artifact obtained from a trusted source.
python3 scripts/validate_inference_system.pyThe validator:
- reloads the serialized estimator;
- confirms the model version, feature count, and frozen threshold;
- reproduces all 200 locked test decisions;
- requires exact agreement for all 200 binary alerts;
- checks probability agreement within
1e-15; - checks one-row inference;
- verifies rejection of a missing feature;
- verifies rejection of a non-finite feature;
- confirms that the model and threshold were not changed after testing.
The current status is ok. Reloaded probabilities differ from the reference
by at most 1.11e-16.
python3 scripts/build_decision_table.pyThe builder verifies 300 scenario-level split assignments, 1,346 pre-arrival decisions, 52 finite predictor columns, three horizon labels, and equal-scenario weights. It also modifies future upstream measurements in one scenario from each partition and requires all prior features to remain exactly unchanged.
python3 scripts/train_early_warning_models.py
python3 scripts/validate_inference_system.py
python3 scripts/plot_early_warning_results.pyThe model-development script uses one thread for the random forest comparator and 15-significant-digit CSV serialization. Consecutive runs in the canonical environment reproduce the model choice, threshold, metrics, alerts, and published prediction table.
| Resource | Role | Location |
|---|---|---|
| ADR1D 1.0.0 | Canonical scenarios and six-sensor histories | GitHub, Zenodo |
| ADR1D sensor observations | Bundled source for decision-table reconstruction | data/synthetic_adr1d_sensor_observations.csv |
| ADR1D-EWS decision table | Derived causal predictors, labels, and evaluation metadata | data/adr1d_early_warning_decision_points.csv |
| ADR1D-EWS model | Frozen histogram-gradient-boosting estimator and threshold | models/adr1d_early_warning_model.joblib |
Water Quality Portal data were reviewed as observational context during task definition but were not used to train, select, or test ADR1D-EWS. Their sampling intervals, incomplete sample-time coverage, and absence of explicit contamination-onset labels do not support the present high-frequency supervised warning experiment. This repository therefore does not redistribute WQP records.
The release contains no publisher material, private measurements, external credentials, or institutional-access content.
- The evidence comes from one-dimensional synthetic ADR1D scenarios rather than field monitoring campaigns.
- Every current scenario contains a contaminant pulse. The test partition has pre-event negative decisions but no independent source-free campaign.
- Event detection of 45 of 45 does not establish field sensitivity, transfer across contaminants, or performance under unknown sensor failures.
- The target is arrival at S06 above
0.01 mg/L; it is not chemical-species identification, source localization, or health-risk classification. - The 30-minute sampling interval limits warning-time resolution to 0.5 h.
- The model assumes the documented geometry, units, censoring rule, and five upstream sensors. A changed network requires new feature construction and validation.
- Probabilities are discriminative scores, not calibrated operational risk estimates. No probability-calibration claim is made.
- Changing the threshold changes sensitivity, specificity, and false-alert burden. A deployment threshold must be chosen with independent validation and explicit response costs.
- The benchmark does not represent sensor drift, missing streams, maintenance outages, adversarial corruption, or changing field chemistry.
- ADR1D-EWS should support scientific evaluation and prototype integration; it should not be the sole basis for environmental, regulatory, or public-health action.
Gerardo Tinoco-Guerrero, Francisco J. Domínguez-Mota, and J. Alberto Guzmán-Torres (2026). ADR1D-EWS: Causal Machine-Learning Early Warning for Reactive Contaminant Transport. Version 1.0.0. Universidad Michoacana de San Nicolás de Hidalgo. https://github.com/gstinoco/ADR1D-EWS
Machine-readable metadata are available in CITATION.cff.
GitHub can display them through its Cite this repository interface. An
archival DOI can be added after the first public release is deposited.
Analyses should also cite the upstream ADR1D benchmark:
Gerardo Tinoco-Guerrero, Francisco J. Domínguez-Mota, and J. Alberto Guzmán-Torres (2026). ADR1D and WQP-NM-Nutrients: A Reproducible Contaminant-Transport Benchmark and Curated Water-Quality Snapshot. https://doi.org/10.5281/zenodo.21499528
- Friedman, J. H. (2001). Greedy function approximation: A gradient boosting machine. The Annals of Statistics, 29(5), 1189-1232. https://doi.org/10.1214/aos/1013203451
- Breiman, L. (2001). Random forests. Machine Learning, 45, 5-32. https://doi.org/10.1023/A:1010933404324
- Ogata, A., and Banks, R. B. (1961). A solution of the differential equation of longitudinal dispersion in porous media. U.S. Geological Survey Professional Paper 411-A. https://doi.org/10.3133/pp411A
Researchers and students advancing reproducible scientific machine learning and numerical modeling for environmental transport
| Photo | Student | Institution | Contact |
|---|---|---|---|
![]() |
Gabriela Pedraza-Jiménez |
||
![]() |
Eli Chagolla-Inzunza |
| Photo | Student | Institution | Contact |
|---|---|---|---|
![]() |
Jorge L. González-Figueroa |
||
![]() |
Christopher N. Magaña-Barocio |
| Photo | Student | Institution | Contact |
|---|---|---|---|
![]() |
Maria Goretti Fraga-Lopez |
Student contributors are acknowledged for their participation in the broader
research program. Formal software citation and copyright attribution remain
limited to the three principal researchers listed in CITATION.cff and the
license files.
Connecting environmental modeling, scientific machine learning, and applied engineering
|
Focus areas
|
ADR1D-EWS uses component-specific licensing:
- Source code and serialized model: MIT License in
LICENSE. - Data tables, reported results, and figures: Creative Commons
Attribution 4.0 International in
LICENSE-DATA. - Upstream ADR1D content: remains subject to the attribution and licensing terms of the archived ADR1D release.
- Third-party software: remains subject to its own licenses.
- Institutional logos and personal photographs: excluded from the MIT and CC BY grants and retained by their respective rights holders.
Copyright attribution identifies Gerardo Tinoco-Guerrero, Francisco J. Domínguez-Mota, and J. Alberto Guzmán-Torres as the principal investigators. The software and model are provided without warranty. Attribution must not imply endorsement by the authors or supporting institutions.
We thank the institutions and partners whose continuing institutional and financial support made early-warning model development, validation, documentation, dissemination, and student participation possible.
|
Collaboration highlights
|
Collaboration highlights
|
|
Primary Research Contact
Scientific coordination and early-warning integration Gerardo Tinoco-Guerrero Universidad Michoacana de San Nicolás de Hidalgo Morelia, Michoacán, Mexico |
Repository Support
Questions, reproducibility reports, and integration problems
|
What does ADR1D-EWS predict?
It predicts whether true concentration at ADR1D sensor S06 will first reach 0.01 mg/L within the next hour, using only upstream S01-S05 histories available through the decision time.
Why are all 45 test events detected when pointwise sensitivity is 0.738?
One event can have two eligible half-hour decisions inside the one-hour window. Event detection requires at least one positive alert; pointwise sensitivity evaluates every eligible decision separately.
Is ADR1D-EWS ready for autonomous field deployment?
No. The current evidence is synthetic, one-dimensional, and does not include independent source-free campaigns, sensor outages, drift, or changing field chemistry. Field use requires new data, calibration, operational thresholds, and independent validation.
Were Water Quality Portal records used to train the classifier?
No. They informed the initial data-suitability review but were excluded from training, selection, and testing because the available records do not provide regular high-frequency histories or explicit contamination-onset labels for this task.
Can the probability threshold be changed?
Technically yes, but the published test metrics apply only to 0.3681513624958759. A different threshold must be selected using independent validation evidence and the intended cost of missed events and false alerts.
Can any Joblib model be loaded safely?
No. Joblib and pickle-based artifacts may execute code while loading. Use the bundled file or another model obtained from a trusted source.














