Reference evapotranspiration (ETo) drives irrigation scheduling and agricultural water management. This project shows, end to end, how to model ETo with three approaches and compare them fairly:
| Model | Type | Uses physics? |
|---|---|---|
| LightGBM | Gradient-boosted trees | No (data-driven baseline) |
| Data-only NN | Neural network | No |
| PINN | Physics-Informed Neural Network | Yes (FAO-56 Penman-Monteith) |
The PINN embeds the FAO-56 Penman-Monteith equation directly into the training loss, so the network is rewarded for agreeing with established agronomy
- not just with the (noisy) training data.
This repo uses fully synthetic, reproducible data generated from real physics, so anyone can clone and run it with no data-licensing concerns.
The interesting story appears when data are scarce and noisy - the realistic situation for many farms and weather stations. Here the physics constraint helps the PINN generalize better than a purely data-driven model.
Scarce + noisy regime (1 station, 1 year, high label noise):
| Model | RMSE ↓ | MAE ↓ | R² ↑ |
|---|---|---|---|
| LightGBM | 0.840 | 0.612 | 0.827 |
| Data-only NN | 0.773 | 0.576 | 0.853 |
| PINN | 0.762 | 0.574 | 0.857 |
Data-rich regime (4 stations, 3 years, low noise) - all models are strong, differences shrink:
| Model | RMSE ↓ | MAE ↓ | R² ↑ |
|---|---|---|---|
| LightGBM | 0.188 | 0.147 | 0.990 |
| Data-only NN | 0.196 | 0.150 | 0.990 |
| PINN | 0.195 | 0.150 | 0.990 |
Takeaway: physics information matters most when data are limited or noisy. With abundant clean data, a good tree model is already excellent.
(Units are mm/day. Your exact numbers may vary slightly by machine / library version. Regenerate everything with the commands below.)
.
├── main.py # End-to-end pipeline (run this)
├── src/
│ ├── physics.py # FAO-56 Penman-Monteith (NumPy) - the "ground truth"
│ ├── data.py # Synthetic weather + ETo dataset generator
│ ├── models_lgbm.py # LightGBM baseline (falls back to scikit-learn)
│ ├── models_pinn.py # PINN + data-only NN (PyTorch, differentiable physics)
│ └── evaluate.py # Metrics + plots
├── notebook.ipynb # Intro PINN notebook (simple ODE example)
├── PINN_Tutorial.md # Step-by-step PINN primer (the simple ODE)
├── STEP_BY_STEP.md # Step-by-step guide for THIS ETo project
├── requirements.txt
├── LICENSE # MIT
└── README.md
Outputs (data/, results/, results_scarce/) are generated on run and are
git-ignored.
git clone https://github.com/GreenSmart-DSS/physics-informed-eto.git
cd physics-informed-eto
python -m venv .venv
# Windows: .venv\Scripts\activate
# macOS/Linux: source .venv/bin/activate
pip install -r requirements.txtpython main.pyThis will:
- Generate a synthetic ETo dataset (
data/eto_synthetic.csv). - Train LightGBM, a data-only NN, and a PINN.
- Print a metrics table and save plots +
metrics.csvtoresults/.
python main.py --stations 1 --years 1 --noise 0.8 --physics-weight 2.0 \
--outdir results_scarce --datadir data_scarce| Flag | Default | Meaning |
|---|---|---|
--stations |
4 | number of virtual weather stations |
--years |
3 | years of daily data per station |
--noise |
0.15 | std-dev of ETo label noise [mm/day] |
--epochs |
800 | NN / PINN training epochs |
--physics-weight |
1.0 | weight of the physics loss term in the PINN |
--seed |
42 | random seed (reproducibility) |
--outdir |
results | where plots + metrics are written |
--datadir |
data | where the generated dataset is written |
ETo is computed from eight daily inputs:
t_max, t_min air temperature [°C]
rh_mean relative humidity [%]
wind_2m wind speed at 2 m [m/s]
rs solar radiation [MJ m⁻² day⁻¹]
elevation station elevation [m]
latitude [decimal degrees]
doy day of year [1..365]
via the standard FAO-56 equation (src/physics.py):
0.408·Δ·(Rn − G) + γ·(900 / (T + 273))·u₂·(es − ea)
ETo = ────────────────────────────────────────────────────────
Δ + γ·(1 + 0.34·u₂)
src/data.py simulates seasonal, internally-consistent weather (temperature
follows an annual sinusoid, solar radiation is capped by clear-sky physics,
humidity anti-correlates with temperature, etc.), computes the true ETo with
the physics engine, then adds Gaussian sensor noise to create the training
label. This gives a realistic yet fully reproducible learning problem.
src/models_pinn.py re-implements Penman-Monteith in PyTorch so it is
differentiable. The network is trained on:
L = L_data + λ · L_physics
L_data = MSE( NN(x), noisy ETo label ) # fit the observations
L_physics = MSE( NN(x), Penman-Monteith(x) ) # obey the equation
Setting λ = 0 recovers an ordinary data-only NN, which is exactly the
baseline we compare against. Increasing λ (--physics-weight) makes the model
lean harder on physics - most useful when data are scarce/noisy.
After a run, results/ contains:
metrics.csv- RMSE, MAE, R², MBE for every modelmetrics_bar.png- grouped bar chart comparing the modelsscatter_<model>.png- predicted-vs-observed scatter (with 1:1 line)loss_<model>.png- training-loss curves for the neural models
New to PINNs? Follow this order:
PINN_Tutorial.md- learn the PINN idea on a tiny ODE (dy/dx + 2y = 0).notebook.ipynb- run that same example interactively.STEP_BY_STEP.md- walk through this ETo project module by module.main.py- run the full comparison and read the code.
- Swap the synthetic data for a real station dataset (keep the 8 feature
columns and an
etolabel column). - Add hard physical bounds (e.g. energy-balance limits) as extra loss terms.
- Turn a physical parameter (like the wind/aerodynamic coefficient) into a
trainable
nn.Parameterto solve an inverse problem. - Try other tabular baselines (XGBoost, CatBoost, Random Forest).
Allen, R.G., Pereira, L.S., Raes, D., Smith, M. (1998). Crop evapotranspiration - Guidelines for computing crop water requirements. FAO Irrigation and drainage paper 56, Rome.
MIT - see LICENSE.