Physics-informed VAE-EVT for Tail-Aware Radio Map Prediction

August 11, 2026 · View on GitHub

The framework predicts a full 256×256 SNR map in a single forward pass and models the bulk and the extreme lower tail of the SNR distribution separately. A Gaussian bulk latent and a GPD-anchored tail latent, routed per pixel by a learned outage probability. Deterministic scene geometry, line-of-sight, shadowing, penetration depth, distance is computed up front and fed to the network as a ten-channel input tensor.


Install

git https://github.com/AmandaGamage/physics-informed-vae-evt.git
cd physics-informed-vae-evt

python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

Requires Python ≥ 3.10 and TensorFlow 2.19.

Resources. A GPU with ≥ 16 GB of memory is recommended. Peak host RAM for the full run is roughly 20 GB: X alone is 3000 × 256 × 256 × 10 float32 ≈ 7.9 GB, and both the raw feature dictionaries and the train/test copies of X are briefly live alongside it. Reduce --n-maps if memory is tight. The pipeline scales linearly.

Data

Download RadioMapSeer.

The dataset is not redistributed here and carries its own licence.

Repository layout

src/vaeevt/
├── config.py       every hyperparameter in one dataclass
├── snr.py          link budget, gain → SNR, the [0,1] target scaler
├── outage.py       per-map outage labelling, GPD anchor fitting
├── features.py     ray tracing and the 10-channel physics tensor  (Sec. III-A)
├── dataset.py      RadioMapSeer loading, tensor assembly, re-thresholding
├── layers.py       dual-latent encoder, attention U-Net decoder  (Sec. III-B/C/D)
├── losses.py       the composite objective                        (Sec. III-E)
├── model.py        GPD reparameterisation, training/inference loop
├── callbacks.py    KL warmup and loss-weight ramps                (Sec. IV-A2)
├── metrics.py      outage RMSE, F1/precision/recall, routing threshold
├── evaluate.py     evaluate the model 
└── train.py        training entry point

The ten input channels

#SymbolChannelDescription
0Bbuilding_mapbinary occupancy
1T_xtx_maptruncated Gaussian at the transmitter
2M_LOSlos_mask1 where the ray to p_tx is unobstructed
3d_Llos_distLoS-masked normalised log distance, Eq. (5)
4S_NLOSnlos_shadowlocalised shadowing score
5D_NLOSnlos_depthnormalised penetration depth, Eq. (6)
6Eshadow_edgeLoS/NLoS boundary map, guides spatial attention
7P_outageoutage_priorcoarse geometric outage risk, Eq. (7)
8D_alldist_allnormalised distance on all free-space pixels
9γ̂_ththresholdbroadcast normalised per-map outage threshold

Channel 9 is what lets one trained model be evaluated at several outage quantiles without retraining: rewrite channel 9, rebuild the labels, re-run inference. That is exactly what dataset.rebuild_for_threshold does.

Using the pieces separately

The physics preprocessing has no TensorFlow dependency and is useful on its own:

from vaeevt import Config, compute_geometric_features, stack_input_tensor

cfg = Config()
feats = compute_geometric_features(tx_y=128, tx_x=64, building_map=B, cfg=cfg)
X = stack_input_tensor(feats, threshold_norm=0.05)   # (256, 256, 10)

The code calls on the global np.random.shuffle, so the labels depended on call order and differed between the training script and the evaluation script. This implementation threads an explicitly seeded np.random.Generator through compute_permap_outage_mask, which makes runs reproducible. Numbers will therefore not bit-match the original run even though the method is identical.

Citation

@inproceedings{
}

Licence

MIT for the code. RadioMapSeer is distributed under its own terms.