Replace legacy files with Trackformer1.1
Browse filesRemove the previous model files and publish only the Trackformer1.1 causal inference bundle.
- # Typhoon PredictTyphoon Predict is an ERA5-conditioned tropical-cyclone track research model. It predicts future cyclone center positions from recent track history and a local atmospheric-state patch. The project is intended for research, experimentation, and reproducible model development.## Model ArchitectureThe model combines:- A convolutional encoder for storm-centered ERA5 atmospheric patches.- A bidirectional GRU for recent cyclone track history.- A fusion multilayer perceptron that combines the atmospheric and track representations.- Probabilistic output heads that predict the mean and log-scale of future latitude and longitude offsets.- A latent projection used to create correlated ensemble members for uncertainty-aware forecasts.The forecast is generated recursively for multiple lead times. The ensemble represents sampled plausible tracks; the mean track is a summary and should not be treated as a guaranteed path.## Training ConfigurationThe released experiment uses:- ERA5 reanalysis data beginning in 1979.- Western Pacific basin samples.- Four historical track steps as input.- Forecast lead times of 6, 12, 24, 48, 72, 96, and 120 hours.- Storm-centered patches with an 8 degree half-width at 0.5 degree resolution.- Chronological split: training through 2015 and validation through 2019.- Batch size 64, learning rate 2e-4, weight decay 1e-4.- Up to 80 epochs with early stopping patience of 12 epochs.- 50 ensemble members for uncertainty sampling.The training pipeline normalizes atmospheric variables, extracts storm-centered windows, aligns ERA5 with track timestamps, and uses chronological validation to reduce temporal leakage.## Data PipelineTraining examples require:1. Quality-controlled tropical-cyclone track fixes with timestamp, latitude, and longitude.2. ERA5 atmospheric fields covering the same time range.3. Storm-centered spatial extraction around each observed cyclone position.4. Historical track sequences paired with future positions at the configured lead times.5. Chronological train and validation splits.Cached patches can be reused to reduce repeated preprocessing and storage overhead during experiments.## InferenceThe local inference script loads a PyTorch checkpoint, prepares the configured input sequence, runs ensemble sampling, and writes forecast points plus an optional HTML map. See run_inference.py and the GitHub repository for the current command-line workflow.When live ERA5 data is unavailable, the example inference path uses normalized atmospheric inputs as a software smoke test. Operational or scientific use requires real, correctly aligned atmospheric analysis data.## Model FormatsThe native release format is PyTorch. GGUF is designed primarily for transformer-style language models and is not a suitable interchange format for this custom CNN-GRU probabilistic model. ONNX or TorchScript conversion may be possible, but any converted model must be validated against the native checkpoint for numerical and forecast consistency.## LimitationsThis is a research model, not an operational warning system. Forecast quality depends on the training data, basin coverage, input alignment, preprocessing, and model calibration. It should not replace official meteorological agencies or emergency-management guidance.## License and AttributionReview the repository license and source-data terms before redistribution or commercial use. ERA5 data is provided by the Copernicus Climate Change Service and follows its applicable terms. +0 -0
- .gitattributes +2 -35
- README.md +92 -154
- forecast.json +0 -74
- forecast_world_map.html +0 -61
- models/trackformer_1_1/manifest.json +33 -0
- models/trackformer_1_1/trackformer_1_1_calibration.json +2180 -0
- v23_terrain_wp.npz → models/trackformer_1_1/trackformer_1_1_intensity_seed0.pt +2 -2
- best.pt → models/trackformer_1_1/trackformer_1_1_intensity_seed1.pt +2 -2
- v23_seed1.pt → models/trackformer_1_1/trackformer_1_1_intensity_seed2.pt +2 -2
- v23_norm_stats.npz → models/trackformer_1_1/trackformer_1_1_norm_stats.npz +0 -0
- v23_seed2.pt → models/trackformer_1_1/trackformer_1_1_structure_seed0.pt +2 -2
- models/trackformer_1_1/trackformer_1_1_structure_seed1.pt +3 -0
- models/trackformer_1_1/trackformer_1_1_structure_seed2.pt +3 -0
- models/trackformer_1_1/trackformer_1_1_temporal_seed0.pt +3 -0
- models/trackformer_1_1/trackformer_1_1_temporal_seed1.pt +3 -0
- models/trackformer_1_1/trackformer_1_1_temporal_seed2.pt +3 -0
- requirements.txt +3 -5
- run_inference.py +0 -94
- run_v23.py +0 -222
- trackformer_1_1.py +35 -0
- trackformer_1_1_base_route.py +419 -0
- trackformer_1_1_intensity.py +748 -0
- trackformer_1_1_route.py +209 -0
- trackformer_1_1_temporal.py +92 -0
- trackformer_v23.py +0 -229
- v23_seed0.pt +0 -3
- v23_seed3.pt +0 -3
- v23_seed4.pt +0 -3
- v23_seed5.pt +0 -3
- v23_seed6.pt +0 -3
- v23_seed7.pt +0 -3
- v23_seed8.pt +0 -3
- v23_seed9.pt +0 -3
# Typhoon PredictTyphoon Predict is an ERA5-conditioned tropical-cyclone track research model. It predicts future cyclone center positions from recent track history and a local atmospheric-state patch. The project is intended for research, experimentation, and reproducible model development.## Model ArchitectureThe model combines:- A convolutional encoder for storm-centered ERA5 atmospheric patches.- A bidirectional GRU for recent cyclone track history.- A fusion multilayer perceptron that combines the atmospheric and track representations.- Probabilistic output heads that predict the mean and log-scale of future latitude and longitude offsets.- A latent projection used to create correlated ensemble members for uncertainty-aware forecasts.The forecast is generated recursively for multiple lead times. The ensemble represents sampled plausible tracks; the mean track is a summary and should not be treated as a guaranteed path.## Training ConfigurationThe released experiment uses:- ERA5 reanalysis data beginning in 1979.- Western Pacific basin samples.- Four historical track steps as input.- Forecast lead times of 6, 12, 24, 48, 72, 96, and 120 hours.- Storm-centered patches with an 8 degree half-width at 0.5 degree resolution.- Chronological split: training through 2015 and validation through 2019.- Batch size 64, learning rate 2e-4, weight decay 1e-4.- Up to 80 epochs with early stopping patience of 12 epochs.- 50 ensemble members for uncertainty sampling.The training pipeline normalizes atmospheric variables, extracts storm-centered windows, aligns ERA5 with track timestamps, and uses chronological validation to reduce temporal leakage.## Data PipelineTraining examples require:1. Quality-controlled tropical-cyclone track fixes with timestamp, latitude, and longitude.2. ERA5 atmospheric fields covering the same time range.3. Storm-centered spatial extraction around each observed cyclone position.4. Historical track sequences paired with future positions at the configured lead times.5. Chronological train and validation splits.Cached patches can be reused to reduce repeated preprocessing and storage overhead during experiments.## InferenceThe local inference script loads a PyTorch checkpoint, prepares the configured input sequence, runs ensemble sampling, and writes forecast points plus an optional HTML map. See run_inference.py and the GitHub repository for the current command-line workflow.When live ERA5 data is unavailable, the example inference path uses normalized atmospheric inputs as a software smoke test. Operational or scientific use requires real, correctly aligned atmospheric analysis data.## Model FormatsThe native release format is PyTorch. GGUF is designed primarily for transformer-style language models and is not a suitable interchange format for this custom CNN-GRU probabilistic model. ONNX or TorchScript conversion may be possible, but any converted model must be validated against the native checkpoint for numerical and forecast consistency.## LimitationsThis is a research model, not an operational warning system. Forecast quality depends on the training data, basin coverage, input alignment, preprocessing, and model calibration. It should not replace official meteorological agencies or emergency-management guidance.## License and AttributionReview the repository license and source-data terms before redistribution or commercial use. ERA5 data is provided by the Copernicus Climate Change Service and follows its applicable terms.
DELETED
|
File without changes
|
.gitattributes
CHANGED
|
@@ -1,35 +1,2 @@
|
|
| 1 |
-
*.
|
| 2 |
-
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 1 |
+
models/**/*.pt filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
models/trackformer_1_1/trackformer_1_1_norm_stats.npz filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -3,165 +3,103 @@ license: mit
|
|
| 3 |
library_name: pytorch
|
| 4 |
pipeline_tag: time-series-forecasting
|
| 5 |
tags:
|
| 6 |
-
- tropical-cyclone
|
| 7 |
-
- weather-forecasting
|
| 8 |
-
- pytorch
|
| 9 |
-
-
|
| 10 |
-
-
|
| 11 |
---
|
| 12 |
|
| 13 |
-
#
|
| 14 |
-
|
| 15 |
-
**Research models — not an operational warning system. Do not use for evacuation, aviation,
|
| 16 |
-
maritime, or emergency decisions.**
|
| 17 |
-
|
| 18 |
-
## Current best model: TrackFormer v23
|
| 19 |
-
|
| 20 |
-
TrackFormer v23 predicts the atmospheric steering flow that carries a storm as an explicit
|
| 21 |
-
chain-of-thought (CoT) intermediate step, conditions that estimate on how the flow has been
|
| 22 |
-
evolving over the previous day (t-24h, t-12h, now), and derives track from it. Result:
|
| 23 |
-
**434.96 km RMS track error** (10-seed ensemble), WP+EP 2020+, full 20-lead-horizon test set
|
| 24 |
-
(3,763 windows).
|
| 25 |
-
|
| 26 |
-
This is the best-performing model in the whole project, reached through a longer architecture
|
| 27 |
-
progression:
|
| 28 |
-
|
| 29 |
-
1. **v10–v20** — a small CNN encoder reads a deep-layer-mean steering-wind patch around the storm.
|
| 30 |
-
2. **v21, v22** — chain-of-thought: predict the steering flow itself, then derive track from it
|
| 31 |
-
(v22 adds a latent CoT with weight-tied feedback rounds).
|
| 32 |
-
3. **v23** — add a temporal history of the CoT steering representation. Best result: **434.96 km**.
|
| 33 |
-
4. **v24–v29** — four further environmental additions on top of v23 (an environmental token, an
|
| 34 |
-
ocean-heat CNN patch, a drift adapter, raw ERA5 steering wind) all came back **null**: once a
|
| 35 |
-
CoT representation already extracts the steering signal that matters, handing the model the raw
|
| 36 |
-
field again is redundant.
|
| 37 |
-
5. **v31–v34** — land/terrain-interaction correction, motivated by real-world reports of typhoons
|
| 38 |
-
stalling at mountainous coastlines (Typhoon Gaemi, 2024, at Taiwan) and terrain-deflection
|
| 39 |
-
literature (AOT-TCNet, arXiv 2603.29200):
|
| 40 |
-
|
| 41 |
-
| model | aggregate track (km) | Typhoon Tip 1979 (km) | Typhoon Noul 2026, ocean/landfall (km) |
|
| 42 |
-
|---|---|---|---|
|
| 43 |
-
| v23 (baseline) | **434.96** | 939 | 267 / 356 |
|
| 44 |
-
| v31 — LandDrag, uniform training | 443.07 (+8.11) | — | — |
|
| 45 |
-
| v32 — LandDrag, window-oversampled | 460.48 (+25.52, backfired) | — | — |
|
| 46 |
-
| v33 — LandDrag, storm-normalized | 442.33 (+7.37) | **876** | **243 / 319** |
|
| 47 |
-
| v34 — LandGate, **frozen v23 backbone** | 460.52 (−0.33 vs. own backbone) | **795** | 287 / 382 |
|
| 48 |
-
|
| 49 |
-
v34 is the methodologically important result: v31–v33 each retrained the *entire* architecture
|
| 50 |
-
from scratch, so their deltas vs. v23 include ~19 km/seed of ordinary retrain noise on top of
|
| 51 |
-
whatever the land correction did. v34 instead freezes a real, already-trained v23 checkpoint and
|
| 52 |
-
trains only a new ~437-parameter gated correction — a true same-backbone-plus-one-addition
|
| 53 |
-
comparison. Result: essentially null everywhere, including the mountainous-near-land regime every
|
| 54 |
-
earlier attempt targeted. On the two real out-of-training storms available, v33 and v34 each split
|
| 55 |
-
1–1 against v23 — a small-n disagreement with the aggregate test set, not a reliable effect.
|
| 56 |
-
|
| 57 |
-
**Methodological lessons:** retrain-to-retrain seed noise (~19 km/seed) is large enough to
|
| 58 |
-
manufacture or hide most small version-to-version deltas; freezing a real backbone and training
|
| 59 |
-
only a small addition isolates a causal effect that comparing two from-scratch runs cannot; and a
|
| 60 |
-
large in-distribution aggregate test set does not always agree with genuinely out-of-training
|
| 61 |
-
real-storm validation.
|
| 62 |
-
|
| 63 |
-
v23's 10-seed ensemble is released in this repo (`v23_seed0.pt`–`v23_seed9.pt`, fp32, 52.6 MB
|
| 64 |
-
each) with a standalone architecture module and CLI — see "Usage — v23" below. Full write-up,
|
| 65 |
-
architecture equations, and every intermediate result: `paper/trackformer.pdf`, "Chain-of-thought
|
| 66 |
-
steering and land-interaction testing," in the GitHub repo
|
| 67 |
-
(**https://github.com/yu314-coder/typhoon-predict**).
|
| 68 |
-
|
| 69 |
-
## Usage — v23
|
| 70 |
-
|
| 71 |
-
Two modes, controlled by whether you pass `--steering`:
|
| 72 |
-
|
| 73 |
-
**IBTrACS-only** (default) — give it nothing but the storm's own recent track (position, max wind,
|
| 74 |
-
central pressure). This is what any best-track record gives you for a storm, nothing more. The
|
| 75 |
-
steering field and its 12h/24h history are zero-filled with an explicit availability flag — the
|
| 76 |
-
same "unavailable == exact zeros, not fabricated" convention used throughout this project.
|
| 77 |
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
-
|
| 83 |
-
u/v, 2.5° resolution, ±20° box centered on the storm) for the current fix and, ideally, the two
|
| 84 |
-
fixes 12h/24h before it. This is what the headline **434.96 km** result requires.
|
| 85 |
|
| 86 |
```bash
|
| 87 |
-
python
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
```
|
| 89 |
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|---|---|---|---|
|
| 110 |
-
| **TrackFormer v9** | 17M (fp16, 33MB) | **track history + IBTrACS environment, protected triple-stream** | all basins, 1980+, 193k partial-lead windows |
|
| 111 |
-
| TrackFormer v8 | 15M (fp16, 30MB) | track history only, protected dual-stream | all basins, 1980+, 193k partial-lead windows |
|
| 112 |
-
| StormFusion-MT v2 | 3.3M (fp16, 6.7MB) | ERA5 patches + track history | WP, 2000+, 1,337 storm-centered windows |
|
| 113 |
-
| TrackFormer (v1) | 21M (fp16, 43MB) | track history only (single-stream) | all basins, 1980+, 84,150 windows |
|
| 114 |
-
|
| 115 |
-
Weights and full reproducible code (dataset builders, training, eval) are in the GitHub repo
|
| 116 |
-
(`models/`).
|
| 117 |
-
|
| 118 |
-
### Results — WP 2020+ held-out test (lower is better)
|
| 119 |
-
|
| 120 |
-
| model | track km | vmax kt | pres hPa | rmw km | radius km |
|
| 121 |
-
|---|---|---|---|---|---|
|
| 122 |
-
| StormFusion-MT v2 (3.3M, ERA5) | 729 | 24.2 | 21.6 | 16.2 | 31.8 |
|
| 123 |
-
| TrackFormer v1 (21M, single-stream) | 720 | 22.1 | 21.2 | 11.8 | 31.5 |
|
| 124 |
-
| TrackFormer v3 (15M, dual-stream) | 659 | 21.6 | 18.1 | 11.8 | 28.8 |
|
| 125 |
-
| TrackFormer v8 (15M, +partial-lead data) | 649 | 20.7 | 15.9 | **11.3** | 27.8 |
|
| 126 |
-
| **TrackFormer v9 (17M, +IBTrACS environment)** | **618** | **18.6** | 15.8 | 11.5 | **27.2** |
|
| 127 |
-
|
| 128 |
-
**Key findings.** (1) A track-only model that never sees ERA5 **matches or beats** the full ERA5
|
| 129 |
-
model, so **data diversity > engineered features > parameters** (a 17.7M ERA5 model overfit and did
|
| 130 |
-
*worse* than the 3.3M one). (2) Naively adding motion-dynamics features to a single-stream model
|
| 131 |
-
improves intensity but hurts track through **negative transfer**; **TrackFormer v3** fixes this with a
|
| 132 |
-
protected dual-stream architecture (separate kinematic/thermodynamic encoders, gradient routing, a
|
| 133 |
-
zero-init gated thermo→track adapter, and a persistence-residual track head), cutting WP-2020+ track
|
| 134 |
-
error to 659 km (−61, storm-bootstrap 95% CI [−103, −16] km, p≈0.995) while keeping the intensity
|
| 135 |
-
gains. Full architecture and derivation (incl. a random-matrix block-covariance uncertainty head) in
|
| 136 |
-
`paper/trackformer.pdf`, Appendix A, in the GitHub repo.
|
| 137 |
-
|
| 138 |
-
### Architectures
|
| 139 |
-
|
| 140 |
-
- **StormFusion-MT v2** — separate inner/outer ERA5 conv encoders keeping a 3×3 grid of spatial
|
| 141 |
-
tokens, track/environment token encoders, a temporal Transformer context, learned + sinusoidal
|
| 142 |
-
lead-time queries, cross-attention decoding, and multi-task state / log-scale heads.
|
| 143 |
-
- **TrackFormer** — the same decoder design, track-only: a 40-dim track-history projection →
|
| 144 |
-
Transformer context (d_model 384, 8 heads, 4+6 layers) → lead queries → dual heads. No
|
| 145 |
-
atmospheric inputs.
|
| 146 |
-
|
| 147 |
-
### Usage
|
| 148 |
-
|
| 149 |
-
See the GitHub repo for `model_v2.py` / `train_track.py`, the checkpoints, and normalization
|
| 150 |
-
stats. Inputs are per-feature standardized (stats saved with each checkpoint / dataset);
|
| 151 |
-
multiply predictions by `TARGET_SCALE = [100,100,35,20,50] + [50]*12` for physical units.
|
| 152 |
-
|
| 153 |
-
## Data
|
| 154 |
-
|
| 155 |
-
IBTrACS v04r01 best tracks (NOAA NCEI) and ERA5 reanalysis (Copernicus/ECMWF). Obtain the source
|
| 156 |
-
data under its own access and licensing terms.
|
| 157 |
-
|
| 158 |
-
## Limitations
|
| 159 |
-
|
| 160 |
-
- Research models throughout — not operational quality in either line. TrackFormer v23 (434.96 km
|
| 161 |
-
RMS track error, 6–120 h) and the released StormFusion-MT/TrackFormer v1–v9 checkpoints
|
| 162 |
-
(~618–730 km, different test split) are both far from operational.
|
| 163 |
-
- v24–v34 (the further additions and land-interaction line built on top of v23) remain research
|
| 164 |
-
artifacts, not packaged for this card's load/run format — only v23 itself is released.
|
| 165 |
-
- The real ceiling is storm **diversity** (~13k storms have ever existed); larger models overfit.
|
| 166 |
-
- Wind-radius labels are sparse; no calibration or comparison against official agency forecasts.
|
| 167 |
-
- Pre-satellite track/intensity labels are lower quality.
|
|
|
|
| 3 |
library_name: pytorch
|
| 4 |
pipeline_tag: time-series-forecasting
|
| 5 |
tags:
|
| 6 |
+
- tropical-cyclone
|
| 7 |
+
- weather-forecasting
|
| 8 |
+
- pytorch
|
| 9 |
+
- ibtracs
|
| 10 |
+
- causal-inference
|
| 11 |
---
|
| 12 |
|
| 13 |
+
# Trackformer1.1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
+
Trackformer1.1 is a research model for causal tropical-cyclone track,
|
| 16 |
+
pressure, intensity, and wind-structure inference over the western Pacific.
|
| 17 |
+
It is not an operational warning system and must not be used for evacuation,
|
| 18 |
+
aviation, maritime, emergency-management, or other safety-critical decisions.
|
| 19 |
+
|
| 20 |
+
## What it uses
|
| 21 |
+
|
| 22 |
+
Inference accepts the observed storm history and weather analyses available at
|
| 23 |
+
the issue time. It uses the current, 12-hour, and 24-hour analysis states to
|
| 24 |
+
extrapolate a bounded western-Pacific pressure and flow state. The route sees
|
| 25 |
+
a domain covering China, Japan, Taiwan, the Philippines, and the open western
|
| 26 |
+
Pacific, so nearby lows, subtropical ridges, troughs, jets, and other storms
|
| 27 |
+
can affect the steering field.
|
| 28 |
+
|
| 29 |
+
No positive-lead weather field, official agency forecast, or post-issue
|
| 30 |
+
observation is passed to the model. Later observations can be used only for
|
| 31 |
+
verification after a forecast has been generated.
|
| 32 |
+
|
| 33 |
+
## Model structure
|
| 34 |
+
|
| 35 |
+
1. A causal regional state module extrapolates pressure, 500 hPa height, and
|
| 36 |
+
850/500/200 hPa wind from three analysis snapshots.
|
| 37 |
+
2. A weighted steering ensemble samples inner, deep-layer, ridge, trough, and
|
| 38 |
+
jet views across the full western-Pacific domain and integrates the route.
|
| 39 |
+
3. Three primary neural experts predict maximum wind, central pressure, RMW,
|
| 40 |
+
and directional 34/50/64 kt radii from a nine-step track window and the
|
| 41 |
+
current four-channel analysis patch.
|
| 42 |
+
4. Three structure experts provide a validation-selected secondary blend.
|
| 43 |
+
5. Three temporal experts read only same-storm 12-hour and 24-hour analysis
|
| 44 |
+
patches. Their residual branch is enabled only for the validated wind
|
| 45 |
+
adjustment; pressure remains on the calibrated primary path.
|
| 46 |
+
6. The final intensity output is coupled to the causal pressure-map minimum,
|
| 47 |
+
pressure deficit, 850 hPa wind, and quadrant anomaly extent. Radius outputs
|
| 48 |
+
remain ordered as R34 >= R50 >= R64.
|
| 49 |
+
|
| 50 |
+
The public bundle contains frozen inference checkpoints and calibration state;
|
| 51 |
+
it does not include raw weather archives or a training service.
|
| 52 |
+
|
| 53 |
+
## Files
|
| 54 |
+
|
| 55 |
+
- `trackformer_1_1.py` - public loader and route API.
|
| 56 |
+
- `trackformer_1_1_route.py` - whole-domain causal pressure-state route.
|
| 57 |
+
- `trackformer_1_1_intensity.py` - intensity and wind-structure inference.
|
| 58 |
+
- `trackformer_1_1_temporal.py` - inference-only temporal expert architecture.
|
| 59 |
+
- `models/trackformer_1_1/` - three expert groups, calibration, and manifest.
|
| 60 |
|
| 61 |
+
## Quick start
|
|
|
|
|
|
|
| 62 |
|
| 63 |
```bash
|
| 64 |
+
python -m pip install -r requirements.txt
|
| 65 |
+
python - <<'PY'
|
| 66 |
+
import numpy as np
|
| 67 |
+
from trackformer_1_1 import load_intensity, forecast_pacific_state
|
| 68 |
+
|
| 69 |
+
model = load_intensity(device="cpu")
|
| 70 |
+
track = np.zeros((9, 54), dtype="float32")
|
| 71 |
+
field = np.zeros((4, 17, 17), dtype="float32")
|
| 72 |
+
current_structure = np.full(13, np.nan, dtype="float32")
|
| 73 |
+
rows, metadata = model.predict(
|
| 74 |
+
track,
|
| 75 |
+
field,
|
| 76 |
+
current_wind=65.0,
|
| 77 |
+
current_pressure=980.0,
|
| 78 |
+
previous_wind=60.0,
|
| 79 |
+
previous_pressure=985.0,
|
| 80 |
+
current_structure=current_structure,
|
| 81 |
+
)
|
| 82 |
+
print(rows[0])
|
| 83 |
+
print(metadata["model"])
|
| 84 |
+
PY
|
| 85 |
```
|
| 86 |
|
| 87 |
+
The intensity contract is `track=(9,54)`, `field=(4,17,17)`, and optional
|
| 88 |
+
`history_field=(8,17,17)` for the 12-hour and 24-hour analysis patches. The
|
| 89 |
+
route contract is `fields=(3,7,latitude,longitude)` and
|
| 90 |
+
`pressure=(3,latitude,longitude)`, ordered current, 12 hours before, and 24
|
| 91 |
+
hours before. See `models/trackformer_1_1/manifest.json` for the complete
|
| 92 |
+
contract.
|
| 93 |
+
|
| 94 |
+
## Requirements and limits
|
| 95 |
+
|
| 96 |
+
CPU inference works with Python 3.10+, NumPy, PyTorch, and SciPy. A modern
|
| 97 |
+
Mac with 8 GB RAM is a practical minimum; 16 GB RAM is preferable when
|
| 98 |
+
decoding large analysis grids. The checkpoints are native PyTorch files.
|
| 99 |
+
GGUF is not an appropriate format for this CNN/Transformer weather model;
|
| 100 |
+
keep the native PyTorch weights or convert to another format only after
|
| 101 |
+
numerical parity testing.
|
| 102 |
+
|
| 103 |
+
The model is experimental. Track and intensity errors vary by storm, basin,
|
| 104 |
+
analysis source, and data quality. Use official meteorological agencies for
|
| 105 |
+
real-world forecasts and warnings.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
forecast.json
DELETED
|
@@ -1,74 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"storm": "Bavi",
|
| 3 |
-
"source": "JTWC/TCGP fixes through 2026-07-10 0600 UTC",
|
| 4 |
-
"initial_time": "2026-07-10T06:00:00Z",
|
| 5 |
-
"initial_lat": 21.9,
|
| 6 |
-
"initial_lon": 126.9,
|
| 7 |
-
"points": [
|
| 8 |
-
{
|
| 9 |
-
"lead_hours": 6,
|
| 10 |
-
"lat": 22.466064453125,
|
| 11 |
-
"lon": 126.55152130126953,
|
| 12 |
-
"p10_lat": 22.288002014160156,
|
| 13 |
-
"p90_lat": 22.62434959411621,
|
| 14 |
-
"p10_lon": 126.4314956665039,
|
| 15 |
-
"p90_lon": 126.70477294921875
|
| 16 |
-
},
|
| 17 |
-
{
|
| 18 |
-
"lead_hours": 12,
|
| 19 |
-
"lat": 22.821269989013672,
|
| 20 |
-
"lon": 126.27210998535156,
|
| 21 |
-
"p10_lat": 22.475460052490234,
|
| 22 |
-
"p90_lat": 23.098590850830078,
|
| 23 |
-
"p10_lon": 126.02592468261719,
|
| 24 |
-
"p90_lon": 126.5489273071289
|
| 25 |
-
},
|
| 26 |
-
{
|
| 27 |
-
"lead_hours": 24,
|
| 28 |
-
"lat": 23.898107528686523,
|
| 29 |
-
"lon": 125.54524230957031,
|
| 30 |
-
"p10_lat": 23.513370513916016,
|
| 31 |
-
"p90_lat": 24.373027801513672,
|
| 32 |
-
"p10_lon": 124.97864532470703,
|
| 33 |
-
"p90_lon": 126.13536834716797
|
| 34 |
-
},
|
| 35 |
-
{
|
| 36 |
-
"lead_hours": 48,
|
| 37 |
-
"lat": 25.304624557495117,
|
| 38 |
-
"lon": 124.66926574707031,
|
| 39 |
-
"p10_lat": 24.34754180908203,
|
| 40 |
-
"p90_lat": 26.398544311523438,
|
| 41 |
-
"p10_lon": 123.34161376953125,
|
| 42 |
-
"p90_lon": 125.87503814697266
|
| 43 |
-
},
|
| 44 |
-
{
|
| 45 |
-
"lead_hours": 72,
|
| 46 |
-
"lat": 26.52523422241211,
|
| 47 |
-
"lon": 123.29611206054688,
|
| 48 |
-
"p10_lat": 24.960338592529297,
|
| 49 |
-
"p90_lat": 28.163558959960938,
|
| 50 |
-
"p10_lon": 120.4675064086914,
|
| 51 |
-
"p90_lon": 125.69815826416016
|
| 52 |
-
},
|
| 53 |
-
{
|
| 54 |
-
"lead_hours": 96,
|
| 55 |
-
"lat": 26.998743057250977,
|
| 56 |
-
"lon": 122.69322204589844,
|
| 57 |
-
"p10_lat": 24.666839599609375,
|
| 58 |
-
"p90_lat": 29.167266845703125,
|
| 59 |
-
"p10_lon": 119.0267562866211,
|
| 60 |
-
"p90_lon": 125.30994415283203
|
| 61 |
-
},
|
| 62 |
-
{
|
| 63 |
-
"lead_hours": 120,
|
| 64 |
-
"lat": 28.687833786010742,
|
| 65 |
-
"lon": 121.19312286376953,
|
| 66 |
-
"p10_lat": 26.278013229370117,
|
| 67 |
-
"p90_lat": 31.288469314575195,
|
| 68 |
-
"p10_lon": 115.32806396484375,
|
| 69 |
-
"p90_lon": 125.7118911743164
|
| 70 |
-
}
|
| 71 |
-
],
|
| 72 |
-
"device": "mps",
|
| 73 |
-
"note": "Mac checkpoint inference using current track fixes and mean-normalized atmospheric input because 2026 ERA5 fields are unavailable locally. Not an operational forecast."
|
| 74 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
forecast_world_map.html
DELETED
|
@@ -1,61 +0,0 @@
|
|
| 1 |
-
<!doctype html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="utf-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 6 |
-
<title>Bavi Mac Model Forecast</title>
|
| 7 |
-
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
| 8 |
-
<style>
|
| 9 |
-
html, body, #map { height: 100%; margin: 0; }
|
| 10 |
-
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
| 11 |
-
.panel { position: absolute; z-index: 1000; top: 16px; left: 16px; width: min(360px, calc(100vw - 32px)); padding: 14px 16px; background: rgba(255,255,255,.96); border: 1px solid #d5d9df; box-shadow: 0 3px 14px rgba(0,0,0,.18); }
|
| 12 |
-
h1 { margin: 0 0 7px; font-size: 20px; }
|
| 13 |
-
p { margin: 5px 0; font-size: 13px; line-height: 1.4; color: #374151; }
|
| 14 |
-
.legend { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 9px; font-size: 12px; color: #374151; }
|
| 15 |
-
.key { display: inline-flex; align-items: center; gap: 5px; }
|
| 16 |
-
.swatch { width: 20px; height: 3px; display: inline-block; }
|
| 17 |
-
.mean { background: #d7263d; }
|
| 18 |
-
.actual { background: #111827; }
|
| 19 |
-
.corridor { width: 20px; height: 10px; background: rgba(245,158,11,.32); border: 1px solid #d97706; }
|
| 20 |
-
</style>
|
| 21 |
-
</head>
|
| 22 |
-
<body>
|
| 23 |
-
<div id="map"></div>
|
| 24 |
-
<section class="panel">
|
| 25 |
-
<h1>Bavi: Mac checkpoint prediction</h1>
|
| 26 |
-
<p><b>Initial fix:</b> 10 Jul 2026 06:00 UTC, 21.9°N 126.9°E</p>
|
| 27 |
-
<p><b>Run:</b> local Mac Apple MPS, 50 ensemble members, 120-hour horizon</p>
|
| 28 |
-
<p><b>Important:</b> this is a research proxy using a mean-normalized atmospheric field because the matching 2026 ERA5 field is not available locally. It is not an operational warning forecast.</p>
|
| 29 |
-
<div class="legend">
|
| 30 |
-
<span class="key"><i class="swatch actual"></i>official initial fix</span>
|
| 31 |
-
<span class="key"><i class="swatch mean"></i>model mean</span>
|
| 32 |
-
<span class="key"><i class="swatch corridor"></i>10–90% corridor</span>
|
| 33 |
-
</div>
|
| 34 |
-
</section>
|
| 35 |
-
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
| 36 |
-
<script>
|
| 37 |
-
const initial = [21.9, 126.9];
|
| 38 |
-
const forecast = [
|
| 39 |
-
{h: 6, lat: 22.4336, lon: 126.5818, la: 22.3279, lb: 22.5795, oa: 126.4390, ob: 126.7272},
|
| 40 |
-
{h: 12, lat: 22.7873, lon: 126.3022, la: 22.5105, lb: 23.0514, oa: 126.0263, ob: 126.6464},
|
| 41 |
-
{h: 24, lat: 23.8919, lon: 125.4258, la: 23.5613, lb: 24.3624, oa: 124.9874, ob: 125.9527},
|
| 42 |
-
{h: 48, lat: 25.6544, lon: 124.6313, la: 24.6905, lb: 26.3908, oa: 123.1265, ob: 126.0750},
|
| 43 |
-
{h: 72, lat: 26.4361, lon: 123.5473, la: 24.9631, lb: 28.2892, oa: 120.7869, ob: 125.8778},
|
| 44 |
-
{h: 96, lat: 26.6257, lon: 121.9343, la: 24.2808, lb: 28.4809, oa: 118.0243, ob: 126.7538},
|
| 45 |
-
{h: 120, lat: 28.6126, lon: 121.8454, la: 26.2118, lb: 30.8639, oa: 117.2112, ob: 126.6462}
|
| 46 |
-
];
|
| 47 |
-
const map = L.map('map', {worldCopyJump: true}).setView([25, 126], 4);
|
| 48 |
-
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {maxZoom: 18, attribution: '© OpenStreetMap contributors'}).addTo(map);
|
| 49 |
-
L.marker(initial).addTo(map).bindPopup('<b>Official initial fix</b><br>21.9°N, 126.9°E<br>10 Jul 2026 06:00 UTC').openPopup();
|
| 50 |
-
const meanLine = [initial];
|
| 51 |
-
forecast.forEach(p => {
|
| 52 |
-
meanLine.push([p.lat, p.lon]);
|
| 53 |
-
L.rectangle([[p.la, p.oa], [p.lb, p.ob]], {color:'#d97706', weight:1, fillColor:'#f59e0b', fillOpacity:.18}).addTo(map).bindTooltip('+' + p.h + 'h model 10–90% box');
|
| 54 |
-
L.circleMarker([p.lat, p.lon], {radius: 5, color:'#d7263d', fillColor:'#d7263d', fillOpacity:1}).addTo(map).bindTooltip('+' + p.h + 'h: ' + p.lat.toFixed(2) + '°N, ' + p.lon.toFixed(2) + '°E');
|
| 55 |
-
});
|
| 56 |
-
L.polyline(meanLine, {color:'#d7263d', weight:4, opacity:.9}).addTo(map);
|
| 57 |
-
L.polyline([initial, [21.9,126.9]], {color:'#111827', weight:5}).addTo(map);
|
| 58 |
-
map.fitBounds(L.latLngBounds(meanLine), {padding: [45, 45]});
|
| 59 |
-
</script>
|
| 60 |
-
</body>
|
| 61 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
models/trackformer_1_1/manifest.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Trackformer1.1",
|
| 3 |
+
"task": "causal western-Pacific tropical-cyclone track, pressure, intensity, and wind-structure research inference",
|
| 4 |
+
"model_type": "regional analysis-state route plus spatial/temporal neural structure ensemble",
|
| 5 |
+
"checkpoint_count": {
|
| 6 |
+
"primary_intensity": 3,
|
| 7 |
+
"structure_experts": 3,
|
| 8 |
+
"temporal_experts": 3
|
| 9 |
+
},
|
| 10 |
+
"training_data": {
|
| 11 |
+
"track": "IBTrACS best-track history",
|
| 12 |
+
"weather": "analysis/reanalysis fields only",
|
| 13 |
+
"split": "storm-held-out chronological train/validation/test split",
|
| 14 |
+
"future_weather_as_input": false,
|
| 15 |
+
"official_forecast_as_input": false
|
| 16 |
+
},
|
| 17 |
+
"track_input_shape": [9, 54],
|
| 18 |
+
"intensity_field_shape": [4, 17, 17],
|
| 19 |
+
"history_field_shape": [8, 17, 17],
|
| 20 |
+
"route_field_layout": "[snapshots=3, channels=7, latitude, longitude] with hgt500,u/v850,u/v500,u/v200",
|
| 21 |
+
"lead_hours": [6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120],
|
| 22 |
+
"outputs": [
|
| 23 |
+
"track latitude/longitude",
|
| 24 |
+
"maximum sustained wind",
|
| 25 |
+
"central pressure",
|
| 26 |
+
"radius of maximum wind",
|
| 27 |
+
"34/50/64 kt wind radii by quadrant"
|
| 28 |
+
],
|
| 29 |
+
"native_distance_unit": "nautical miles",
|
| 30 |
+
"public_distance_unit": "kilometres",
|
| 31 |
+
"license": "MIT",
|
| 32 |
+
"warning": "Research output only; not an operational warning or safety-critical forecast."
|
| 33 |
+
}
|
models/trackformer_1_1/trackformer_1_1_calibration.json
ADDED
|
@@ -0,0 +1,2180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"version": "Trackformer1.1-calibration",
|
| 3 |
+
"created_utc": "2026-08-04T03:35:06.917313+00:00",
|
| 4 |
+
"fit_split": "val (storm-held-out 2016-2019)",
|
| 5 |
+
"evaluation_split": "test (storm-held-out 2020+)",
|
| 6 |
+
"features": [
|
| 7 |
+
"bias",
|
| 8 |
+
"current_pressure_hpa",
|
| 9 |
+
"current_wind_kt",
|
| 10 |
+
"primary_predicted_wind_kt",
|
| 11 |
+
"predicted_wind_minus_current_wind_kt",
|
| 12 |
+
"current_pressure_minus_previous_pressure_hpa",
|
| 13 |
+
"current_wind_minus_previous_wind_kt"
|
| 14 |
+
],
|
| 15 |
+
"wind_target_convention": "same as Trackformer1.1 model output (1-minute estimate); blend uses current wind as an observed forecast-time anchor",
|
| 16 |
+
"checkpoint_count": 3,
|
| 17 |
+
"metrics": {
|
| 18 |
+
"raw_val_pressure_mae_all_leads_hpa": 12.745838165283203,
|
| 19 |
+
"calibrated_val_pressure_mae_all_leads_hpa": 13.02608874699582,
|
| 20 |
+
"raw_test_pressure_mae_all_leads_hpa": 12.803126335144043,
|
| 21 |
+
"calibrated_test_pressure_mae_all_leads_hpa": 12.420955012285965,
|
| 22 |
+
"joint_calibrated_val_pressure_mae_all_leads_hpa": 12.730707934229281,
|
| 23 |
+
"joint_calibrated_test_pressure_mae_all_leads_hpa": 12.230934403282232,
|
| 24 |
+
"raw_val_pressure_mae_120h_hpa": 16.14628028869629,
|
| 25 |
+
"calibrated_val_pressure_mae_120h_hpa": 15.681344145679113,
|
| 26 |
+
"raw_test_pressure_mae_120h_hpa": 16.42153549194336,
|
| 27 |
+
"calibrated_test_pressure_mae_120h_hpa": 14.524238379900986,
|
| 28 |
+
"joint_calibrated_val_pressure_mae_120h_hpa": 15.545918282225045,
|
| 29 |
+
"joint_calibrated_test_pressure_mae_120h_hpa": 14.458328501318364,
|
| 30 |
+
"pressure_anchored_val_pressure_mae_all_leads_hpa": 12.587559582395707,
|
| 31 |
+
"pressure_anchored_test_pressure_mae_all_leads_hpa": 11.99345142652379,
|
| 32 |
+
"pressure_anchored_val_pressure_mae_120h_hpa": 15.544537539571403,
|
| 33 |
+
"pressure_anchored_test_pressure_mae_120h_hpa": 14.435201089126327,
|
| 34 |
+
"raw_val_wind_mae_all_leads_kt": 17.10424041748047,
|
| 35 |
+
"calibrated_val_wind_mae_all_leads_kt": 16.91584014892578,
|
| 36 |
+
"raw_test_wind_mae_all_leads_kt": 16.560529708862305,
|
| 37 |
+
"calibrated_test_wind_mae_all_leads_kt": 16.396728515625,
|
| 38 |
+
"raw_val_wind_mae_120h_kt": 21.62657356262207,
|
| 39 |
+
"calibrated_val_wind_mae_120h_kt": 21.440736770629883,
|
| 40 |
+
"raw_test_wind_mae_120h_kt": 20.42694664001465,
|
| 41 |
+
"calibrated_test_wind_mae_120h_kt": 20.36166763305664,
|
| 42 |
+
"raw_structure_mae_120h_native": [
|
| 43 |
+
13.61,
|
| 44 |
+
34.27,
|
| 45 |
+
35.875,
|
| 46 |
+
31.447,
|
| 47 |
+
32.959,
|
| 48 |
+
18.523,
|
| 49 |
+
19.544,
|
| 50 |
+
17.72,
|
| 51 |
+
17.436,
|
| 52 |
+
11.272,
|
| 53 |
+
11.788,
|
| 54 |
+
10.746,
|
| 55 |
+
10.529
|
| 56 |
+
],
|
| 57 |
+
"blended_structure_mae_120h_native": [
|
| 58 |
+
13.615,
|
| 59 |
+
34.109,
|
| 60 |
+
35.432,
|
| 61 |
+
31.447,
|
| 62 |
+
32.737,
|
| 63 |
+
18.626,
|
| 64 |
+
19.763,
|
| 65 |
+
17.891,
|
| 66 |
+
17.661,
|
| 67 |
+
11.29,
|
| 68 |
+
11.747,
|
| 69 |
+
10.746,
|
| 70 |
+
10.475
|
| 71 |
+
]
|
| 72 |
+
},
|
| 73 |
+
"calibrations": [
|
| 74 |
+
{
|
| 75 |
+
"beta": [
|
| 76 |
+
979.3647389439432,
|
| 77 |
+
1.9158192596454662,
|
| 78 |
+
-10.625316061573692,
|
| 79 |
+
-10.76484441891422,
|
| 80 |
+
1.4216365382963003,
|
| 81 |
+
0.355287389506127,
|
| 82 |
+
-2.653478803931415
|
| 83 |
+
],
|
| 84 |
+
"mean": [
|
| 85 |
+
0.0,
|
| 86 |
+
978.1810383079426,
|
| 87 |
+
64.5815707735542,
|
| 88 |
+
65.59845112223823,
|
| 89 |
+
1.0168803486840257,
|
| 90 |
+
1.905043632598728,
|
| 91 |
+
1.4701967164620617
|
| 92 |
+
],
|
| 93 |
+
"scale": [
|
| 94 |
+
1.0,
|
| 95 |
+
50.53637228468569,
|
| 96 |
+
33.10783692877566,
|
| 97 |
+
32.14420199228587,
|
| 98 |
+
4.047375438494807,
|
| 99 |
+
54.79089485626104,
|
| 100 |
+
6.246907173086729
|
| 101 |
+
]
|
| 102 |
+
},
|
| 103 |
+
{
|
| 104 |
+
"beta": [
|
| 105 |
+
978.6045857988166,
|
| 106 |
+
1.159678080634184,
|
| 107 |
+
-10.476473797695414,
|
| 108 |
+
-11.027839130141558,
|
| 109 |
+
0.25385328395687057,
|
| 110 |
+
0.45833242498682186,
|
| 111 |
+
-3.181226204489663
|
| 112 |
+
],
|
| 113 |
+
"mean": [
|
| 114 |
+
0.0,
|
| 115 |
+
976.7190828402366,
|
| 116 |
+
64.52144970414201,
|
| 117 |
+
65.9957684040775,
|
| 118 |
+
1.4743186999354843,
|
| 119 |
+
1.9063609467455622,
|
| 120 |
+
1.4738165680473372
|
| 121 |
+
],
|
| 122 |
+
"scale": [
|
| 123 |
+
1.0,
|
| 124 |
+
62.987212868626365,
|
| 125 |
+
33.18977815879015,
|
| 126 |
+
31.37842015427967,
|
| 127 |
+
6.600915311239932,
|
| 128 |
+
54.79480783300083,
|
| 129 |
+
6.245251042331157
|
| 130 |
+
]
|
| 131 |
+
},
|
| 132 |
+
{
|
| 133 |
+
"beta": [
|
| 134 |
+
978.0161338069863,
|
| 135 |
+
0.6738374269289117,
|
| 136 |
+
-10.062859593303838,
|
| 137 |
+
-11.437666297247567,
|
| 138 |
+
-1.0826424344554468,
|
| 139 |
+
0.3649143931938072,
|
| 140 |
+
-2.4904427275749708
|
| 141 |
+
],
|
| 142 |
+
"mean": [
|
| 143 |
+
0.0,
|
| 144 |
+
975.2491119005329,
|
| 145 |
+
64.46758436944938,
|
| 146 |
+
65.93912073678699,
|
| 147 |
+
1.4715363673376147,
|
| 148 |
+
1.7575488454706927,
|
| 149 |
+
1.4773534635879217
|
| 150 |
+
],
|
| 151 |
+
"scale": [
|
| 152 |
+
1.0,
|
| 153 |
+
73.34317349947774,
|
| 154 |
+
33.277770226738575,
|
| 155 |
+
30.210884717895517,
|
| 156 |
+
9.857814699499565,
|
| 157 |
+
56.14964419892791,
|
| 158 |
+
6.253661273135333
|
| 159 |
+
]
|
| 160 |
+
},
|
| 161 |
+
{
|
| 162 |
+
"beta": [
|
| 163 |
+
977.6126259632483,
|
| 164 |
+
0.3647266027866861,
|
| 165 |
+
-9.488025865326827,
|
| 166 |
+
-11.892068495452548,
|
| 167 |
+
-2.183654318457364,
|
| 168 |
+
0.26197009521576825,
|
| 169 |
+
-1.7017446652271828
|
| 170 |
+
],
|
| 171 |
+
"mean": [
|
| 172 |
+
0.0,
|
| 173 |
+
973.9270895080024,
|
| 174 |
+
64.41123295791346,
|
| 175 |
+
65.77106518447222,
|
| 176 |
+
1.359832226558769,
|
| 177 |
+
1.5952874925903973,
|
| 178 |
+
1.4903675163011263
|
| 179 |
+
],
|
| 180 |
+
"scale": [
|
| 181 |
+
1.0,
|
| 182 |
+
81.55905643779808,
|
| 183 |
+
33.37057257362826,
|
| 184 |
+
28.96361227936494,
|
| 185 |
+
12.738465555338884,
|
| 186 |
+
57.48200889919564,
|
| 187 |
+
6.2549372779430845
|
| 188 |
+
]
|
| 189 |
+
},
|
| 190 |
+
{
|
| 191 |
+
"beta": [
|
| 192 |
+
977.3788958147819,
|
| 193 |
+
0.22978396861298192,
|
| 194 |
+
-8.819607237745391,
|
| 195 |
+
-12.14781196679265,
|
| 196 |
+
-2.743935857195942,
|
| 197 |
+
0.04992926797301592,
|
| 198 |
+
-1.3349625849092375
|
| 199 |
+
],
|
| 200 |
+
"mean": [
|
| 201 |
+
0.0,
|
| 202 |
+
972.7582368655387,
|
| 203 |
+
64.35366577619472,
|
| 204 |
+
65.45164637299878,
|
| 205 |
+
1.0979805968040597,
|
| 206 |
+
1.5890471950133571,
|
| 207 |
+
1.5031166518254675
|
| 208 |
+
],
|
| 209 |
+
"scale": [
|
| 210 |
+
1.0,
|
| 211 |
+
88.23866281788312,
|
| 212 |
+
33.456506355578135,
|
| 213 |
+
27.75689837682731,
|
| 214 |
+
15.347421693937026,
|
| 215 |
+
57.524265495181936,
|
| 216 |
+
6.247664265837107
|
| 217 |
+
]
|
| 218 |
+
},
|
| 219 |
+
{
|
| 220 |
+
"beta": [
|
| 221 |
+
977.2594465932758,
|
| 222 |
+
0.2760853766109035,
|
| 223 |
+
-8.128811856734984,
|
| 224 |
+
-12.238453665761691,
|
| 225 |
+
-2.9696078556524057,
|
| 226 |
+
-0.03436852261542168,
|
| 227 |
+
-1.1949635193531134
|
| 228 |
+
],
|
| 229 |
+
"mean": [
|
| 230 |
+
0.0,
|
| 231 |
+
972.4617673311515,
|
| 232 |
+
64.34275513240107,
|
| 233 |
+
64.98030532864153,
|
| 234 |
+
0.6375501962404668,
|
| 235 |
+
1.584498661112764,
|
| 236 |
+
1.5165129425766142
|
| 237 |
+
],
|
| 238 |
+
"scale": [
|
| 239 |
+
1.0,
|
| 240 |
+
89.9186470511227,
|
| 241 |
+
33.49768138488822,
|
| 242 |
+
26.56546307724779,
|
| 243 |
+
17.78815483866843,
|
| 244 |
+
57.59243440899489,
|
| 245 |
+
6.244839191073782
|
| 246 |
+
]
|
| 247 |
+
},
|
| 248 |
+
{
|
| 249 |
+
"beta": [
|
| 250 |
+
977.275208830549,
|
| 251 |
+
0.3302926221894572,
|
| 252 |
+
-7.4827672649673,
|
| 253 |
+
-12.181590337721174,
|
| 254 |
+
-2.958535712519607,
|
| 255 |
+
-0.05888547667459402,
|
| 256 |
+
-1.1528998888740287
|
| 257 |
+
],
|
| 258 |
+
"mean": [
|
| 259 |
+
0.0,
|
| 260 |
+
972.166915274463,
|
| 261 |
+
64.32801312649164,
|
| 262 |
+
64.60189941317412,
|
| 263 |
+
0.2738862863979647,
|
| 264 |
+
1.5853221957040573,
|
| 265 |
+
1.527744630071599
|
| 266 |
+
],
|
| 267 |
+
"scale": [
|
| 268 |
+
1.0,
|
| 269 |
+
91.5873950283228,
|
| 270 |
+
33.53705182833371,
|
| 271 |
+
25.505423746027805,
|
| 272 |
+
20.194468969727616,
|
| 273 |
+
57.6690743414348,
|
| 274 |
+
6.241065684632235
|
| 275 |
+
]
|
| 276 |
+
},
|
| 277 |
+
{
|
| 278 |
+
"beta": [
|
| 279 |
+
977.4265828468792,
|
| 280 |
+
0.3456389495464159,
|
| 281 |
+
-6.887855834667866,
|
| 282 |
+
-11.982728429603078,
|
| 283 |
+
-2.8483573166969975,
|
| 284 |
+
-0.01676464039392852,
|
| 285 |
+
-1.155605952556869
|
| 286 |
+
],
|
| 287 |
+
"mean": [
|
| 288 |
+
0.0,
|
| 289 |
+
971.8718754677443,
|
| 290 |
+
64.30399640772339,
|
| 291 |
+
63.97318780188467,
|
| 292 |
+
-0.3308086069806788,
|
| 293 |
+
1.5837449483610238,
|
| 294 |
+
1.545577009429726
|
| 295 |
+
],
|
| 296 |
+
"scale": [
|
| 297 |
+
1.0,
|
| 298 |
+
93.26701702857976,
|
| 299 |
+
33.579947436293786,
|
| 300 |
+
24.620441527249962,
|
| 301 |
+
22.370585532622172,
|
| 302 |
+
57.76717976524845,
|
| 303 |
+
6.2318182994660445
|
| 304 |
+
]
|
| 305 |
+
},
|
| 306 |
+
{
|
| 307 |
+
"beta": [
|
| 308 |
+
977.7077824519231,
|
| 309 |
+
0.3742494390176481,
|
| 310 |
+
-6.340741475621145,
|
| 311 |
+
-11.754265657938584,
|
| 312 |
+
-2.6828266265386937,
|
| 313 |
+
0.016897803479940448,
|
| 314 |
+
-1.1134641714774627
|
| 315 |
+
],
|
| 316 |
+
"mean": [
|
| 317 |
+
0.0,
|
| 318 |
+
971.5954026442307,
|
| 319 |
+
64.2549579326923,
|
| 320 |
+
62.96718305378006,
|
| 321 |
+
-1.2877748783391256,
|
| 322 |
+
1.5760216346153846,
|
| 323 |
+
1.5689603365384615
|
| 324 |
+
],
|
| 325 |
+
"scale": [
|
| 326 |
+
1.0,
|
| 327 |
+
94.93760398672073,
|
| 328 |
+
33.613050887448374,
|
| 329 |
+
23.706436575938007,
|
| 330 |
+
24.41700063160801,
|
| 331 |
+
57.87417128115503,
|
| 332 |
+
6.217322594269493
|
| 333 |
+
]
|
| 334 |
+
},
|
| 335 |
+
{
|
| 336 |
+
"beta": [
|
| 337 |
+
978.1375037775763,
|
| 338 |
+
0.4649928695065894,
|
| 339 |
+
-5.8395505850139715,
|
| 340 |
+
-11.477235869564007,
|
| 341 |
+
-2.4819997208339086,
|
| 342 |
+
0.10752048000060792,
|
| 343 |
+
-1.1103025131158928
|
| 344 |
+
],
|
| 345 |
+
"mean": [
|
| 346 |
+
0.0,
|
| 347 |
+
971.3303112722876,
|
| 348 |
+
64.17527954064673,
|
| 349 |
+
62.03331732324835,
|
| 350 |
+
-2.1419622182629916,
|
| 351 |
+
1.5754004230885463,
|
| 352 |
+
1.5929283771532186
|
| 353 |
+
],
|
| 354 |
+
"scale": [
|
| 355 |
+
1.0,
|
| 356 |
+
96.6811703822946,
|
| 357 |
+
33.65355257692086,
|
| 358 |
+
22.79463316794241,
|
| 359 |
+
26.2208909033306,
|
| 360 |
+
58.03843631538493,
|
| 361 |
+
6.204621897205797
|
| 362 |
+
]
|
| 363 |
+
},
|
| 364 |
+
{
|
| 365 |
+
"beta": [
|
| 366 |
+
978.6816245816854,
|
| 367 |
+
0.6120426122179327,
|
| 368 |
+
-5.393762657505621,
|
| 369 |
+
-11.16348574493176,
|
| 370 |
+
-2.2680355887737136,
|
| 371 |
+
0.21808458899551153,
|
| 372 |
+
-1.107812340721413
|
| 373 |
+
],
|
| 374 |
+
"mean": [
|
| 375 |
+
0.0,
|
| 376 |
+
971.0885305749924,
|
| 377 |
+
64.04745968968665,
|
| 378 |
+
60.8586191099612,
|
| 379 |
+
-3.1888405779846396,
|
| 380 |
+
1.5728627928202008,
|
| 381 |
+
1.6186492242166108
|
| 382 |
+
],
|
| 383 |
+
"scale": [
|
| 384 |
+
1.0,
|
| 385 |
+
98.45100928052985,
|
| 386 |
+
33.671915141098914,
|
| 387 |
+
21.924313547692126,
|
| 388 |
+
27.825420835081715,
|
| 389 |
+
58.230564926174964,
|
| 390 |
+
6.192075094590328
|
| 391 |
+
]
|
| 392 |
+
},
|
| 393 |
+
{
|
| 394 |
+
"beta": [
|
| 395 |
+
979.3450283438027,
|
| 396 |
+
0.7319180819424854,
|
| 397 |
+
-5.008376019672533,
|
| 398 |
+
-10.768778319983676,
|
| 399 |
+
-2.0429282545741065,
|
| 400 |
+
0.3674105985052596,
|
| 401 |
+
-1.2015351802290106
|
| 402 |
+
],
|
| 403 |
+
"mean": [
|
| 404 |
+
0.0,
|
| 405 |
+
970.8610387620653,
|
| 406 |
+
63.89229354986977,
|
| 407 |
+
59.479008114427415,
|
| 408 |
+
-4.413285438072377,
|
| 409 |
+
1.5874061590317143,
|
| 410 |
+
1.6341351309943313
|
| 411 |
+
],
|
| 412 |
+
"scale": [
|
| 413 |
+
1.0,
|
| 414 |
+
100.23062800348737,
|
| 415 |
+
33.674191443192385,
|
| 416 |
+
21.208226375492284,
|
| 417 |
+
29.24792990211032,
|
| 418 |
+
58.4371110933118,
|
| 419 |
+
6.184937924572314
|
| 420 |
+
]
|
| 421 |
+
},
|
| 422 |
+
{
|
| 423 |
+
"beta": [
|
| 424 |
+
980.105604446503,
|
| 425 |
+
0.7666194688253678,
|
| 426 |
+
-4.689530902345973,
|
| 427 |
+
-10.289081391756739,
|
| 428 |
+
-1.8255623125939082,
|
| 429 |
+
0.4955171144086594,
|
| 430 |
+
-1.3491949486587504
|
| 431 |
+
],
|
| 432 |
+
"mean": [
|
| 433 |
+
0.0,
|
| 434 |
+
970.6553960166743,
|
| 435 |
+
63.68905357418558,
|
| 436 |
+
58.03452988909188,
|
| 437 |
+
-5.654523683032338,
|
| 438 |
+
1.6220472440944882,
|
| 439 |
+
1.6265246255982708
|
| 440 |
+
],
|
| 441 |
+
"scale": [
|
| 442 |
+
1.0,
|
| 443 |
+
102.0201408123609,
|
| 444 |
+
33.62765022480105,
|
| 445 |
+
20.715588686897885,
|
| 446 |
+
30.3788753865059,
|
| 447 |
+
58.65838429251217,
|
| 448 |
+
6.17016637339649
|
| 449 |
+
]
|
| 450 |
+
},
|
| 451 |
+
{
|
| 452 |
+
"beta": [
|
| 453 |
+
980.9283154121864,
|
| 454 |
+
0.7315739553554528,
|
| 455 |
+
-4.397436869389718,
|
| 456 |
+
-9.8222155905967,
|
| 457 |
+
-1.6061360661083666,
|
| 458 |
+
0.6129502278031091,
|
| 459 |
+
-1.459642620721356
|
| 460 |
+
],
|
| 461 |
+
"mean": [
|
| 462 |
+
0.0,
|
| 463 |
+
970.4040829047842,
|
| 464 |
+
63.52236247467664,
|
| 465 |
+
56.605309408885475,
|
| 466 |
+
-6.917053074410943,
|
| 467 |
+
1.6501480442574412,
|
| 468 |
+
1.619448340345956
|
| 469 |
+
],
|
| 470 |
+
"scale": [
|
| 471 |
+
1.0,
|
| 472 |
+
103.88514324892287,
|
| 473 |
+
33.61229933848826,
|
| 474 |
+
20.186672231267604,
|
| 475 |
+
31.42790190641402,
|
| 476 |
+
58.92879090161479,
|
| 477 |
+
6.170992037104559
|
| 478 |
+
]
|
| 479 |
+
},
|
| 480 |
+
{
|
| 481 |
+
"beta": [
|
| 482 |
+
981.8252993068684,
|
| 483 |
+
0.5928998786610551,
|
| 484 |
+
-4.101469153326283,
|
| 485 |
+
-9.42271938876919,
|
| 486 |
+
-1.4771057284324305,
|
| 487 |
+
0.663139976586497,
|
| 488 |
+
-1.582271831745498
|
| 489 |
+
],
|
| 490 |
+
"mean": [
|
| 491 |
+
0.0,
|
| 492 |
+
970.11468178954,
|
| 493 |
+
63.39004410838059,
|
| 494 |
+
55.095669031969464,
|
| 495 |
+
-8.29437507190416,
|
| 496 |
+
1.6852551984877127,
|
| 497 |
+
1.613421550094518
|
| 498 |
+
],
|
| 499 |
+
"scale": [
|
| 500 |
+
1.0,
|
| 501 |
+
105.82467616006231,
|
| 502 |
+
33.61784905160175,
|
| 503 |
+
19.689630541879367,
|
| 504 |
+
32.26821406536604,
|
| 505 |
+
59.243797369533446,
|
| 506 |
+
6.163178774221445
|
| 507 |
+
]
|
| 508 |
+
},
|
| 509 |
+
{
|
| 510 |
+
"beta": [
|
| 511 |
+
982.781539444267,
|
| 512 |
+
0.6025154669117084,
|
| 513 |
+
-3.823209689209051,
|
| 514 |
+
-9.064186918081038,
|
| 515 |
+
-1.3626446088366062,
|
| 516 |
+
0.659595056954032,
|
| 517 |
+
-1.7078633128626304
|
| 518 |
+
],
|
| 519 |
+
"mean": [
|
| 520 |
+
0.0,
|
| 521 |
+
970.1533056531459,
|
| 522 |
+
63.22868093260939,
|
| 523 |
+
53.804770275488785,
|
| 524 |
+
-9.423910663973901,
|
| 525 |
+
1.7376237623762376,
|
| 526 |
+
1.6045991695943789
|
| 527 |
+
],
|
| 528 |
+
"scale": [
|
| 529 |
+
1.0,
|
| 530 |
+
106.49924076137351,
|
| 531 |
+
33.55507285388263,
|
| 532 |
+
19.118239462204585,
|
| 533 |
+
32.921422156523604,
|
| 534 |
+
59.64370137739178,
|
| 535 |
+
6.154107714919649
|
| 536 |
+
]
|
| 537 |
+
},
|
| 538 |
+
{
|
| 539 |
+
"beta": [
|
| 540 |
+
983.7722258273848,
|
| 541 |
+
0.6687148174280548,
|
| 542 |
+
-3.5729812231535414,
|
| 543 |
+
-8.792480353894183,
|
| 544 |
+
-1.3003606126715186,
|
| 545 |
+
0.499498994331008,
|
| 546 |
+
-1.6753764571780796
|
| 547 |
+
],
|
| 548 |
+
"mean": [
|
| 549 |
+
0.0,
|
| 550 |
+
970.1787800129786,
|
| 551 |
+
63.05759247242051,
|
| 552 |
+
52.68523681194731,
|
| 553 |
+
-10.372355683216563,
|
| 554 |
+
1.6383841661258922,
|
| 555 |
+
1.5837118754055808
|
| 556 |
+
],
|
| 557 |
+
"scale": [
|
| 558 |
+
1.0,
|
| 559 |
+
107.29605632482229,
|
| 560 |
+
33.53430196992395,
|
| 561 |
+
18.571850136403008,
|
| 562 |
+
33.45834533772734,
|
| 563 |
+
58.75240472843225,
|
| 564 |
+
6.141393220321764
|
| 565 |
+
]
|
| 566 |
+
},
|
| 567 |
+
{
|
| 568 |
+
"beta": [
|
| 569 |
+
984.8051476654017,
|
| 570 |
+
0.7936591779096613,
|
| 571 |
+
-3.3305818529271134,
|
| 572 |
+
-8.53375512175729,
|
| 573 |
+
-1.2606585077726875,
|
| 574 |
+
0.3600260216844111,
|
| 575 |
+
-1.762847372231583
|
| 576 |
+
],
|
| 577 |
+
"mean": [
|
| 578 |
+
0.0,
|
| 579 |
+
970.1988120772149,
|
| 580 |
+
62.87163834350767,
|
| 581 |
+
51.340679551052034,
|
| 582 |
+
-11.5309587998509,
|
| 583 |
+
1.3928394654347467,
|
| 584 |
+
1.5367101138426003
|
| 585 |
+
],
|
| 586 |
+
"scale": [
|
| 587 |
+
1.0,
|
| 588 |
+
108.15524982595403,
|
| 589 |
+
33.52528278502775,
|
| 590 |
+
18.101358971225256,
|
| 591 |
+
33.91105548737279,
|
| 592 |
+
56.37757439013659,
|
| 593 |
+
6.0910850383445165
|
| 594 |
+
]
|
| 595 |
+
},
|
| 596 |
+
{
|
| 597 |
+
"beta": [
|
| 598 |
+
985.8285618053217,
|
| 599 |
+
0.9087397364417128,
|
| 600 |
+
-3.022972086698143,
|
| 601 |
+
-8.322423688142754,
|
| 602 |
+
-1.3446136356566873,
|
| 603 |
+
0.3946433377092663,
|
| 604 |
+
-1.7388411472291765
|
| 605 |
+
],
|
| 606 |
+
"mean": [
|
| 607 |
+
0.0,
|
| 608 |
+
970.2800606264735,
|
| 609 |
+
62.76507241495453,
|
| 610 |
+
49.89703195558408,
|
| 611 |
+
-12.868040449894732,
|
| 612 |
+
1.1162007409902324,
|
| 613 |
+
1.5151566183900302
|
| 614 |
+
],
|
| 615 |
+
"scale": [
|
| 616 |
+
1.0,
|
| 617 |
+
108.48954799478317,
|
| 618 |
+
33.561878314366474,
|
| 619 |
+
17.68762257887688,
|
| 620 |
+
34.08294834352858,
|
| 621 |
+
53.89123657113572,
|
| 622 |
+
6.0795658479880625
|
| 623 |
+
]
|
| 624 |
+
},
|
| 625 |
+
{
|
| 626 |
+
"beta": [
|
| 627 |
+
986.8479875626188,
|
| 628 |
+
0.9988686965957757,
|
| 629 |
+
-2.6834677443169466,
|
| 630 |
+
-8.099529947859729,
|
| 631 |
+
-1.4667797091389447,
|
| 632 |
+
0.5844770131345003,
|
| 633 |
+
-1.5645908925443726
|
| 634 |
+
],
|
| 635 |
+
"mean": [
|
| 636 |
+
0.0,
|
| 637 |
+
970.663672482294,
|
| 638 |
+
62.6716185869753,
|
| 639 |
+
48.54065532316958,
|
| 640 |
+
-14.130963267924193,
|
| 641 |
+
0.833822767317326,
|
| 642 |
+
1.4964588011746416
|
| 643 |
+
],
|
| 644 |
+
"scale": [
|
| 645 |
+
1.0,
|
| 646 |
+
107.56308024859545,
|
| 647 |
+
33.588233290907006,
|
| 648 |
+
17.283634363151315,
|
| 649 |
+
33.904315820004165,
|
| 650 |
+
51.321735772531284,
|
| 651 |
+
6.081771020585556
|
| 652 |
+
]
|
| 653 |
+
}
|
| 654 |
+
],
|
| 655 |
+
"pressure_joint_features": [
|
| 656 |
+
"bias",
|
| 657 |
+
"current_pressure_hpa",
|
| 658 |
+
"current_wind_kt",
|
| 659 |
+
"calibrated_predicted_wind_kt",
|
| 660 |
+
"raw_predicted_pressure_hpa",
|
| 661 |
+
"raw_predicted_pressure_minus_current_hpa",
|
| 662 |
+
"calibrated_predicted_wind_minus_current_kt",
|
| 663 |
+
"current_pressure_minus_previous_pressure_hpa",
|
| 664 |
+
"current_wind_minus_previous_wind_kt"
|
| 665 |
+
],
|
| 666 |
+
"pressure_joint_calibrations": [
|
| 667 |
+
{
|
| 668 |
+
"beta": [
|
| 669 |
+
979.3647389439432,
|
| 670 |
+
2.1244914734848255,
|
| 671 |
+
-6.002552197162719,
|
| 672 |
+
-6.110930904804655,
|
| 673 |
+
10.470565126637606,
|
| 674 |
+
3.817114397046572,
|
| 675 |
+
0.5822412387685045,
|
| 676 |
+
0.16010090364060767,
|
| 677 |
+
-1.5439923636219977
|
| 678 |
+
],
|
| 679 |
+
"mean": [
|
| 680 |
+
0.0,
|
| 681 |
+
978.1810383079426,
|
| 682 |
+
64.5815707735542,
|
| 683 |
+
65.6266441483449,
|
| 684 |
+
979.0951558127063,
|
| 685 |
+
0.9141175047636491,
|
| 686 |
+
1.0450733747906875,
|
| 687 |
+
1.905043632598728,
|
| 688 |
+
1.4701967164620617
|
| 689 |
+
],
|
| 690 |
+
"scale": [
|
| 691 |
+
1.0,
|
| 692 |
+
50.53637228468569,
|
| 693 |
+
33.10783692877566,
|
| 694 |
+
32.124343617572436,
|
| 695 |
+
24.762051237279874,
|
| 696 |
+
39.796705658565024,
|
| 697 |
+
4.159573246659055,
|
| 698 |
+
54.79089485626104,
|
| 699 |
+
6.246907173086729
|
| 700 |
+
]
|
| 701 |
+
},
|
| 702 |
+
{
|
| 703 |
+
"beta": [
|
| 704 |
+
978.6045857988166,
|
| 705 |
+
0.8713799654438338,
|
| 706 |
+
-7.7527258211177985,
|
| 707 |
+
-8.237667857241275,
|
| 708 |
+
6.714629045232456,
|
| 709 |
+
2.1450799243690115,
|
| 710 |
+
0.018155804213034234,
|
| 711 |
+
0.2910972149675519,
|
| 712 |
+
-2.3186426773038207
|
| 713 |
+
],
|
| 714 |
+
"mean": [
|
| 715 |
+
0.0,
|
| 716 |
+
976.7190828402366,
|
| 717 |
+
64.52144970414201,
|
| 718 |
+
66.21541114460082,
|
| 719 |
+
978.656016247089,
|
| 720 |
+
1.9369334068524062,
|
| 721 |
+
1.6939614404588057,
|
| 722 |
+
1.9063609467455622,
|
| 723 |
+
1.4738165680473372
|
| 724 |
+
],
|
| 725 |
+
"scale": [
|
| 726 |
+
1.0,
|
| 727 |
+
62.987212868626365,
|
| 728 |
+
33.18977815879015,
|
| 729 |
+
31.219218256081252,
|
| 730 |
+
25.2167769986195,
|
| 731 |
+
53.347899343611246,
|
| 732 |
+
7.58431677406538,
|
| 733 |
+
54.79480783300083,
|
| 734 |
+
6.245251042331157
|
| 735 |
+
]
|
| 736 |
+
},
|
| 737 |
+
{
|
| 738 |
+
"beta": [
|
| 739 |
+
978.0161338069863,
|
| 740 |
+
0.42796089044169344,
|
| 741 |
+
-8.495149928651433,
|
| 742 |
+
-9.72355373046241,
|
| 743 |
+
4.306850308592872,
|
| 744 |
+
1.2549277151505402,
|
| 745 |
+
-0.9592509347076426,
|
| 746 |
+
-0.05607486236487535,
|
| 747 |
+
-2.032501028079223
|
| 748 |
+
],
|
| 749 |
+
"mean": [
|
| 750 |
+
0.0,
|
| 751 |
+
975.2491119005329,
|
| 752 |
+
64.46758436944938,
|
| 753 |
+
66.03674610847548,
|
| 754 |
+
978.6352417010256,
|
| 755 |
+
3.3861298004927334,
|
| 756 |
+
1.5691617390261097,
|
| 757 |
+
1.7575488454706927,
|
| 758 |
+
1.4773534635879217
|
| 759 |
+
],
|
| 760 |
+
"scale": [
|
| 761 |
+
1.0,
|
| 762 |
+
73.34317349947774,
|
| 763 |
+
33.277770226738575,
|
| 764 |
+
30.11070808190084,
|
| 765 |
+
26.15742021970667,
|
| 766 |
+
64.75917499444205,
|
| 767 |
+
10.511785983418822,
|
| 768 |
+
56.14964419892791,
|
| 769 |
+
6.253661273135333
|
| 770 |
+
]
|
| 771 |
+
},
|
| 772 |
+
{
|
| 773 |
+
"beta": [
|
| 774 |
+
977.6126259632483,
|
| 775 |
+
0.25733740761036766,
|
| 776 |
+
-8.223026850989438,
|
| 777 |
+
-10.354225400510941,
|
| 778 |
+
3.7337223226615857,
|
| 779 |
+
1.0377977674639305,
|
| 780 |
+
-1.9363265512130752,
|
| 781 |
+
-0.3109780621723116,
|
| 782 |
+
-1.4713391665546236
|
| 783 |
+
],
|
| 784 |
+
"mean": [
|
| 785 |
+
0.0,
|
| 786 |
+
973.9270895080024,
|
| 787 |
+
64.41123295791346,
|
| 788 |
+
65.7963597440974,
|
| 789 |
+
978.8774953411398,
|
| 790 |
+
4.950405833137481,
|
| 791 |
+
1.3851267861839434,
|
| 792 |
+
1.5952874925903973,
|
| 793 |
+
1.4903675163011263
|
| 794 |
+
],
|
| 795 |
+
"scale": [
|
| 796 |
+
1.0,
|
| 797 |
+
81.55905643779808,
|
| 798 |
+
33.37057257362826,
|
| 799 |
+
28.928456009421513,
|
| 800 |
+
26.279899465884235,
|
| 801 |
+
74.324356368596,
|
| 802 |
+
12.97541456694104,
|
| 803 |
+
57.48200889919564,
|
| 804 |
+
6.2549372779430845
|
| 805 |
+
]
|
| 806 |
+
},
|
| 807 |
+
{
|
| 808 |
+
"beta": [
|
| 809 |
+
977.3788958147819,
|
| 810 |
+
0.19565831212711526,
|
| 811 |
+
-7.627340048103773,
|
| 812 |
+
-10.525660728364645,
|
| 813 |
+
3.65180442270761,
|
| 814 |
+
0.9114316402072032,
|
| 815 |
+
-2.4560523652352155,
|
| 816 |
+
-0.5048895919362318,
|
| 817 |
+
-1.2221630437590159
|
| 818 |
+
],
|
| 819 |
+
"mean": [
|
| 820 |
+
0.0,
|
| 821 |
+
972.7582368655387,
|
| 822 |
+
64.35366577619472,
|
| 823 |
+
65.43843451049428,
|
| 824 |
+
979.1345288578711,
|
| 825 |
+
6.376291992332433,
|
| 826 |
+
1.0847687342995598,
|
| 827 |
+
1.5890471950133571,
|
| 828 |
+
1.5031166518254675
|
| 829 |
+
],
|
| 830 |
+
"scale": [
|
| 831 |
+
1.0,
|
| 832 |
+
88.23866281788312,
|
| 833 |
+
33.456506355578135,
|
| 834 |
+
27.78207056922819,
|
| 835 |
+
25.33951308468857,
|
| 836 |
+
82.58471046960283,
|
| 837 |
+
15.162746063357607,
|
| 838 |
+
57.524265495181936,
|
| 839 |
+
6.247664265837107
|
| 840 |
+
]
|
| 841 |
+
},
|
| 842 |
+
{
|
| 843 |
+
"beta": [
|
| 844 |
+
977.2594465932758,
|
| 845 |
+
0.21130054047204685,
|
| 846 |
+
-7.018341900403828,
|
| 847 |
+
-10.58329505292094,
|
| 848 |
+
3.5920580788574266,
|
| 849 |
+
0.8153638167483641,
|
| 850 |
+
-2.727568831621463,
|
| 851 |
+
-0.5801340900358954,
|
| 852 |
+
-1.1526400476893242
|
| 853 |
+
],
|
| 854 |
+
"mean": [
|
| 855 |
+
0.0,
|
| 856 |
+
972.4617673311515,
|
| 857 |
+
64.34275513240107,
|
| 858 |
+
64.95803123366723,
|
| 859 |
+
979.3716344541115,
|
| 860 |
+
6.909867122959999,
|
| 861 |
+
0.6152761012661567,
|
| 862 |
+
1.584498661112764,
|
| 863 |
+
1.5165129425766142
|
| 864 |
+
],
|
| 865 |
+
"scale": [
|
| 866 |
+
1.0,
|
| 867 |
+
89.9186470511227,
|
| 868 |
+
33.49768138488822,
|
| 869 |
+
26.6383543934097,
|
| 870 |
+
24.546593638967988,
|
| 871 |
+
84.83689103290898,
|
| 872 |
+
17.166710109155744,
|
| 873 |
+
57.59243440899489,
|
| 874 |
+
6.244839191073782
|
| 875 |
+
]
|
| 876 |
+
},
|
| 877 |
+
{
|
| 878 |
+
"beta": [
|
| 879 |
+
977.275208830549,
|
| 880 |
+
0.2227503073241216,
|
| 881 |
+
-6.426683692995159,
|
| 882 |
+
-10.520635051916873,
|
| 883 |
+
3.5429980271404626,
|
| 884 |
+
0.74185897909257,
|
| 885 |
+
-2.8219107567360324,
|
| 886 |
+
-0.6004254146464897,
|
| 887 |
+
-1.1561584357339976
|
| 888 |
+
],
|
| 889 |
+
"mean": [
|
| 890 |
+
0.0,
|
| 891 |
+
972.166915274463,
|
| 892 |
+
64.32801312649164,
|
| 893 |
+
64.58668019188688,
|
| 894 |
+
979.5777262002586,
|
| 895 |
+
7.410810925795525,
|
| 896 |
+
0.25866706539523343,
|
| 897 |
+
1.5853221957040573,
|
| 898 |
+
1.527744630071599
|
| 899 |
+
],
|
| 900 |
+
"scale": [
|
| 901 |
+
1.0,
|
| 902 |
+
91.5873950283228,
|
| 903 |
+
33.53705182833371,
|
| 904 |
+
25.602260620153988,
|
| 905 |
+
23.943168400019932,
|
| 906 |
+
86.8486866400537,
|
| 907 |
+
19.072189386922403,
|
| 908 |
+
57.6690743414348,
|
| 909 |
+
6.241065684632235
|
| 910 |
+
]
|
| 911 |
+
},
|
| 912 |
+
{
|
| 913 |
+
"beta": [
|
| 914 |
+
977.4265828468792,
|
| 915 |
+
0.23446887366411298,
|
| 916 |
+
-5.841713308306412,
|
| 917 |
+
-10.298255141336625,
|
| 918 |
+
3.538099855573987,
|
| 919 |
+
0.6848819961434182,
|
| 920 |
+
-2.804375619473821,
|
| 921 |
+
-0.5602620390530704,
|
| 922 |
+
-1.1905193358282722
|
| 923 |
+
],
|
| 924 |
+
"mean": [
|
| 925 |
+
0.0,
|
| 926 |
+
971.8718754677443,
|
| 927 |
+
64.30399640772339,
|
| 928 |
+
63.996917009317954,
|
| 929 |
+
979.8604918721014,
|
| 930 |
+
7.9886164043570975,
|
| 931 |
+
-0.30707939897642006,
|
| 932 |
+
1.5837449483610238,
|
| 933 |
+
1.545577009429726
|
| 934 |
+
],
|
| 935 |
+
"scale": [
|
| 936 |
+
1.0,
|
| 937 |
+
93.26701702857976,
|
| 938 |
+
33.579947436293786,
|
| 939 |
+
24.703194308095586,
|
| 940 |
+
23.414953388343157,
|
| 941 |
+
89.03173262580074,
|
| 942 |
+
20.765893804880417,
|
| 943 |
+
57.76717976524845,
|
| 944 |
+
6.2318182994660445
|
| 945 |
+
]
|
| 946 |
+
},
|
| 947 |
+
{
|
| 948 |
+
"beta": [
|
| 949 |
+
977.7077824519231,
|
| 950 |
+
0.2731983522105385,
|
| 951 |
+
-5.277213350632824,
|
| 952 |
+
-10.088854720334119,
|
| 953 |
+
3.4939775555802064,
|
| 954 |
+
0.590462787082304,
|
| 955 |
+
-2.808858166118079,
|
| 956 |
+
-0.5226757418073821,
|
| 957 |
+
-1.1750023522827215
|
| 958 |
+
],
|
| 959 |
+
"mean": [
|
| 960 |
+
0.0,
|
| 961 |
+
971.5954026442307,
|
| 962 |
+
64.2549579326923,
|
| 963 |
+
63.085182326989106,
|
| 964 |
+
980.3641269023602,
|
| 965 |
+
8.768724258129414,
|
| 966 |
+
-1.1697756054166417,
|
| 967 |
+
1.5760216346153846,
|
| 968 |
+
1.5689603365384615
|
| 969 |
+
],
|
| 970 |
+
"scale": [
|
| 971 |
+
1.0,
|
| 972 |
+
94.93760398672073,
|
| 973 |
+
33.613050887448374,
|
| 974 |
+
23.75717496005899,
|
| 975 |
+
22.878541050644525,
|
| 976 |
+
91.45421724246802,
|
| 977 |
+
22.17963402594675,
|
| 978 |
+
57.87417128115503,
|
| 979 |
+
6.217322594269493
|
| 980 |
+
]
|
| 981 |
+
},
|
| 982 |
+
{
|
| 983 |
+
"beta": [
|
| 984 |
+
978.1375037775763,
|
| 985 |
+
0.3554194868427499,
|
| 986 |
+
-4.767789317327665,
|
| 987 |
+
-9.900624192069188,
|
| 988 |
+
3.3333618901542037,
|
| 989 |
+
0.42821914484805135,
|
| 990 |
+
-2.7793970447450915,
|
| 991 |
+
-0.40881233039958326,
|
| 992 |
+
-1.1973209086991168
|
| 993 |
+
],
|
| 994 |
+
"mean": [
|
| 995 |
+
0.0,
|
| 996 |
+
971.3303112722876,
|
| 997 |
+
64.17527954064673,
|
| 998 |
+
62.25949261227415,
|
| 999 |
+
981.2040692465151,
|
| 1000 |
+
9.873757974227486,
|
| 1001 |
+
-1.9157869289489844,
|
| 1002 |
+
1.5754004230885463,
|
| 1003 |
+
1.5929283771532186
|
| 1004 |
+
],
|
| 1005 |
+
"scale": [
|
| 1006 |
+
1.0,
|
| 1007 |
+
96.6811703822946,
|
| 1008 |
+
33.65355257692086,
|
| 1009 |
+
22.790069308723673,
|
| 1010 |
+
22.401349860189654,
|
| 1011 |
+
94.13272251001614,
|
| 1012 |
+
23.452160129348044,
|
| 1013 |
+
58.03843631538493,
|
| 1014 |
+
6.204621897205797
|
| 1015 |
+
]
|
| 1016 |
+
},
|
| 1017 |
+
{
|
| 1018 |
+
"beta": [
|
| 1019 |
+
978.6816245816854,
|
| 1020 |
+
0.45700856193464073,
|
| 1021 |
+
-4.282115832870207,
|
| 1022 |
+
-9.676417705655844,
|
| 1023 |
+
3.202586323985946,
|
| 1024 |
+
0.263424547765887,
|
| 1025 |
+
-2.744635628620983,
|
| 1026 |
+
-0.2812257628990227,
|
| 1027 |
+
-1.2145691549473818
|
| 1028 |
+
],
|
| 1029 |
+
"mean": [
|
| 1030 |
+
0.0,
|
| 1031 |
+
971.0885305749924,
|
| 1032 |
+
64.04745968968665,
|
| 1033 |
+
61.24103552841074,
|
| 1034 |
+
982.0910472678383,
|
| 1035 |
+
11.002516692845989,
|
| 1036 |
+
-2.806424161275905,
|
| 1037 |
+
1.5728627928202008,
|
| 1038 |
+
1.6186492242166108
|
| 1039 |
+
],
|
| 1040 |
+
"scale": [
|
| 1041 |
+
1.0,
|
| 1042 |
+
98.45100928052985,
|
| 1043 |
+
33.671915141098914,
|
| 1044 |
+
21.846830745260633,
|
| 1045 |
+
22.00199125306356,
|
| 1046 |
+
96.68925057678072,
|
| 1047 |
+
24.488503386648656,
|
| 1048 |
+
58.230564926174964,
|
| 1049 |
+
6.192075094590328
|
| 1050 |
+
]
|
| 1051 |
+
},
|
| 1052 |
+
{
|
| 1053 |
+
"beta": [
|
| 1054 |
+
979.3450283438027,
|
| 1055 |
+
0.5026116383483132,
|
| 1056 |
+
-3.815461840782289,
|
| 1057 |
+
-9.321996789582482,
|
| 1058 |
+
3.1706830476456243,
|
| 1059 |
+
0.1869869500870577,
|
| 1060 |
+
-2.67047300578785,
|
| 1061 |
+
-0.13156395947336183,
|
| 1062 |
+
-1.321484648871915
|
| 1063 |
+
],
|
| 1064 |
+
"mean": [
|
| 1065 |
+
0.0,
|
| 1066 |
+
970.8610387620653,
|
| 1067 |
+
63.89229354986977,
|
| 1068 |
+
60.07496924171664,
|
| 1069 |
+
983.040380796722,
|
| 1070 |
+
12.179342034656822,
|
| 1071 |
+
-3.8173243087375806,
|
| 1072 |
+
1.5874061590317143,
|
| 1073 |
+
1.6341351309943313
|
| 1074 |
+
],
|
| 1075 |
+
"scale": [
|
| 1076 |
+
1.0,
|
| 1077 |
+
100.23062800348737,
|
| 1078 |
+
33.674191443192385,
|
| 1079 |
+
21.02995207203543,
|
| 1080 |
+
21.69896940964998,
|
| 1081 |
+
98.52812879500274,
|
| 1082 |
+
25.29834728692795,
|
| 1083 |
+
58.4371110933118,
|
| 1084 |
+
6.184937924572314
|
| 1085 |
+
]
|
| 1086 |
+
},
|
| 1087 |
+
{
|
| 1088 |
+
"beta": [
|
| 1089 |
+
980.105604446503,
|
| 1090 |
+
0.4819710508401576,
|
| 1091 |
+
-3.454897296475616,
|
| 1092 |
+
-8.997754477229522,
|
| 1093 |
+
2.9217652089719137,
|
| 1094 |
+
0.13715468467271474,
|
| 1095 |
+
-2.6075446851545543,
|
| 1096 |
+
0.031810072532767186,
|
| 1097 |
+
-1.467156119894784
|
| 1098 |
+
],
|
| 1099 |
+
"mean": [
|
| 1100 |
+
0.0,
|
| 1101 |
+
970.6553960166743,
|
| 1102 |
+
63.68905357418558,
|
| 1103 |
+
58.87242237660589,
|
| 1104 |
+
983.8862911363949,
|
| 1105 |
+
13.230895119720502,
|
| 1106 |
+
-4.816631199052089,
|
| 1107 |
+
1.6220472440944882,
|
| 1108 |
+
1.6265246255982708
|
| 1109 |
+
],
|
| 1110 |
+
"scale": [
|
| 1111 |
+
1.0,
|
| 1112 |
+
102.0201408123609,
|
| 1113 |
+
33.62765022480105,
|
| 1114 |
+
20.411349603025727,
|
| 1115 |
+
21.523634442810142,
|
| 1116 |
+
100.00571132508524,
|
| 1117 |
+
25.877311290276214,
|
| 1118 |
+
58.65838429251217,
|
| 1119 |
+
6.17016637339649
|
| 1120 |
+
]
|
| 1121 |
+
},
|
| 1122 |
+
{
|
| 1123 |
+
"beta": [
|
| 1124 |
+
980.9283154121864,
|
| 1125 |
+
0.42406183474284354,
|
| 1126 |
+
-3.101795843309463,
|
| 1127 |
+
-8.59816263901043,
|
| 1128 |
+
2.823608314829573,
|
| 1129 |
+
0.16132514777708762,
|
| 1130 |
+
-2.4809137481393724,
|
| 1131 |
+
0.1604517720706006,
|
| 1132 |
+
-1.5813368102627405
|
| 1133 |
+
],
|
| 1134 |
+
"mean": [
|
| 1135 |
+
0.0,
|
| 1136 |
+
970.4040829047842,
|
| 1137 |
+
63.52236247467664,
|
| 1138 |
+
57.70297459142684,
|
| 1139 |
+
984.7348281791393,
|
| 1140 |
+
14.330745274355168,
|
| 1141 |
+
-5.819387881169164,
|
| 1142 |
+
1.6501480442574412,
|
| 1143 |
+
1.619448340345956
|
| 1144 |
+
],
|
| 1145 |
+
"scale": [
|
| 1146 |
+
1.0,
|
| 1147 |
+
103.88514324892287,
|
| 1148 |
+
33.61229933848826,
|
| 1149 |
+
19.754848726816206,
|
| 1150 |
+
21.389626694015444,
|
| 1151 |
+
101.29978840472074,
|
| 1152 |
+
26.440624986798476,
|
| 1153 |
+
58.92879090161479,
|
| 1154 |
+
6.170992037104559
|
| 1155 |
+
]
|
| 1156 |
+
},
|
| 1157 |
+
{
|
| 1158 |
+
"beta": [
|
| 1159 |
+
981.8252993068684,
|
| 1160 |
+
0.3282559402948829,
|
| 1161 |
+
-2.7917770927328625,
|
| 1162 |
+
-8.299277606331454,
|
| 1163 |
+
2.6249222689964187,
|
| 1164 |
+
0.20733431960316168,
|
| 1165 |
+
-2.409438660750097,
|
| 1166 |
+
0.24058273814096195,
|
| 1167 |
+
-1.7023733781274468
|
| 1168 |
+
],
|
| 1169 |
+
"mean": [
|
| 1170 |
+
0.0,
|
| 1171 |
+
970.11468178954,
|
| 1172 |
+
63.39004410838059,
|
| 1173 |
+
56.4489657036824,
|
| 1174 |
+
985.5594694910368,
|
| 1175 |
+
15.44478770149678,
|
| 1176 |
+
-6.941078406200517,
|
| 1177 |
+
1.6852551984877127,
|
| 1178 |
+
1.613421550094518
|
| 1179 |
+
],
|
| 1180 |
+
"scale": [
|
| 1181 |
+
1.0,
|
| 1182 |
+
105.82467616006231,
|
| 1183 |
+
33.61784905160175,
|
| 1184 |
+
19.14823400203281,
|
| 1185 |
+
21.34911374648086,
|
| 1186 |
+
102.74317126116405,
|
| 1187 |
+
27.003378378170513,
|
| 1188 |
+
59.243797369533446,
|
| 1189 |
+
6.163178774221445
|
| 1190 |
+
]
|
| 1191 |
+
},
|
| 1192 |
+
{
|
| 1193 |
+
"beta": [
|
| 1194 |
+
982.781539444267,
|
| 1195 |
+
0.31618464576562727,
|
| 1196 |
+
-2.511209001172497,
|
| 1197 |
+
-7.999402402069606,
|
| 1198 |
+
2.497911494808863,
|
| 1199 |
+
0.18749180315001615,
|
| 1200 |
+
-2.3085711754224185,
|
| 1201 |
+
0.2585546959223717,
|
| 1202 |
+
-1.8317178283595457
|
| 1203 |
+
],
|
| 1204 |
+
"mean": [
|
| 1205 |
+
0.0,
|
| 1206 |
+
970.1533056531459,
|
| 1207 |
+
63.22868093260939,
|
| 1208 |
+
55.3443473903031,
|
| 1209 |
+
986.1220949673112,
|
| 1210 |
+
15.96878931416524,
|
| 1211 |
+
-7.884333544438428,
|
| 1212 |
+
1.7376237623762376,
|
| 1213 |
+
1.6045991695943789
|
| 1214 |
+
],
|
| 1215 |
+
"scale": [
|
| 1216 |
+
1.0,
|
| 1217 |
+
106.49924076137351,
|
| 1218 |
+
33.55507285388263,
|
| 1219 |
+
18.482496364875466,
|
| 1220 |
+
21.217461982998845,
|
| 1221 |
+
103.07606579674263,
|
| 1222 |
+
27.54307460962646,
|
| 1223 |
+
59.64370137739178,
|
| 1224 |
+
6.154107714919649
|
| 1225 |
+
]
|
| 1226 |
+
},
|
| 1227 |
+
{
|
| 1228 |
+
"beta": [
|
| 1229 |
+
983.7722258273848,
|
| 1230 |
+
0.33981880304597933,
|
| 1231 |
+
-2.2724190975909124,
|
| 1232 |
+
-7.716540651393952,
|
| 1233 |
+
2.487078414467849,
|
| 1234 |
+
0.15245280072872294,
|
| 1235 |
+
-2.188940669353884,
|
| 1236 |
+
0.08654409924485539,
|
| 1237 |
+
-1.8049092360904309
|
| 1238 |
+
],
|
| 1239 |
+
"mean": [
|
| 1240 |
+
0.0,
|
| 1241 |
+
970.1787800129786,
|
| 1242 |
+
63.05759247242051,
|
| 1243 |
+
54.32578140431453,
|
| 1244 |
+
987.0499083947857,
|
| 1245 |
+
16.871128381807104,
|
| 1246 |
+
-8.731811068724843,
|
| 1247 |
+
1.6383841661258922,
|
| 1248 |
+
1.5837118754055808
|
| 1249 |
+
],
|
| 1250 |
+
"scale": [
|
| 1251 |
+
1.0,
|
| 1252 |
+
107.29605632482229,
|
| 1253 |
+
33.53430196992395,
|
| 1254 |
+
17.865324126234672,
|
| 1255 |
+
21.019255280469395,
|
| 1256 |
+
103.73911523783231,
|
| 1257 |
+
28.166402893346593,
|
| 1258 |
+
58.75240472843225,
|
| 1259 |
+
6.141393220321764
|
| 1260 |
+
]
|
| 1261 |
+
},
|
| 1262 |
+
{
|
| 1263 |
+
"beta": [
|
| 1264 |
+
984.8051476654017,
|
| 1265 |
+
0.3951285735895073,
|
| 1266 |
+
-2.045812347972766,
|
| 1267 |
+
-7.401342794020973,
|
| 1268 |
+
2.5680992481457343,
|
| 1269 |
+
0.10213632471215839,
|
| 1270 |
+
-2.0796369943547277,
|
| 1271 |
+
-0.0974652163959373,
|
| 1272 |
+
-1.9027420660166647
|
| 1273 |
+
],
|
| 1274 |
+
"mean": [
|
| 1275 |
+
0.0,
|
| 1276 |
+
970.1988120772149,
|
| 1277 |
+
62.87163834350767,
|
| 1278 |
+
53.108948937069755,
|
| 1279 |
+
988.0273557485792,
|
| 1280 |
+
17.82854367136424,
|
| 1281 |
+
-9.76268940423507,
|
| 1282 |
+
1.3928394654347467,
|
| 1283 |
+
1.5367101138426003
|
| 1284 |
+
],
|
| 1285 |
+
"scale": [
|
| 1286 |
+
1.0,
|
| 1287 |
+
108.15524982595403,
|
| 1288 |
+
33.52528278502775,
|
| 1289 |
+
17.333953998949035,
|
| 1290 |
+
20.799190418897737,
|
| 1291 |
+
104.55786154820235,
|
| 1292 |
+
28.71080692804529,
|
| 1293 |
+
56.37757439013659,
|
| 1294 |
+
6.0910850383445165
|
| 1295 |
+
]
|
| 1296 |
+
},
|
| 1297 |
+
{
|
| 1298 |
+
"beta": [
|
| 1299 |
+
985.8285618053217,
|
| 1300 |
+
0.44350304929556517,
|
| 1301 |
+
-1.8276319568600226,
|
| 1302 |
+
-7.161537529233555,
|
| 1303 |
+
2.612368345826643,
|
| 1304 |
+
0.055391102093499026,
|
| 1305 |
+
-2.0487106124521177,
|
| 1306 |
+
-0.10864333910550422,
|
| 1307 |
+
-1.8863535960463707
|
| 1308 |
+
],
|
| 1309 |
+
"mean": [
|
| 1310 |
+
0.0,
|
| 1311 |
+
970.2800606264735,
|
| 1312 |
+
62.76507241495453,
|
| 1313 |
+
51.71612292526145,
|
| 1314 |
+
989.0245838364434,
|
| 1315 |
+
18.744523209969817,
|
| 1316 |
+
-11.048949497080926,
|
| 1317 |
+
1.1162007409902324,
|
| 1318 |
+
1.5151566183900302
|
| 1319 |
+
],
|
| 1320 |
+
"scale": [
|
| 1321 |
+
1.0,
|
| 1322 |
+
108.48954799478317,
|
| 1323 |
+
33.561878314366474,
|
| 1324 |
+
16.93686204423229,
|
| 1325 |
+
20.63978997941638,
|
| 1326 |
+
104.76933022718211,
|
| 1327 |
+
29.264807512918956,
|
| 1328 |
+
53.89123657113572,
|
| 1329 |
+
6.0795658479880625
|
| 1330 |
+
]
|
| 1331 |
+
},
|
| 1332 |
+
{
|
| 1333 |
+
"beta": [
|
| 1334 |
+
986.8479875626188,
|
| 1335 |
+
0.470340939618217,
|
| 1336 |
+
-1.6504285707864457,
|
| 1337 |
+
-6.963977969610529,
|
| 1338 |
+
2.5909179161370255,
|
| 1339 |
+
0.02709494425615321,
|
| 1340 |
+
-2.0348327401780897,
|
| 1341 |
+
0.045200539893647855,
|
| 1342 |
+
-1.722190325073895
|
| 1343 |
+
],
|
| 1344 |
+
"mean": [
|
| 1345 |
+
0.0,
|
| 1346 |
+
970.663672482294,
|
| 1347 |
+
62.6716185869753,
|
| 1348 |
+
50.29897747716873,
|
| 1349 |
+
989.9238731027123,
|
| 1350 |
+
19.260200620418303,
|
| 1351 |
+
-12.372641116066658,
|
| 1352 |
+
0.833822767317326,
|
| 1353 |
+
1.4964588011746416
|
| 1354 |
+
],
|
| 1355 |
+
"scale": [
|
| 1356 |
+
1.0,
|
| 1357 |
+
107.56308024859545,
|
| 1358 |
+
33.588233290907006,
|
| 1359 |
+
16.634206748030838,
|
| 1360 |
+
20.60855568110453,
|
| 1361 |
+
103.4789331077719,
|
| 1362 |
+
29.685587447486235,
|
| 1363 |
+
51.321735772531284,
|
| 1364 |
+
6.081771020585556
|
| 1365 |
+
]
|
| 1366 |
+
}
|
| 1367 |
+
],
|
| 1368 |
+
"pressure_anchor_alpha": [
|
| 1369 |
+
0.5522160094389178,
|
| 1370 |
+
0.6962881299556641,
|
| 1371 |
+
0.7642130854290704,
|
| 1372 |
+
0.8250283768554111,
|
| 1373 |
+
0.8682641709344661,
|
| 1374 |
+
0.8950311405808169,
|
| 1375 |
+
0.912311094958641,
|
| 1376 |
+
0.9251489323454353,
|
| 1377 |
+
0.9362969190603518,
|
| 1378 |
+
0.9450910275730012,
|
| 1379 |
+
0.953263937622913,
|
| 1380 |
+
0.9605904396465027,
|
| 1381 |
+
0.9650098139429872,
|
| 1382 |
+
0.9694222567928482,
|
| 1383 |
+
0.973242185094525,
|
| 1384 |
+
0.9765976015127111,
|
| 1385 |
+
0.9792250259796094,
|
| 1386 |
+
0.9817388698882592,
|
| 1387 |
+
0.9835805375991149,
|
| 1388 |
+
0.9838229441690179
|
| 1389 |
+
],
|
| 1390 |
+
"pressure_anchor_policy": "validation-fitted blend with valid observed current pressure; no blend when current pressure is missing",
|
| 1391 |
+
"wind_blend_alpha": [
|
| 1392 |
+
1.0277212275681893,
|
| 1393 |
+
1.1489795568075691,
|
| 1394 |
+
1.0663403998709395,
|
| 1395 |
+
1.0186010559120267,
|
| 1396 |
+
0.987967013306198,
|
| 1397 |
+
0.9650641191631195,
|
| 1398 |
+
0.9444263916806881,
|
| 1399 |
+
0.9282677834453357,
|
| 1400 |
+
0.9083684904148223,
|
| 1401 |
+
0.8944074477909971,
|
| 1402 |
+
0.8800766496867902,
|
| 1403 |
+
0.8649619816342672,
|
| 1404 |
+
0.8518192654447947,
|
| 1405 |
+
0.8413105316408789,
|
| 1406 |
+
0.8368414301404576,
|
| 1407 |
+
0.8366307550923628,
|
| 1408 |
+
0.8418348967136898,
|
| 1409 |
+
0.8466503456441108,
|
| 1410 |
+
0.8586348526797178,
|
| 1411 |
+
0.8755695728454723
|
| 1412 |
+
],
|
| 1413 |
+
"structure_blend_features": [
|
| 1414 |
+
"current_rmw_or_radius_native_nm",
|
| 1415 |
+
"primary_structure_native_nm",
|
| 1416 |
+
"optional_secondary_structure_expert_native_nm"
|
| 1417 |
+
],
|
| 1418 |
+
"structure_blend_alpha": [
|
| 1419 |
+
[
|
| 1420 |
+
1.0,
|
| 1421 |
+
1.0,
|
| 1422 |
+
0.5549493412791997,
|
| 1423 |
+
1.0,
|
| 1424 |
+
0.9753869181876241,
|
| 1425 |
+
0.6093764778483237,
|
| 1426 |
+
0.6047313500745581,
|
| 1427 |
+
0.6098850823506468,
|
| 1428 |
+
0.592881985511388,
|
| 1429 |
+
0.42590516633443765,
|
| 1430 |
+
0.6195051390738097,
|
| 1431 |
+
1.0,
|
| 1432 |
+
0.5608806719907258
|
| 1433 |
+
],
|
| 1434 |
+
[
|
| 1435 |
+
1.0,
|
| 1436 |
+
1.0,
|
| 1437 |
+
0.7786456821183296,
|
| 1438 |
+
1.0,
|
| 1439 |
+
1.0,
|
| 1440 |
+
0.8160746904360723,
|
| 1441 |
+
0.7729585422358642,
|
| 1442 |
+
0.8425760483695786,
|
| 1443 |
+
0.8142225430932971,
|
| 1444 |
+
0.7046296166746046,
|
| 1445 |
+
0.7969846939122845,
|
| 1446 |
+
1.0,
|
| 1447 |
+
0.6884011708218611
|
| 1448 |
+
],
|
| 1449 |
+
[
|
| 1450 |
+
1.0,
|
| 1451 |
+
1.0,
|
| 1452 |
+
0.8090536105281151,
|
| 1453 |
+
1.0,
|
| 1454 |
+
1.0,
|
| 1455 |
+
0.8471455035922965,
|
| 1456 |
+
0.7857574635319617,
|
| 1457 |
+
0.8874206525034055,
|
| 1458 |
+
0.860082995947976,
|
| 1459 |
+
0.8239691354642307,
|
| 1460 |
+
0.8772408335676136,
|
| 1461 |
+
1.0,
|
| 1462 |
+
0.7127141967586793
|
| 1463 |
+
],
|
| 1464 |
+
[
|
| 1465 |
+
1.0,
|
| 1466 |
+
1.0,
|
| 1467 |
+
0.7979206267482302,
|
| 1468 |
+
1.0,
|
| 1469 |
+
1.0,
|
| 1470 |
+
0.8513245701071473,
|
| 1471 |
+
0.7557281044138736,
|
| 1472 |
+
0.8953281132435532,
|
| 1473 |
+
0.8823452204080448,
|
| 1474 |
+
0.8994108661181437,
|
| 1475 |
+
0.935888284206547,
|
| 1476 |
+
1.0,
|
| 1477 |
+
0.7614745781233312
|
| 1478 |
+
],
|
| 1479 |
+
[
|
| 1480 |
+
1.0,
|
| 1481 |
+
1.0,
|
| 1482 |
+
0.7889656833371654,
|
| 1483 |
+
1.0,
|
| 1484 |
+
0.9995282276968595,
|
| 1485 |
+
0.813974979553649,
|
| 1486 |
+
0.7261373602450153,
|
| 1487 |
+
0.8520991012979712,
|
| 1488 |
+
0.8571141216277187,
|
| 1489 |
+
0.9547290251465658,
|
| 1490 |
+
0.959194489030165,
|
| 1491 |
+
1.0,
|
| 1492 |
+
0.8093554855049242
|
| 1493 |
+
],
|
| 1494 |
+
[
|
| 1495 |
+
1.0,
|
| 1496 |
+
1.0,
|
| 1497 |
+
0.790841522213487,
|
| 1498 |
+
1.0,
|
| 1499 |
+
0.989425399763569,
|
| 1500 |
+
0.7677379120463775,
|
| 1501 |
+
0.6804313851656493,
|
| 1502 |
+
0.8081026737119595,
|
| 1503 |
+
0.8160784965854719,
|
| 1504 |
+
0.9553568730634433,
|
| 1505 |
+
0.94906212902518,
|
| 1506 |
+
1.0,
|
| 1507 |
+
0.8506601404079891
|
| 1508 |
+
],
|
| 1509 |
+
[
|
| 1510 |
+
1.0,
|
| 1511 |
+
1.0,
|
| 1512 |
+
0.7946505021912497,
|
| 1513 |
+
1.0,
|
| 1514 |
+
0.9842208422424614,
|
| 1515 |
+
0.7309946140644263,
|
| 1516 |
+
0.6519656259054535,
|
| 1517 |
+
0.7832582102291513,
|
| 1518 |
+
0.7751977883552036,
|
| 1519 |
+
0.9498443768015948,
|
| 1520 |
+
0.9364057805619501,
|
| 1521 |
+
1.0,
|
| 1522 |
+
0.8686047360783458
|
| 1523 |
+
],
|
| 1524 |
+
[
|
| 1525 |
+
1.0,
|
| 1526 |
+
1.0,
|
| 1527 |
+
0.8048051919510004,
|
| 1528 |
+
1.0,
|
| 1529 |
+
0.977409615843526,
|
| 1530 |
+
0.7224643906694298,
|
| 1531 |
+
0.6365795828673083,
|
| 1532 |
+
0.766592821423072,
|
| 1533 |
+
0.7398517247851228,
|
| 1534 |
+
0.9404076028219883,
|
| 1535 |
+
0.9255773996092755,
|
| 1536 |
+
1.0,
|
| 1537 |
+
0.86686536561545
|
| 1538 |
+
],
|
| 1539 |
+
[
|
| 1540 |
+
0.9989969287476876,
|
| 1541 |
+
1.0,
|
| 1542 |
+
0.8060150457554067,
|
| 1543 |
+
1.0,
|
| 1544 |
+
0.9635497591152855,
|
| 1545 |
+
0.7298933141800126,
|
| 1546 |
+
0.6276519134653644,
|
| 1547 |
+
0.7587540062356607,
|
| 1548 |
+
0.7370947132207954,
|
| 1549 |
+
0.9282816956429373,
|
| 1550 |
+
0.8960508524231412,
|
| 1551 |
+
1.0,
|
| 1552 |
+
0.8517048378522423
|
| 1553 |
+
],
|
| 1554 |
+
[
|
| 1555 |
+
0.9962949755088357,
|
| 1556 |
+
1.0,
|
| 1557 |
+
0.8024267962891638,
|
| 1558 |
+
1.0,
|
| 1559 |
+
0.9532191018037218,
|
| 1560 |
+
0.7414856091287201,
|
| 1561 |
+
0.629047832296785,
|
| 1562 |
+
0.7588638521544967,
|
| 1563 |
+
0.7411730238585288,
|
| 1564 |
+
0.9244463977489501,
|
| 1565 |
+
0.896517586802664,
|
| 1566 |
+
1.0,
|
| 1567 |
+
0.8363283369905071
|
| 1568 |
+
],
|
| 1569 |
+
[
|
| 1570 |
+
0.9906150062159008,
|
| 1571 |
+
0.9928592943545933,
|
| 1572 |
+
0.8017538447631487,
|
| 1573 |
+
1.0,
|
| 1574 |
+
0.9481221332985175,
|
| 1575 |
+
0.7504970569022518,
|
| 1576 |
+
0.6437585926652084,
|
| 1577 |
+
0.7682739191164304,
|
| 1578 |
+
0.7458488449448917,
|
| 1579 |
+
0.9016502567528687,
|
| 1580 |
+
0.9196549454100816,
|
| 1581 |
+
1.0,
|
| 1582 |
+
0.8222928380829917
|
| 1583 |
+
],
|
| 1584 |
+
[
|
| 1585 |
+
0.9872786368824288,
|
| 1586 |
+
0.9870256484759862,
|
| 1587 |
+
0.8036197038036084,
|
| 1588 |
+
1.0,
|
| 1589 |
+
0.9456840187031937,
|
| 1590 |
+
0.7536197843475184,
|
| 1591 |
+
0.6720399020859967,
|
| 1592 |
+
0.7861770994146164,
|
| 1593 |
+
0.7545241776213014,
|
| 1594 |
+
0.8622271776091911,
|
| 1595 |
+
0.9544728012066145,
|
| 1596 |
+
1.0,
|
| 1597 |
+
0.8091403753140322
|
| 1598 |
+
],
|
| 1599 |
+
[
|
| 1600 |
+
0.9858912730817866,
|
| 1601 |
+
0.9782260755748675,
|
| 1602 |
+
0.8224480738020116,
|
| 1603 |
+
1.0,
|
| 1604 |
+
0.9408562399433626,
|
| 1605 |
+
0.751923185451568,
|
| 1606 |
+
0.7079975704373077,
|
| 1607 |
+
0.8081543972650469,
|
| 1608 |
+
0.7607623883181744,
|
| 1609 |
+
0.838646332479633,
|
| 1610 |
+
1.0,
|
| 1611 |
+
1.0,
|
| 1612 |
+
0.8165953708628378
|
| 1613 |
+
],
|
| 1614 |
+
[
|
| 1615 |
+
0.9871317357369944,
|
| 1616 |
+
0.9675020524431993,
|
| 1617 |
+
0.8435905187514622,
|
| 1618 |
+
1.0,
|
| 1619 |
+
0.9354981762431331,
|
| 1620 |
+
0.7469631531510479,
|
| 1621 |
+
0.746366785020888,
|
| 1622 |
+
0.8291036037207948,
|
| 1623 |
+
0.7566102823880994,
|
| 1624 |
+
0.8383450731194783,
|
| 1625 |
+
1.0,
|
| 1626 |
+
1.0,
|
| 1627 |
+
0.826557900171084
|
| 1628 |
+
],
|
| 1629 |
+
[
|
| 1630 |
+
0.9854462797686748,
|
| 1631 |
+
0.9575337922720787,
|
| 1632 |
+
0.8632940254404728,
|
| 1633 |
+
1.0,
|
| 1634 |
+
0.9361793399856915,
|
| 1635 |
+
0.7280315743113683,
|
| 1636 |
+
0.7782362972629818,
|
| 1637 |
+
0.8461591629929479,
|
| 1638 |
+
0.7364949444253786,
|
| 1639 |
+
0.8495574180679718,
|
| 1640 |
+
1.0,
|
| 1641 |
+
1.0,
|
| 1642 |
+
0.8319355974514233
|
| 1643 |
+
],
|
| 1644 |
+
[
|
| 1645 |
+
0.9799350216481773,
|
| 1646 |
+
0.948053493097228,
|
| 1647 |
+
0.8777561442871291,
|
| 1648 |
+
1.0,
|
| 1649 |
+
0.9389259406046586,
|
| 1650 |
+
0.7268423625933376,
|
| 1651 |
+
0.8056289626997586,
|
| 1652 |
+
0.858630296390731,
|
| 1653 |
+
0.7303639110314664,
|
| 1654 |
+
0.8680875406118934,
|
| 1655 |
+
1.0,
|
| 1656 |
+
1.0,
|
| 1657 |
+
0.8546179948062796
|
| 1658 |
+
],
|
| 1659 |
+
[
|
| 1660 |
+
0.9740716852608886,
|
| 1661 |
+
0.9403381171414777,
|
| 1662 |
+
0.8814982444352361,
|
| 1663 |
+
1.0,
|
| 1664 |
+
0.9376654486264268,
|
| 1665 |
+
0.720231177359567,
|
| 1666 |
+
0.8229001102851932,
|
| 1667 |
+
0.883352887181907,
|
| 1668 |
+
0.7374999300634676,
|
| 1669 |
+
0.862734447860439,
|
| 1670 |
+
1.0,
|
| 1671 |
+
1.0,
|
| 1672 |
+
0.8757411053631797
|
| 1673 |
+
],
|
| 1674 |
+
[
|
| 1675 |
+
0.9686221325152385,
|
| 1676 |
+
0.9305462545814487,
|
| 1677 |
+
0.883874105956774,
|
| 1678 |
+
1.0,
|
| 1679 |
+
0.9383603227817988,
|
| 1680 |
+
0.714941943020425,
|
| 1681 |
+
0.8307300156928373,
|
| 1682 |
+
0.9061038362396052,
|
| 1683 |
+
0.7477493305948096,
|
| 1684 |
+
0.8656451575094964,
|
| 1685 |
+
1.0,
|
| 1686 |
+
1.0,
|
| 1687 |
+
0.9054612373084847
|
| 1688 |
+
],
|
| 1689 |
+
[
|
| 1690 |
+
0.9662039837991756,
|
| 1691 |
+
0.9275664889767167,
|
| 1692 |
+
0.8912243532082841,
|
| 1693 |
+
1.0,
|
| 1694 |
+
0.9403396402123274,
|
| 1695 |
+
0.7157872514985167,
|
| 1696 |
+
0.8458014076196586,
|
| 1697 |
+
0.9215386465479368,
|
| 1698 |
+
0.7592801106445597,
|
| 1699 |
+
0.8765838611817265,
|
| 1700 |
+
1.0,
|
| 1701 |
+
1.0,
|
| 1702 |
+
0.9177342044253676
|
| 1703 |
+
],
|
| 1704 |
+
[
|
| 1705 |
+
0.9568044542361462,
|
| 1706 |
+
0.9385390035449604,
|
| 1707 |
+
0.9215888189795768,
|
| 1708 |
+
1.0,
|
| 1709 |
+
0.9503495133295314,
|
| 1710 |
+
0.7244552996295812,
|
| 1711 |
+
0.8600858326127176,
|
| 1712 |
+
0.9420104384137908,
|
| 1713 |
+
0.7495840140906448,
|
| 1714 |
+
0.9216970099860546,
|
| 1715 |
+
1.0,
|
| 1716 |
+
1.0,
|
| 1717 |
+
0.9582402976615332
|
| 1718 |
+
]
|
| 1719 |
+
],
|
| 1720 |
+
"checkpoint_root": "models/trackformer_1_1",
|
| 1721 |
+
"structure_checkpoint_root": "models/trackformer_1_1",
|
| 1722 |
+
"structure_expert_alpha": [
|
| 1723 |
+
[
|
| 1724 |
+
0.0,
|
| 1725 |
+
0.9361481070518494,
|
| 1726 |
+
0.934322714805603,
|
| 1727 |
+
0.8858486413955688,
|
| 1728 |
+
0.9938719272613525,
|
| 1729 |
+
0.9489486813545227,
|
| 1730 |
+
0.9187760949134827,
|
| 1731 |
+
0.9228118062019348,
|
| 1732 |
+
0.9387768507003784,
|
| 1733 |
+
0.7600101828575134,
|
| 1734 |
+
0.7809425592422485,
|
| 1735 |
+
0.8700641393661499,
|
| 1736 |
+
0.8380165100097656
|
| 1737 |
+
],
|
| 1738 |
+
[
|
| 1739 |
+
0.0,
|
| 1740 |
+
0.7883517742156982,
|
| 1741 |
+
0.8298157453536987,
|
| 1742 |
+
0.7546190619468689,
|
| 1743 |
+
0.891186535358429,
|
| 1744 |
+
0.8175759315490723,
|
| 1745 |
+
0.7462324500083923,
|
| 1746 |
+
0.7695027589797974,
|
| 1747 |
+
0.7692294716835022,
|
| 1748 |
+
0.6570655107498169,
|
| 1749 |
+
0.5868498086929321,
|
| 1750 |
+
0.7810206413269043,
|
| 1751 |
+
0.6184545159339905
|
| 1752 |
+
],
|
| 1753 |
+
[
|
| 1754 |
+
0.0,
|
| 1755 |
+
0.6988456845283508,
|
| 1756 |
+
0.7698739171028137,
|
| 1757 |
+
0.6341904401779175,
|
| 1758 |
+
0.781299352645874,
|
| 1759 |
+
0.7261586785316467,
|
| 1760 |
+
0.7200528383255005,
|
| 1761 |
+
0.6777958273887634,
|
| 1762 |
+
0.6547061204910278,
|
| 1763 |
+
0.6781790852546692,
|
| 1764 |
+
0.38850289583206177,
|
| 1765 |
+
0.739601194858551,
|
| 1766 |
+
0.4023652672767639
|
| 1767 |
+
],
|
| 1768 |
+
[
|
| 1769 |
+
0.0,
|
| 1770 |
+
0.6077090501785278,
|
| 1771 |
+
0.75852370262146,
|
| 1772 |
+
0.5542184710502625,
|
| 1773 |
+
0.668627917766571,
|
| 1774 |
+
0.6401088833808899,
|
| 1775 |
+
0.7013946771621704,
|
| 1776 |
+
0.6870279908180237,
|
| 1777 |
+
0.6323707103729248,
|
| 1778 |
+
0.7411512136459351,
|
| 1779 |
+
0.19386884570121765,
|
| 1780 |
+
0.6939995288848877,
|
| 1781 |
+
0.3757428824901581
|
| 1782 |
+
],
|
| 1783 |
+
[
|
| 1784 |
+
0.0,
|
| 1785 |
+
0.5452795028686523,
|
| 1786 |
+
0.8159465193748474,
|
| 1787 |
+
0.5034195780754089,
|
| 1788 |
+
0.5881041884422302,
|
| 1789 |
+
0.6064771413803101,
|
| 1790 |
+
0.7117744088172913,
|
| 1791 |
+
0.6996253728866577,
|
| 1792 |
+
0.6671974062919617,
|
| 1793 |
+
0.7673929929733276,
|
| 1794 |
+
0.1434878557920456,
|
| 1795 |
+
0.6283478736877441,
|
| 1796 |
+
0.3827730417251587
|
| 1797 |
+
],
|
| 1798 |
+
[
|
| 1799 |
+
0.0,
|
| 1800 |
+
0.4475332498550415,
|
| 1801 |
+
0.8445053696632385,
|
| 1802 |
+
0.47751277685165405,
|
| 1803 |
+
0.49595507979393005,
|
| 1804 |
+
0.5493122339248657,
|
| 1805 |
+
0.6586169004440308,
|
| 1806 |
+
0.6564105749130249,
|
| 1807 |
+
0.6699652671813965,
|
| 1808 |
+
0.7021469473838806,
|
| 1809 |
+
0.0653224065899849,
|
| 1810 |
+
0.5543050765991211,
|
| 1811 |
+
0.38605213165283203
|
| 1812 |
+
],
|
| 1813 |
+
[
|
| 1814 |
+
0.006327976007014513,
|
| 1815 |
+
0.34530171751976013,
|
| 1816 |
+
0.8709386587142944,
|
| 1817 |
+
0.4365202486515045,
|
| 1818 |
+
0.4342052638530731,
|
| 1819 |
+
0.5322895646095276,
|
| 1820 |
+
0.6248392462730408,
|
| 1821 |
+
0.5894513726234436,
|
| 1822 |
+
0.7012799382209778,
|
| 1823 |
+
0.626562237739563,
|
| 1824 |
+
0.015733113512396812,
|
| 1825 |
+
0.44189080595970154,
|
| 1826 |
+
0.42251747846603394
|
| 1827 |
+
],
|
| 1828 |
+
[
|
| 1829 |
+
0.005135390907526016,
|
| 1830 |
+
0.2684456408023834,
|
| 1831 |
+
0.8810635805130005,
|
| 1832 |
+
0.4226278066635132,
|
| 1833 |
+
0.39568835496902466,
|
| 1834 |
+
0.5028242468833923,
|
| 1835 |
+
0.6358619332313538,
|
| 1836 |
+
0.5655720829963684,
|
| 1837 |
+
0.7411352396011353,
|
| 1838 |
+
0.5535630583763123,
|
| 1839 |
+
0.0,
|
| 1840 |
+
0.3738625943660736,
|
| 1841 |
+
0.4239470660686493
|
| 1842 |
+
],
|
| 1843 |
+
[
|
| 1844 |
+
0.023205677047371864,
|
| 1845 |
+
0.21901144087314606,
|
| 1846 |
+
0.8607876896858215,
|
| 1847 |
+
0.43362921476364136,
|
| 1848 |
+
0.3754803538322449,
|
| 1849 |
+
0.5236393809318542,
|
| 1850 |
+
0.6005999445915222,
|
| 1851 |
+
0.5589695572853088,
|
| 1852 |
+
0.7332651615142822,
|
| 1853 |
+
0.47174206376075745,
|
| 1854 |
+
0.009717775508761406,
|
| 1855 |
+
0.35761409997940063,
|
| 1856 |
+
0.4059191644191742
|
| 1857 |
+
],
|
| 1858 |
+
[
|
| 1859 |
+
0.038556747138500214,
|
| 1860 |
+
0.1978488266468048,
|
| 1861 |
+
0.834216296672821,
|
| 1862 |
+
0.4369942843914032,
|
| 1863 |
+
0.3645400106906891,
|
| 1864 |
+
0.5292827486991882,
|
| 1865 |
+
0.556195855140686,
|
| 1866 |
+
0.5879309773445129,
|
| 1867 |
+
0.7033751010894775,
|
| 1868 |
+
0.4590916037559509,
|
| 1869 |
+
0.06794524937868118,
|
| 1870 |
+
0.3964718282222748,
|
| 1871 |
+
0.42892006039619446
|
| 1872 |
+
],
|
| 1873 |
+
[
|
| 1874 |
+
0.0822339728474617,
|
| 1875 |
+
0.15992115437984467,
|
| 1876 |
+
0.7834641337394714,
|
| 1877 |
+
0.42339903116226196,
|
| 1878 |
+
0.34574249386787415,
|
| 1879 |
+
0.48923566937446594,
|
| 1880 |
+
0.5099923610687256,
|
| 1881 |
+
0.627753496170044,
|
| 1882 |
+
0.6312965750694275,
|
| 1883 |
+
0.4491918981075287,
|
| 1884 |
+
0.08122247457504272,
|
| 1885 |
+
0.33692267537117004,
|
| 1886 |
+
0.465788871049881
|
| 1887 |
+
],
|
| 1888 |
+
[
|
| 1889 |
+
0.11642027646303177,
|
| 1890 |
+
0.118315190076828,
|
| 1891 |
+
0.7409539222717285,
|
| 1892 |
+
0.4470598101615906,
|
| 1893 |
+
0.3227274417877197,
|
| 1894 |
+
0.4294480085372925,
|
| 1895 |
+
0.49222809076309204,
|
| 1896 |
+
0.7322760820388794,
|
| 1897 |
+
0.5469415783882141,
|
| 1898 |
+
0.36480313539505005,
|
| 1899 |
+
0.024686744436621666,
|
| 1900 |
+
0.2646491229534149,
|
| 1901 |
+
0.4103580713272095
|
| 1902 |
+
],
|
| 1903 |
+
[
|
| 1904 |
+
0.1868918538093567,
|
| 1905 |
+
0.10242144018411636,
|
| 1906 |
+
0.6756333708763123,
|
| 1907 |
+
0.4364897906780243,
|
| 1908 |
+
0.290968120098114,
|
| 1909 |
+
0.4043419063091278,
|
| 1910 |
+
0.45341530442237854,
|
| 1911 |
+
0.7665934562683105,
|
| 1912 |
+
0.5088798999786377,
|
| 1913 |
+
0.24618837237358093,
|
| 1914 |
+
0.0,
|
| 1915 |
+
0.16969110071659088,
|
| 1916 |
+
0.35006994009017944
|
| 1917 |
+
],
|
| 1918 |
+
[
|
| 1919 |
+
0.2503180205821991,
|
| 1920 |
+
0.1268322765827179,
|
| 1921 |
+
0.5735778212547302,
|
| 1922 |
+
0.4366355836391449,
|
| 1923 |
+
0.30388057231903076,
|
| 1924 |
+
0.442385733127594,
|
| 1925 |
+
0.398971289396286,
|
| 1926 |
+
0.7520959377288818,
|
| 1927 |
+
0.5388989448547363,
|
| 1928 |
+
0.26548394560813904,
|
| 1929 |
+
0.008171271532773972,
|
| 1930 |
+
0.1257970780134201,
|
| 1931 |
+
0.3485977053642273
|
| 1932 |
+
],
|
| 1933 |
+
[
|
| 1934 |
+
0.2900908887386322,
|
| 1935 |
+
0.16893012821674347,
|
| 1936 |
+
0.4577920436859131,
|
| 1937 |
+
0.4277336597442627,
|
| 1938 |
+
0.3232477009296417,
|
| 1939 |
+
0.531777560710907,
|
| 1940 |
+
0.39727750420570374,
|
| 1941 |
+
0.6795014142990112,
|
| 1942 |
+
0.48868805170059204,
|
| 1943 |
+
0.2898959517478943,
|
| 1944 |
+
0.1418028324842453,
|
| 1945 |
+
0.12177688628435135,
|
| 1946 |
+
0.2917960286140442
|
| 1947 |
+
],
|
| 1948 |
+
[
|
| 1949 |
+
0.2792268693447113,
|
| 1950 |
+
0.2319025993347168,
|
| 1951 |
+
0.4227851331233978,
|
| 1952 |
+
0.44706737995147705,
|
| 1953 |
+
0.3128238320350647,
|
| 1954 |
+
0.6999660134315491,
|
| 1955 |
+
0.43848201632499695,
|
| 1956 |
+
0.5300739407539368,
|
| 1957 |
+
0.39969006180763245,
|
| 1958 |
+
0.39730560779571533,
|
| 1959 |
+
0.26391106843948364,
|
| 1960 |
+
0.18718558549880981,
|
| 1961 |
+
0.33163970708847046
|
| 1962 |
+
],
|
| 1963 |
+
[
|
| 1964 |
+
0.28902363777160645,
|
| 1965 |
+
0.28392016887664795,
|
| 1966 |
+
0.4272365868091583,
|
| 1967 |
+
0.5053210854530334,
|
| 1968 |
+
0.2757094204425812,
|
| 1969 |
+
0.8456398248672485,
|
| 1970 |
+
0.4518815577030182,
|
| 1971 |
+
0.45749273896217346,
|
| 1972 |
+
0.3181617558002472,
|
| 1973 |
+
0.4448428452014923,
|
| 1974 |
+
0.3280032277107239,
|
| 1975 |
+
0.21995072066783905,
|
| 1976 |
+
0.40605419874191284
|
| 1977 |
+
],
|
| 1978 |
+
[
|
| 1979 |
+
0.30310046672821045,
|
| 1980 |
+
0.3220006227493286,
|
| 1981 |
+
0.4188046455383301,
|
| 1982 |
+
0.5786864757537842,
|
| 1983 |
+
0.2313844859600067,
|
| 1984 |
+
0.9909186959266663,
|
| 1985 |
+
0.3816840648651123,
|
| 1986 |
+
0.41152098774909973,
|
| 1987 |
+
0.2809295356273651,
|
| 1988 |
+
0.5186776518821716,
|
| 1989 |
+
0.4197472035884857,
|
| 1990 |
+
0.19759920239448547,
|
| 1991 |
+
0.5271488428115845
|
| 1992 |
+
],
|
| 1993 |
+
[
|
| 1994 |
+
0.3526214361190796,
|
| 1995 |
+
0.35234275460243225,
|
| 1996 |
+
0.43079662322998047,
|
| 1997 |
+
0.5791170597076416,
|
| 1998 |
+
0.1899081915616989,
|
| 1999 |
+
1.0,
|
| 2000 |
+
0.36282944679260254,
|
| 2001 |
+
0.3539854884147644,
|
| 2002 |
+
0.28582316637039185,
|
| 2003 |
+
0.496574729681015,
|
| 2004 |
+
0.43650829792022705,
|
| 2005 |
+
0.10935331135988235,
|
| 2006 |
+
0.5543745160102844
|
| 2007 |
+
],
|
| 2008 |
+
[
|
| 2009 |
+
0.41773712635040283,
|
| 2010 |
+
0.3903321921825409,
|
| 2011 |
+
0.47357064485549927,
|
| 2012 |
+
0.5240141749382019,
|
| 2013 |
+
0.20888248085975647,
|
| 2014 |
+
1.0,
|
| 2015 |
+
0.37714311480522156,
|
| 2016 |
+
0.2732824683189392,
|
| 2017 |
+
0.40228506922721863,
|
| 2018 |
+
0.46711480617523193,
|
| 2019 |
+
0.3247523307800293,
|
| 2020 |
+
0.06386715173721313,
|
| 2021 |
+
0.638704240322113
|
| 2022 |
+
]
|
| 2023 |
+
],
|
| 2024 |
+
"structure_native_unit": "nautical_miles",
|
| 2025 |
+
"track_input_clip": {
|
| 2026 |
+
"method": "per-feature 0.1%-99.9% bounds from train split only",
|
| 2027 |
+
"quantiles": [
|
| 2028 |
+
0.001,
|
| 2029 |
+
0.999
|
| 2030 |
+
],
|
| 2031 |
+
"lower": [
|
| 2032 |
+
-3.8563146591186523,
|
| 2033 |
+
-3.859726667404175,
|
| 2034 |
+
-2.669590950012207,
|
| 2035 |
+
-2.828439712524414,
|
| 2036 |
+
-1.514838695526123,
|
| 2037 |
+
-0.877439558506012,
|
| 2038 |
+
-0.20029544830322266,
|
| 2039 |
+
-0.5029191970825195,
|
| 2040 |
+
-0.4581102430820465,
|
| 2041 |
+
-0.4543793201446533,
|
| 2042 |
+
-0.44044506549835205,
|
| 2043 |
+
-0.4524264335632324,
|
| 2044 |
+
-0.32242000102996826,
|
| 2045 |
+
-0.3159736096858978,
|
| 2046 |
+
-0.31081247329711914,
|
| 2047 |
+
-0.31689807772636414,
|
| 2048 |
+
-0.2571345567703247,
|
| 2049 |
+
-0.25485461950302124,
|
| 2050 |
+
-0.25090861320495605,
|
| 2051 |
+
-0.253328412771225,
|
| 2052 |
+
0.0,
|
| 2053 |
+
-1.109837293624878,
|
| 2054 |
+
-1.4902359247207642,
|
| 2055 |
+
-1.5492080450057983,
|
| 2056 |
+
-3.043226718902588,
|
| 2057 |
+
-0.8778678178787231,
|
| 2058 |
+
-0.22049888968467712,
|
| 2059 |
+
-0.6087565422058105,
|
| 2060 |
+
-0.5303210616111755,
|
| 2061 |
+
-0.5303064584732056,
|
| 2062 |
+
-0.530174195766449,
|
| 2063 |
+
-0.5302548408508301,
|
| 2064 |
+
-0.3700577914714813,
|
| 2065 |
+
-0.3699590861797333,
|
| 2066 |
+
-0.36966797709465027,
|
| 2067 |
+
-0.36991986632347107,
|
| 2068 |
+
-0.28939348459243774,
|
| 2069 |
+
-0.2892126142978668,
|
| 2070 |
+
-0.2889581322669983,
|
| 2071 |
+
-0.2892405688762665,
|
| 2072 |
+
-1.000949740409851,
|
| 2073 |
+
-1.9555184841156006,
|
| 2074 |
+
-1.3818809986114502,
|
| 2075 |
+
-3.743236541748047,
|
| 2076 |
+
-4.178081035614014,
|
| 2077 |
+
-7.5739426612854,
|
| 2078 |
+
-1.9869922399520874,
|
| 2079 |
+
-0.791445791721344,
|
| 2080 |
+
-1.895654320716858,
|
| 2081 |
+
-1.900766134262085,
|
| 2082 |
+
-1.8269368410110474,
|
| 2083 |
+
-1.1999244689941406,
|
| 2084 |
+
-1.2392349243164062,
|
| 2085 |
+
-1.5779284238815308
|
| 2086 |
+
],
|
| 2087 |
+
"upper": [
|
| 2088 |
+
3.46697735786438,
|
| 2089 |
+
2.962402105331421,
|
| 2090 |
+
3.792782783508301,
|
| 2091 |
+
3.5486209392547607,
|
| 2092 |
+
3.303658962249756,
|
| 2093 |
+
1.1986064910888672,
|
| 2094 |
+
9.119026184082031,
|
| 2095 |
+
6.6407904624938965,
|
| 2096 |
+
5.609043598175049,
|
| 2097 |
+
5.1235761642456055,
|
| 2098 |
+
5.236553192138672,
|
| 2099 |
+
5.678617000579834,
|
| 2100 |
+
6.238978385925293,
|
| 2101 |
+
6.22589635848999,
|
| 2102 |
+
6.73668098449707,
|
| 2103 |
+
6.552648067474365,
|
| 2104 |
+
7.652637958526611,
|
| 2105 |
+
7.037979602813721,
|
| 2106 |
+
6.988698482513428,
|
| 2107 |
+
7.6510162353515625,
|
| 2108 |
+
0.0,
|
| 2109 |
+
1.7232352495193481,
|
| 2110 |
+
1.4755607843399048,
|
| 2111 |
+
1.5490014553070068,
|
| 2112 |
+
0.3285984992980957,
|
| 2113 |
+
1.1391236782073975,
|
| 2114 |
+
4.535170078277588,
|
| 2115 |
+
1.6426928043365479,
|
| 2116 |
+
1.8856501579284668,
|
| 2117 |
+
1.8857020139694214,
|
| 2118 |
+
1.8861727714538574,
|
| 2119 |
+
1.88588547706604,
|
| 2120 |
+
2.7022807598114014,
|
| 2121 |
+
2.7030017375946045,
|
| 2122 |
+
2.705130100250244,
|
| 2123 |
+
2.7032880783081055,
|
| 2124 |
+
3.45550274848938,
|
| 2125 |
+
3.4576637744903564,
|
| 2126 |
+
3.460709571838379,
|
| 2127 |
+
3.457329511642456,
|
| 2128 |
+
2.0224556922912598,
|
| 2129 |
+
1.5678421258926392,
|
| 2130 |
+
4.212479591369629,
|
| 2131 |
+
3.814079999923706,
|
| 2132 |
+
4.954610824584961,
|
| 2133 |
+
5.965235233306885,
|
| 2134 |
+
0.5032732486724854,
|
| 2135 |
+
1.2635104656219482,
|
| 2136 |
+
2.1631147861480713,
|
| 2137 |
+
3.6985673904418945,
|
| 2138 |
+
1.0047227144241333,
|
| 2139 |
+
2.321204662322998,
|
| 2140 |
+
3.150118350982666,
|
| 2141 |
+
1.4707427024841309
|
| 2142 |
+
]
|
| 2143 |
+
},
|
| 2144 |
+
"temporal_checkpoint_root": "models/trackformer_1_1",
|
| 2145 |
+
"temporal_wind_blend_alpha": [
|
| 2146 |
+
1.006157773537495,
|
| 2147 |
+
1.082670297723516,
|
| 2148 |
+
1.0076812926999208,
|
| 2149 |
+
0.9684541176843595,
|
| 2150 |
+
0.937035559922028,
|
| 2151 |
+
0.9061196390585828,
|
| 2152 |
+
0.8828505612779566,
|
| 2153 |
+
0.8649551163976384,
|
| 2154 |
+
0.850160066458564,
|
| 2155 |
+
0.8436538164907559,
|
| 2156 |
+
0.8360733120277236,
|
| 2157 |
+
0.8246513293372426,
|
| 2158 |
+
0.8130469984314852,
|
| 2159 |
+
0.8040146581229802,
|
| 2160 |
+
0.8026821984394348,
|
| 2161 |
+
0.806057957319184,
|
| 2162 |
+
0.8108057351642745,
|
| 2163 |
+
0.8134246099298341,
|
| 2164 |
+
0.8212623454663425,
|
| 2165 |
+
0.8365160626176127
|
| 2166 |
+
],
|
| 2167 |
+
"temporal_wind_min_current_kt": 80.0,
|
| 2168 |
+
"temporal_wind_metrics": {
|
| 2169 |
+
"val": {
|
| 2170 |
+
"all_leads_kt": 16.9397,
|
| 2171 |
+
"lead_120h_kt": 21.4157
|
| 2172 |
+
},
|
| 2173 |
+
"test": {
|
| 2174 |
+
"all_leads_kt": 16.3692,
|
| 2175 |
+
"lead_120h_kt": 20.328
|
| 2176 |
+
}
|
| 2177 |
+
},
|
| 2178 |
+
"temporal_input_policy": "same-storm t-12 h and t-24 h dlm4 analysis patches; no future atmospheric fields",
|
| 2179 |
+
"pressure_policy": "retain the calibrated pressure path; temporal branch is wind-only"
|
| 2180 |
+
}
|
v23_terrain_wp.npz → models/trackformer_1_1/trackformer_1_1_intensity_seed0.pt
RENAMED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f26a55bb3db6e88a69656e2633e8316f71698866d4be4c336a1b2524a5f58401
|
| 3 |
+
size 8896661
|
best.pt → models/trackformer_1_1/trackformer_1_1_intensity_seed1.pt
RENAMED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:74c379bda87dd52a3d7558d58110d104e427d7972ad59e2dca02b4e61b641e1c
|
| 3 |
+
size 8896725
|
v23_seed1.pt → models/trackformer_1_1/trackformer_1_1_intensity_seed2.pt
RENAMED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7023a827ebc100d19a70183e5ffe9f23023da58c178eb9fcd668d681a2c90b53
|
| 3 |
+
size 8896661
|
v23_norm_stats.npz → models/trackformer_1_1/trackformer_1_1_norm_stats.npz
RENAMED
|
File without changes
|
v23_seed2.pt → models/trackformer_1_1/trackformer_1_1_structure_seed0.pt
RENAMED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:46a2d38cae5ce1cb727f589d5dbd969a05480aaf32e3b33d4b7ab3e285beb42a
|
| 3 |
+
size 8896961
|
models/trackformer_1_1/trackformer_1_1_structure_seed1.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:13d1840bc8f4d7bb144dad2d1c07dd9f8ea8cf7bdd81675788431672e9755b8e
|
| 3 |
+
size 8897025
|
models/trackformer_1_1/trackformer_1_1_structure_seed2.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f46a8f03b4723baa4fd9068877b3a4006edf898f084e2dd7be6be90ea44dd562
|
| 3 |
+
size 8896961
|
models/trackformer_1_1/trackformer_1_1_temporal_seed0.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:251ae688062543dfa748f46840054d4e2bb54d0701b2b73f8f51c976f9ac3c78
|
| 3 |
+
size 9531936
|
models/trackformer_1_1/trackformer_1_1_temporal_seed1.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9c705fea9bc73b0971be8949d777c22d6bede38b40134f34f00f1082eb081b49
|
| 3 |
+
size 9531872
|
models/trackformer_1_1/trackformer_1_1_temporal_seed2.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c90ce4dc0a409585bf5a81f0747248be086c2698be9a790296a1dc56a9ca76ae
|
| 3 |
+
size 9531936
|
requirements.txt
CHANGED
|
@@ -1,5 +1,3 @@
|
|
| 1 |
-
numpy=
|
| 2 |
-
torch=
|
| 3 |
-
|
| 4 |
-
matplotlib==3.11.0
|
| 5 |
-
folium==0.20.0
|
|
|
|
| 1 |
+
numpy>=1.26
|
| 2 |
+
torch>=2.1
|
| 3 |
+
scipy>=1.11
|
|
|
|
|
|
run_inference.py
DELETED
|
@@ -1,94 +0,0 @@
|
|
| 1 |
-
import json
|
| 2 |
-
import argparse
|
| 3 |
-
from pathlib import Path
|
| 4 |
-
import numpy as np
|
| 5 |
-
import torch
|
| 6 |
-
from torch import nn
|
| 7 |
-
|
| 8 |
-
ROOT = Path(__file__).resolve().parent
|
| 9 |
-
parser = argparse.ArgumentParser(description="Run the Bavi typhoon checkpoint locally.")
|
| 10 |
-
parser.add_argument("--checkpoint", type=Path, default=ROOT / "best.pt")
|
| 11 |
-
parser.add_argument("--output", type=Path, default=ROOT / "forecast.json")
|
| 12 |
-
parser.add_argument("--device", choices=["auto", "mps", "cpu"], default="auto")
|
| 13 |
-
args = parser.parse_args()
|
| 14 |
-
CKPT = args.checkpoint.expanduser().resolve()
|
| 15 |
-
OUT = args.output.expanduser().resolve()
|
| 16 |
-
|
| 17 |
-
class FieldEncoder(nn.Module):
|
| 18 |
-
def __init__(self, channels, hidden=128):
|
| 19 |
-
super().__init__()
|
| 20 |
-
self.net = nn.Sequential(
|
| 21 |
-
nn.Conv2d(channels,64,3,padding=1), nn.GELU(), nn.BatchNorm2d(64),
|
| 22 |
-
nn.Conv2d(64,96,3,stride=2,padding=1), nn.GELU(), nn.BatchNorm2d(96),
|
| 23 |
-
nn.Conv2d(96,hidden,3,stride=2,padding=1), nn.GELU(),
|
| 24 |
-
nn.AdaptiveAvgPool2d(1))
|
| 25 |
-
def forward(self, x): return self.net(x).flatten(1)
|
| 26 |
-
|
| 27 |
-
class ERA5CycloneEnsemble(nn.Module):
|
| 28 |
-
def __init__(self, track_dim, channels, steps, output_dim, hidden=192, latent=32):
|
| 29 |
-
super().__init__()
|
| 30 |
-
self.steps, self.output_dim, self.latent = steps, output_dim, latent
|
| 31 |
-
self.field = FieldEncoder(channels, 128)
|
| 32 |
-
self.track_gru = nn.GRU(track_dim,96,batch_first=True,bidirectional=True)
|
| 33 |
-
self.fuse = nn.Sequential(nn.Linear(128*steps+192,hidden),nn.GELU(),nn.Dropout(.15),nn.Linear(hidden,hidden),nn.GELU())
|
| 34 |
-
self.mean = nn.Linear(hidden,output_dim)
|
| 35 |
-
self.log_scale = nn.Linear(hidden,output_dim)
|
| 36 |
-
self.latent_proj = nn.Sequential(nn.Linear(latent,hidden),nn.GELU(),nn.Linear(hidden,output_dim))
|
| 37 |
-
def encode(self, track_x, field_x):
|
| 38 |
-
b,t,c,h,w=field_x.shape
|
| 39 |
-
field_z=self.field(field_x.reshape(b*t,c,h,w)).reshape(b,t,-1).flatten(1)
|
| 40 |
-
_,state=self.track_gru(track_x)
|
| 41 |
-
return self.fuse(torch.cat([field_z,state.transpose(0,1).flatten(1)],dim=1))
|
| 42 |
-
def sample(self, track_x, field_x, n, temperature=1.0):
|
| 43 |
-
z=self.encode(track_x,field_x)
|
| 44 |
-
mean=self.mean(z); log_scale=self.log_scale(z).clamp(-5,2)
|
| 45 |
-
eps=torch.randn(n,z.shape[0],self.latent,device=z.device)
|
| 46 |
-
low=self.latent_proj(eps.reshape(-1,self.latent)).reshape(n,z.shape[0],-1)
|
| 47 |
-
independent=torch.randn_like(low)*torch.exp(log_scale).unsqueeze(0)
|
| 48 |
-
return mean.unsqueeze(0)+temperature*(low+independent)
|
| 49 |
-
|
| 50 |
-
raw=torch.load(CKPT,map_location="cpu",weights_only=False)
|
| 51 |
-
if args.device == "mps":
|
| 52 |
-
device = torch.device("mps")
|
| 53 |
-
elif args.device == "cpu":
|
| 54 |
-
device = torch.device("cpu")
|
| 55 |
-
else:
|
| 56 |
-
device=torch.device("mps" if torch.backends.mps.is_available() else "cpu")
|
| 57 |
-
config=raw["config"]
|
| 58 |
-
track_scaler=raw["track_scaler"]; y_scaler=raw["y_scaler"]
|
| 59 |
-
field_mean=np.asarray(raw["field_mean"],dtype="float32")
|
| 60 |
-
field_std=np.asarray(raw["field_std"],dtype="float32")
|
| 61 |
-
model=ERA5CycloneEnsemble(9,10,1,28).to(device)
|
| 62 |
-
model.load_state_dict(raw["model_state"]); model.eval()
|
| 63 |
-
|
| 64 |
-
fixes=[
|
| 65 |
-
("2026-07-09T12:00:00",19.2,128.8,100,952),
|
| 66 |
-
("2026-07-09T18:00:00",20.1,128.2,90,953),
|
| 67 |
-
("2026-07-10T00:00:00",20.8,127.3,75,964),
|
| 68 |
-
("2026-07-10T06:00:00",21.9,126.9,75,962),
|
| 69 |
-
]
|
| 70 |
-
track=[]
|
| 71 |
-
for i,(stamp,lat,lon,wind,pres) in enumerate(fixes):
|
| 72 |
-
dlat=0 if i==0 else lat-fixes[i-1][1]
|
| 73 |
-
dlon=0 if i==0 else ((lon-fixes[i-1][2]+180)%360)-180
|
| 74 |
-
track.append([lat,lon,wind,pres,dlat,dlon,float(np.hypot(dlat,dlon)),np.sin(2*np.pi*191/366),np.cos(2*np.pi*191/366)])
|
| 75 |
-
track=np.asarray(track,dtype="float32")
|
| 76 |
-
track[:,:1]-=track[-1:,0:1]
|
| 77 |
-
track[:,1:2]=((track[:,1:2]-track[-1:,1:2]+180)%360)-180
|
| 78 |
-
track[:,2]/=100.0
|
| 79 |
-
track[:,3]=(track[:,3]-950.0)/50.0
|
| 80 |
-
xtrack=track_scaler.transform(track).astype("float32")[None]
|
| 81 |
-
field=np.zeros((1,1,10,33,33),dtype="float32")
|
| 82 |
-
with torch.no_grad():
|
| 83 |
-
ens=model.sample(torch.from_numpy(xtrack).to(device),torch.from_numpy(field).to(device),50,float(config.get("sample_temperature",1.0))).cpu().numpy()
|
| 84 |
-
ens=ens.mean(0,keepdims=True)+(ens-ens.mean(0,keepdims=True))*1.1297996044158936
|
| 85 |
-
pred=y_scaler.inverse_transform(ens[:,0,:])
|
| 86 |
-
base_lat,base_lon=fixes[-1][1],fixes[-1][2]
|
| 87 |
-
points=[]
|
| 88 |
-
for k,lead in enumerate(config["lead_hours"]):
|
| 89 |
-
j=4*k
|
| 90 |
-
lat=base_lat+pred[:,j]; lon=(base_lon+pred[:,j+1])%360
|
| 91 |
-
points.append({"lead_hours":int(lead),"lat":float(lat.mean()),"lon":float(lon.mean()),"p10_lat":float(np.quantile(lat,.1)),"p90_lat":float(np.quantile(lat,.9)),"p10_lon":float(np.quantile(lon,.1)),"p90_lon":float(np.quantile(lon,.9))})
|
| 92 |
-
result={"storm":"Bavi","source":"JTWC/TCGP fixes through 2026-07-10 0600 UTC","initial_time":"2026-07-10T06:00:00Z","initial_lat":base_lat,"initial_lon":base_lon,"points":points,"device":str(device),"note":"Mac checkpoint inference using current track fixes and mean-normalized atmospheric input because 2026 ERA5 fields are unavailable locally. Not an operational forecast."}
|
| 93 |
-
OUT.write_text(json.dumps(result,indent=2))
|
| 94 |
-
print(json.dumps(result,indent=2))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
run_v23.py
DELETED
|
@@ -1,222 +0,0 @@
|
|
| 1 |
-
"""Run TrackFormer v23 on a storm's track history, in either of two modes:
|
| 2 |
-
|
| 3 |
-
IBTrACS-only (default, no --steering given): the model sees only the storm's own recent
|
| 4 |
-
positions/wind/pressure -- exactly what IBTrACS (or any best-track record) gives you for a past
|
| 5 |
-
storm, nothing else. The steering field and its 12h/24h history are zero-filled with an explicit
|
| 6 |
-
availability flag, the same "unavailable == exact zeros, not fabricated" convention used
|
| 7 |
-
throughout this project whenever a field genuinely isn't there.
|
| 8 |
-
|
| 9 |
-
Full data (--steering given): the model additionally sees a real deep-layer-mean steering-wind
|
| 10 |
-
patch (850/500/200 hPa u/v, weighted 0.269/0.500/0.231) around the storm -- for the current fix
|
| 11 |
-
and, if present, t-12h/t-24h. This is what the project's headline 434.96 km number requires; on
|
| 12 |
-
Typhoon Dolphin (2026) zeroing this field out shifted the 120h forecast position by ~600 km while
|
| 13 |
-
softening the intensity forecast only modestly (99 -> 90 kt) -- see the project's README for the
|
| 14 |
-
full ablation.
|
| 15 |
-
|
| 16 |
-
Usage:
|
| 17 |
-
python run_v23.py --track my_storm.json --out forecast.json
|
| 18 |
-
python run_v23.py --track my_storm.json --steering my_steering.npz --out forecast.json
|
| 19 |
-
|
| 20 |
-
--track JSON format: a list of fixes, OLDEST to NEWEST, spaced 6 hours apart, ending at the fix to
|
| 21 |
-
forecast from ("now"). Up to 9 fixes are used (fewer is fine -- the model pads with the same
|
| 22 |
-
pre-genesis zero-fill it saw for young storms in training); extra leading fixes beyond 9 are
|
| 23 |
-
ignored.
|
| 24 |
-
[{"time": "2026-07-29T00:00", "lat": 14.1, "lon": 169.1, "vmax_kt": 121.6, "pres_hpa": 941},
|
| 25 |
-
{"time": "2026-07-29T06:00", "lat": 14.5, "lon": 168.4, "vmax_kt": 121.7, "pres_hpa": 941}]
|
| 26 |
-
`pres_hpa` may be null/omitted per-fix if unknown -- it is then treated as unavailable for that fix
|
| 27 |
-
(zero-filled, flagged), not fabricated.
|
| 28 |
-
|
| 29 |
-
--steering NPZ format (optional): float32 arrays of shape [2,17,17] (u,v in m/s, 2.5 deg
|
| 30 |
-
resolution, +-20 deg box centered on the storm), keyed by ISO time strings matching entries in
|
| 31 |
-
--track (e.g. "2026-07-29T06:00"). Only the LAST fix's key is required; keys for the fixes 12h and
|
| 32 |
-
24h before it are used for v23's temporal-history stack if present, and zero-filled (flagged
|
| 33 |
-
unavailable) if not -- so a steering file with only the current fix still runs, just without the
|
| 34 |
-
temporal-history benefit. See _fetch_dolphin_steering.py in the repo root for a working example of
|
| 35 |
-
building this from NOAA/NOMADS GFS analysis fields for a live storm, or from ERA5 for a past one.
|
| 36 |
-
"""
|
| 37 |
-
import argparse
|
| 38 |
-
import json
|
| 39 |
-
import math
|
| 40 |
-
import os
|
| 41 |
-
import sys
|
| 42 |
-
|
| 43 |
-
import numpy as np
|
| 44 |
-
import torch
|
| 45 |
-
|
| 46 |
-
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 47 |
-
from trackformer_v23 import build_v23, TMEAN, TSTD, DSC, TARGET_SCALE # noqa: E402
|
| 48 |
-
|
| 49 |
-
R = 111.2 # km per degree latitude
|
| 50 |
-
HIST = 9 # kinematic-history window length the model was trained with
|
| 51 |
-
|
| 52 |
-
_here = os.path.dirname(os.path.abspath(__file__))
|
| 53 |
-
_terrain = np.load(os.path.join(_here, "v23_terrain_wp.npz"))
|
| 54 |
-
_T_LAT, _T_LON, _LSM = _terrain["lat"], _terrain["lon"], _terrain["lsm"]
|
| 55 |
-
_LAND = _LSM > 0.5
|
| 56 |
-
_LAND_LAT = _T_LAT[np.where(_LAND)[0]]
|
| 57 |
-
_LAND_LON = _T_LON[np.where(_LAND)[1]]
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
def dist2land(lat_i, lon_i):
|
| 61 |
-
if len(_LAND_LAT) == 0:
|
| 62 |
-
return 3000.0
|
| 63 |
-
dlat = _LAND_LAT - lat_i
|
| 64 |
-
dlon = (_LAND_LON - lon_i) * math.cos(math.radians(lat_i))
|
| 65 |
-
return float(np.hypot(dlon * R, dlat * R).min())
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
def load_track(path):
|
| 69 |
-
fixes = json.load(open(path))
|
| 70 |
-
fixes = fixes[-HIST:] if len(fixes) > HIST else fixes
|
| 71 |
-
times = [f["time"] for f in fixes]
|
| 72 |
-
lat = np.array([f["lat"] for f in fixes], dtype="float64")
|
| 73 |
-
lon = np.array([f["lon"] for f in fixes], dtype="float64")
|
| 74 |
-
vmax = np.array([f["vmax_kt"] for f in fixes], dtype="float64")
|
| 75 |
-
pres = np.array([f.get("pres_hpa", None) if f.get("pres_hpa", None) is not None else np.nan
|
| 76 |
-
for f in fixes], dtype="float64")
|
| 77 |
-
tns = np.array([np.datetime64(t).astype("datetime64[ns]").astype("int64") for t in times])
|
| 78 |
-
return times, tns, lat, lon, vmax, pres
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
def build_window(times, tns, lat, lon, vmax, pres):
|
| 82 |
-
"""Kinematic/thermodynamic feature window -- same construction as this project's other
|
| 83 |
-
real-storm inference scripts (e.g. _dolphin_v23_v35.py / _noul_v33.py)."""
|
| 84 |
-
n = len(times)
|
| 85 |
-
base = n - 1
|
| 86 |
-
hidx = [max(0, base - HIST + 1 + k) for k in range(HIST)]
|
| 87 |
-
n_padded = max(0, HIST - 1 - base)
|
| 88 |
-
t0 = int(tns[base])
|
| 89 |
-
doy = (np.datetime64(times[base]) - np.datetime64(times[base][:4] + "-01-01")).astype(int) + 1
|
| 90 |
-
phase = 2 * math.pi * doy / 365.25
|
| 91 |
-
seq = np.zeros((HIST, 54), dtype="float32")
|
| 92 |
-
prev, pdir = -1, None
|
| 93 |
-
|
| 94 |
-
def mkm(a, b, c, d):
|
| 95 |
-
dlat = c - a; dlon = ((d - b + 180) % 360) - 180
|
| 96 |
-
return dlon * R * math.cos(math.radians((a + c) / 2)), dlat * R
|
| 97 |
-
|
| 98 |
-
for i, idx in enumerate(hidx):
|
| 99 |
-
e, n_ = mkm(lat[base], lon[base], lat[idx], lon[idx])
|
| 100 |
-
se, sn = (0., 0.) if prev < 0 else mkm(lat[prev], lon[prev], lat[idx], lon[idx])
|
| 101 |
-
f = seq[i]; f[0:4] = [e, n_, se, sn]
|
| 102 |
-
vv = [vmax[idx], pres[idx], np.nan, np.nan]
|
| 103 |
-
for j in range(4):
|
| 104 |
-
f[4 + j] = vv[j] if np.isfinite(vv[j]) else 0.
|
| 105 |
-
f[24:28] = [float(np.isfinite(x)) for x in vv]
|
| 106 |
-
f[21:23] = [math.sin(phase), math.cos(phase)]; f[23] = (t0 - int(tns[idx])) / 3.6e12
|
| 107 |
-
sp = math.hypot(se, sn); hs, hc = (se / sp, sn / sp) if (sp > 1e-3 and prev >= 0) else (0., 0.)
|
| 108 |
-
f[40], f[41], f[42] = hs, hc, sp
|
| 109 |
-
f[43] = (pdir[0] * hc - pdir[1] * hs) if (pdir and (hs or hc) and (pdir[0] or pdir[1])) else 0.
|
| 110 |
-
if prev >= 0:
|
| 111 |
-
dv = np.isfinite(vmax[prev]) and np.isfinite(vmax[idx])
|
| 112 |
-
dp = np.isfinite(pres[prev]) and np.isfinite(pres[idx])
|
| 113 |
-
f[44] = vmax[idx] - vmax[prev] if dv else 0.
|
| 114 |
-
f[45] = pres[idx] - pres[prev] if dp else 0.
|
| 115 |
-
f[46], f[47] = float(dv), float(dp)
|
| 116 |
-
lat_i, lon_i = lat[idx], lon[idx]
|
| 117 |
-
m = np.datetime64(times[idx]).astype("datetime64[M]").astype(int) % 12 + 1
|
| 118 |
-
d2l = dist2land(lat_i, lon_i % 360)
|
| 119 |
-
thermal = 0.5 * 23.44 * math.sin(2 * math.pi * (m - 3) / 12.0)
|
| 120 |
-
f[48] = lat_i; f[49] = abs(lat_i); f[50] = math.sin(math.radians(lon_i)); f[51] = math.cos(math.radians(lon_i))
|
| 121 |
-
f[52] = d2l; f[53] = max(0., min(31., 30. - 0.30 * abs(lat_i - thermal) ** 1.4))
|
| 122 |
-
if hs or hc:
|
| 123 |
-
pdir = (hs, hc)
|
| 124 |
-
prev = idx
|
| 125 |
-
|
| 126 |
-
seq_n = (seq - TMEAN) / TSTD
|
| 127 |
-
vpair = np.concatenate([seq[-1, 2:4], seq[-2, 2:4]]).astype("float32")
|
| 128 |
-
return seq_n, vpair, n_padded
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
def load_steering(path, times):
|
| 132 |
-
"""Returns (slp[1,4,17,17], hist[1,8,17,17], have[1,2]) for the LAST fix in `times`. `path` may
|
| 133 |
-
be None -- then everything is zero-filled (the IBTrACS-only ablation)."""
|
| 134 |
-
if path is None:
|
| 135 |
-
return (np.zeros((1, 4, 17, 17), "float32"),
|
| 136 |
-
np.zeros((1, 8, 17, 17), "float32"),
|
| 137 |
-
np.zeros((1, 2), "float32"))
|
| 138 |
-
dlm = np.load(path)
|
| 139 |
-
now_t = np.datetime64(times[-1])
|
| 140 |
-
|
| 141 |
-
def key_at(back_h):
|
| 142 |
-
return str(now_t - np.timedelta64(back_h, "h"))
|
| 143 |
-
|
| 144 |
-
slp = np.zeros((1, 4, 17, 17), "float32")
|
| 145 |
-
now_key = times[-1]
|
| 146 |
-
if now_key in dlm:
|
| 147 |
-
uv = dlm[now_key]
|
| 148 |
-
slp[0, 2:4] = np.clip(uv / DSC[:, None, None], -4.0, 4.0)
|
| 149 |
-
elif key_at(0) in dlm:
|
| 150 |
-
uv = dlm[key_at(0)]
|
| 151 |
-
slp[0, 2:4] = np.clip(uv / DSC[:, None, None], -4.0, 4.0)
|
| 152 |
-
|
| 153 |
-
hist = np.zeros((1, 8, 17, 17), "float32")
|
| 154 |
-
have = np.zeros((1, 2), "float32")
|
| 155 |
-
cur = slp[0]
|
| 156 |
-
for c, back in enumerate((12, 24)):
|
| 157 |
-
k = key_at(back)
|
| 158 |
-
if k in dlm:
|
| 159 |
-
uv = dlm[k]
|
| 160 |
-
hist[0, c * 4 + 2:c * 4 + 4] = np.clip(uv / DSC[:, None, None], -4.0, 4.0)
|
| 161 |
-
have[0, c] = 1.0
|
| 162 |
-
else:
|
| 163 |
-
hist[0, c * 4:(c + 1) * 4] = cur
|
| 164 |
-
return slp, hist, have
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
@torch.no_grad()
|
| 168 |
-
def forecast(models, times, tns, lat, lon, vmax, pres, steering_path):
|
| 169 |
-
seq_n, vpair, n_padded = build_window(times, tns, lat, lon, vmax, pres)
|
| 170 |
-
tr = torch.from_numpy(seq_n[None]); vp = torch.from_numpy(vpair[None])
|
| 171 |
-
slp, hist, have = load_steering(steering_path, times)
|
| 172 |
-
args = [tr, vp, torch.from_numpy(slp), torch.from_numpy(hist), torch.from_numpy(have)]
|
| 173 |
-
motion = torch.stack([m(*args)[0] for m in models]).mean(0)[0] * TARGET_SCALE
|
| 174 |
-
motion = motion.float().numpy() # [20, 17]
|
| 175 |
-
la, lo = float(lat[-1]), float(lon[-1])
|
| 176 |
-
lats, lons, vmaxs, presses = [], [], [], []
|
| 177 |
-
for L in range(20):
|
| 178 |
-
e, n_ = motion[L, 0], motion[L, 1]
|
| 179 |
-
la = la + n_ / R; lo = lo + e / (R * math.cos(math.radians(la)))
|
| 180 |
-
lats.append(la); lons.append(lo)
|
| 181 |
-
vmaxs.append(float(motion[L, 2])); presses.append(float(motion[L, 3]))
|
| 182 |
-
return lats, lons, vmaxs, presses, n_padded
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
def main():
|
| 186 |
-
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
| 187 |
-
ap.add_argument("--track", required=True, help="track-history JSON, see module docstring")
|
| 188 |
-
ap.add_argument("--steering", default=None, help="steering NPZ (omit for IBTrACS-only mode)")
|
| 189 |
-
ap.add_argument("--seeds", default=os.path.join(_here, "v23_seed*.pt"), help="checkpoint glob")
|
| 190 |
-
ap.add_argument("--out", default=None, help="write forecast JSON here (default: stdout)")
|
| 191 |
-
args = ap.parse_args()
|
| 192 |
-
|
| 193 |
-
import glob
|
| 194 |
-
ckpts = sorted(glob.glob(args.seeds))
|
| 195 |
-
if not ckpts:
|
| 196 |
-
sys.exit(f"no checkpoints matched {args.seeds!r}")
|
| 197 |
-
models = []
|
| 198 |
-
for c in ckpts:
|
| 199 |
-
m = build_v23().eval()
|
| 200 |
-
m.load_state_dict(torch.load(c, map_location="cpu", weights_only=False)["model"])
|
| 201 |
-
models.append(m)
|
| 202 |
-
mode = "IBTrACS-only (steering zeroed)" if args.steering is None else f"full data ({args.steering})"
|
| 203 |
-
print(f"loaded {len(models)} v23 seeds, mode: {mode}", file=sys.stderr)
|
| 204 |
-
|
| 205 |
-
times, tns, lat, lon, vmax, pres = load_track(args.track)
|
| 206 |
-
lats, lons, vmaxs, presses, n_padded = forecast(models, times, tns, lat, lon, vmax, pres, args.steering)
|
| 207 |
-
|
| 208 |
-
out = {"issue_time": times[-1], "mode": mode, "base_lat": float(lat[-1]), "base_lon": float(lon[-1]),
|
| 209 |
-
"lead_hours": list(range(6, 121, 6)),
|
| 210 |
-
"lats": [round(float(x), 3) for x in lats], "lons": [round(float(x), 3) for x in lons],
|
| 211 |
-
"vmax_kt": [round(float(x), 1) for x in vmaxs], "pres_hpa": [round(float(x), 1) for x in presses],
|
| 212 |
-
"n_padded_history": n_padded}
|
| 213 |
-
text = json.dumps(out, indent=2)
|
| 214 |
-
if args.out:
|
| 215 |
-
open(args.out, "w").write(text)
|
| 216 |
-
print(f"wrote {args.out}", file=sys.stderr)
|
| 217 |
-
else:
|
| 218 |
-
print(text)
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
if __name__ == "__main__":
|
| 222 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
trackformer_1_1.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Public Trackformer1.1 loading and inference API."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from trackformer_1_1_intensity import Trackformer11IntensityEnsemble
|
| 8 |
+
from trackformer_1_1_route import (
|
| 9 |
+
LEAD_HOURS,
|
| 10 |
+
build_pacific_route,
|
| 11 |
+
detect_pressure_systems,
|
| 12 |
+
forecast_pacific_state,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
PACKAGE_ROOT = Path(__file__).resolve().parent
|
| 17 |
+
MODEL_ROOT = PACKAGE_ROOT / "models" / "trackformer_1_1"
|
| 18 |
+
CALIBRATION_PATH = MODEL_ROOT / "trackformer_1_1_calibration.json"
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def load_intensity(device: str | None = None) -> Trackformer11IntensityEnsemble:
|
| 22 |
+
"""Load the frozen Trackformer1.1 intensity and structure experts."""
|
| 23 |
+
|
| 24 |
+
return Trackformer11IntensityEnsemble(MODEL_ROOT, CALIBRATION_PATH, device=device)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
__all__ = [
|
| 28 |
+
"CALIBRATION_PATH",
|
| 29 |
+
"LEAD_HOURS",
|
| 30 |
+
"MODEL_ROOT",
|
| 31 |
+
"build_pacific_route",
|
| 32 |
+
"detect_pressure_systems",
|
| 33 |
+
"forecast_pacific_state",
|
| 34 |
+
"load_intensity",
|
| 35 |
+
]
|
trackformer_1_1_base_route.py
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Causal large-system steering ensemble for out-of-archive cases.
|
| 3 |
+
|
| 4 |
+
Trackformer1.1 is a physical route candidate, not a consensus of official forecasts.
|
| 5 |
+
Each member reads only analysis snapshots at or before the issue time. It
|
| 6 |
+
samples multiple pressure levels over inner and synoptic-scale rings around
|
| 7 |
+
the evolving center, and a small SLP-gradient component can represent the
|
| 8 |
+
large-scale pressure field. The final route is the weighted mean of the
|
| 9 |
+
integrated members so curvature and disagreement remain visible.
|
| 10 |
+
|
| 11 |
+
Input field layout:
|
| 12 |
+
fields: (snapshots, 7, latitude, longitude)
|
| 13 |
+
channels: hgt500, u850, v850, u500, v500, u200, v200
|
| 14 |
+
snapshots: current, t-12, t-24 (earlier snapshots may be zeroed)
|
| 15 |
+
pressure: (snapshots, latitude, longitude) SLP in hPa, optional
|
| 16 |
+
|
| 17 |
+
No forecast lead field, official forecast track, or future observation is
|
| 18 |
+
accepted by this module.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
from __future__ import annotations
|
| 22 |
+
|
| 23 |
+
import math
|
| 24 |
+
from typing import Sequence
|
| 25 |
+
|
| 26 |
+
import numpy as np
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
VERSION = "Trackformer1.1-causal-dynamic-big-system-steering-ensemble"
|
| 30 |
+
LEADS = 20
|
| 31 |
+
LEVEL_WEIGHTS = np.asarray([0.269, 0.500, 0.231], dtype="float32")
|
| 32 |
+
MOTION_SLOPES = np.asarray([0.76, 0.78], dtype="float32")
|
| 33 |
+
MOTION_INTERCEPTS = np.asarray([-2.03, 0.40], dtype="float32")
|
| 34 |
+
TENDENCY_SCALES = (0.0, 0.65, 1.25)
|
| 35 |
+
TENDENCY_SCALE_WEIGHTS = (0.15, 0.45, 0.40)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
ROUTE_VARIANTS = (
|
| 39 |
+
{
|
| 40 |
+
"name": "inner_850",
|
| 41 |
+
"level_weights": (1.0, 0.0, 0.0),
|
| 42 |
+
"ring_degrees": (3.0, 5.0),
|
| 43 |
+
"pressure_fraction": 0.00,
|
| 44 |
+
"weight": 0.30,
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"name": "deep_layer_inner",
|
| 48 |
+
"level_weights": (0.269, 0.500, 0.231),
|
| 49 |
+
"ring_degrees": (3.0, 8.0),
|
| 50 |
+
"pressure_fraction": 0.04,
|
| 51 |
+
"weight": 0.22,
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
"name": "broad_850_ridge",
|
| 55 |
+
"level_weights": (1.0, 0.0, 0.0),
|
| 56 |
+
"ring_degrees": (5.0, 11.0),
|
| 57 |
+
"pressure_fraction": 0.05,
|
| 58 |
+
"weight": 0.16,
|
| 59 |
+
},
|
| 60 |
+
{
|
| 61 |
+
"name": "broad_500_trough",
|
| 62 |
+
"level_weights": (0.0, 1.0, 0.0),
|
| 63 |
+
"ring_degrees": (5.0, 12.0),
|
| 64 |
+
"pressure_fraction": 0.04,
|
| 65 |
+
"weight": 0.12,
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"name": "synoptic_deep",
|
| 69 |
+
"level_weights": (0.269, 0.500, 0.231),
|
| 70 |
+
"ring_degrees": (7.0, 16.0),
|
| 71 |
+
"pressure_fraction": 0.10,
|
| 72 |
+
"weight": 0.12,
|
| 73 |
+
},
|
| 74 |
+
{
|
| 75 |
+
"name": "outer_200_jet",
|
| 76 |
+
"level_weights": (0.0, 0.0, 1.0),
|
| 77 |
+
"ring_degrees": (8.0, 18.0),
|
| 78 |
+
"pressure_fraction": 0.02,
|
| 79 |
+
"weight": 0.08,
|
| 80 |
+
},
|
| 81 |
+
)
|
| 82 |
+
CURVATURE_VARIANTS = (0.0, 0.20, 0.40)
|
| 83 |
+
SNAPSHOT_WEIGHTS = (0.50, 0.30, 0.20)
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def _sorted_axes(latitude: np.ndarray, longitude: np.ndarray, field: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
| 87 |
+
latitude = np.asarray(latitude, dtype="float32").reshape(-1)
|
| 88 |
+
longitude = np.asarray(longitude, dtype="float32").reshape(-1)
|
| 89 |
+
field = np.asarray(field, dtype="float32")
|
| 90 |
+
lat_order = np.argsort(latitude)
|
| 91 |
+
lon_order = np.argsort(longitude)
|
| 92 |
+
return latitude[lat_order], longitude[lon_order], field[..., lat_order, :][..., :, lon_order]
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def _longitude_queries(values: np.ndarray, longitude: np.ndarray) -> np.ndarray:
|
| 96 |
+
values = np.asarray(values, dtype="float64")
|
| 97 |
+
low, high = float(longitude[0]), float(longitude[-1])
|
| 98 |
+
if low >= 0.0 and high > 180.0:
|
| 99 |
+
return np.mod(values, 360.0)
|
| 100 |
+
if low < 0.0 and high <= 180.0:
|
| 101 |
+
return ((values + 180.0) % 360.0) - 180.0
|
| 102 |
+
return np.clip(values, low, high)
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def _bilinear(field: np.ndarray, latitude: np.ndarray, longitude: np.ndarray, query_lat: np.ndarray, query_lon: np.ndarray) -> np.ndarray:
|
| 106 |
+
"""Bilinear sample a 2-D field or a 2-channel field on a regular grid."""
|
| 107 |
+
|
| 108 |
+
lat, lon, values = _sorted_axes(latitude, longitude, field)
|
| 109 |
+
query_lat = np.clip(np.asarray(query_lat, dtype="float64"), float(lat[0]), float(lat[-1]))
|
| 110 |
+
query_lon = _longitude_queries(query_lon, lon)
|
| 111 |
+
row = np.interp(query_lat, lat, np.arange(len(lat), dtype="float64"))
|
| 112 |
+
column = np.interp(query_lon, lon, np.arange(len(lon), dtype="float64"))
|
| 113 |
+
row0 = np.floor(row).astype("int64")
|
| 114 |
+
col0 = np.floor(column).astype("int64")
|
| 115 |
+
row1 = np.minimum(row0 + 1, len(lat) - 1)
|
| 116 |
+
col1 = np.minimum(col0 + 1, len(lon) - 1)
|
| 117 |
+
rf = row - row0
|
| 118 |
+
cf = column - col0
|
| 119 |
+
if values.ndim == 2:
|
| 120 |
+
return (
|
| 121 |
+
values[row0, col0] * (1.0 - rf) * (1.0 - cf)
|
| 122 |
+
+ values[row1, col0] * rf * (1.0 - cf)
|
| 123 |
+
+ values[row0, col1] * (1.0 - rf) * cf
|
| 124 |
+
+ values[row1, col1] * rf * cf
|
| 125 |
+
).astype("float32")
|
| 126 |
+
return np.stack([
|
| 127 |
+
_bilinear(values[channel], lat, lon, query_lat, query_lon)
|
| 128 |
+
for channel in range(values.shape[0])
|
| 129 |
+
], axis=1).astype("float32")
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def _geostrophic_field(pressure: np.ndarray, latitude: np.ndarray, longitude: np.ndarray) -> np.ndarray:
|
| 133 |
+
"""Approximate geostrophic wind from causal SLP gradients."""
|
| 134 |
+
|
| 135 |
+
pressure = np.asarray(pressure, dtype="float32")
|
| 136 |
+
lat, lon, sorted_pressure = _sorted_axes(latitude, longitude, pressure)
|
| 137 |
+
earth_radius = 6_371_000.0
|
| 138 |
+
omega = 7.2921159e-5
|
| 139 |
+
dy = np.gradient(np.deg2rad(lat) * earth_radius)
|
| 140 |
+
dx = np.deg2rad(float(np.median(np.diff(lon)))) * earth_radius * np.cos(np.deg2rad(lat))
|
| 141 |
+
dp_dy = np.gradient(sorted_pressure.astype("float64"), axis=0) * 100.0 / dy[:, None]
|
| 142 |
+
dp_dx = np.gradient(sorted_pressure.astype("float64"), axis=1) * 100.0 / dx[:, None]
|
| 143 |
+
coriolis = 2.0 * omega * np.sin(np.deg2rad(lat))
|
| 144 |
+
coriolis = np.where(np.abs(coriolis) < 2.0e-5, np.sign(coriolis) * 2.0e-5, coriolis)
|
| 145 |
+
coriolis = np.where(coriolis == 0.0, 2.0e-5, coriolis)
|
| 146 |
+
rho = 1.15
|
| 147 |
+
u = -dp_dy / (rho * coriolis[:, None])
|
| 148 |
+
v = dp_dx / (rho * coriolis[:, None])
|
| 149 |
+
return np.stack([u, v]).astype("float32")
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def _ring(ring_degrees: tuple[float, float]) -> tuple[np.ndarray, np.ndarray]:
|
| 153 |
+
radii = np.arange(float(ring_degrees[0]), float(ring_degrees[1]) + 0.01, 1.0, dtype="float32")
|
| 154 |
+
angles = np.linspace(0.0, 2.0 * math.pi, 32, endpoint=False, dtype="float32")
|
| 155 |
+
north = (radii[:, None] * np.sin(angles)[None, :]).reshape(-1)
|
| 156 |
+
east = (radii[:, None] * np.cos(angles)[None, :]).reshape(-1)
|
| 157 |
+
return north, east
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def _step(flow: tuple[float, float]) -> np.ndarray:
|
| 161 |
+
u, v = flow
|
| 162 |
+
return np.asarray([
|
| 163 |
+
(float(MOTION_SLOPES[0]) * u + float(MOTION_INTERCEPTS[0])) * 21.6,
|
| 164 |
+
(float(MOTION_SLOPES[1]) * v + float(MOTION_INTERCEPTS[1])) * 21.6,
|
| 165 |
+
], dtype="float32")
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def _clip_tendency(values: np.ndarray) -> np.ndarray:
|
| 169 |
+
"""Limit noisy analysis differences without using any future frame."""
|
| 170 |
+
|
| 171 |
+
values = np.nan_to_num(np.asarray(values, dtype="float32"), copy=True)
|
| 172 |
+
if values.ndim < 3:
|
| 173 |
+
return values
|
| 174 |
+
for channel in range(values.shape[0]):
|
| 175 |
+
scale = float(np.nanpercentile(np.abs(values[channel]), 98.0))
|
| 176 |
+
if scale > 0.0 and math.isfinite(scale):
|
| 177 |
+
values[channel] = np.clip(values[channel], -2.0 * scale, 2.0 * scale)
|
| 178 |
+
return values
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def build_route(
|
| 182 |
+
fields: np.ndarray,
|
| 183 |
+
latitude: np.ndarray,
|
| 184 |
+
longitude: np.ndarray,
|
| 185 |
+
base_latitude: float,
|
| 186 |
+
base_longitude: float,
|
| 187 |
+
pressure: np.ndarray | None = None,
|
| 188 |
+
available: Sequence[float] = (1.0, 1.0),
|
| 189 |
+
route_variants: Sequence[dict] = ROUTE_VARIANTS,
|
| 190 |
+
curvature_variants: Sequence[float] = CURVATURE_VARIANTS,
|
| 191 |
+
snapshot_weights: Sequence[float] = SNAPSHOT_WEIGHTS,
|
| 192 |
+
tendency_scales: Sequence[float] = TENDENCY_SCALES,
|
| 193 |
+
history_motion_km_per_6h: tuple[float, float] | None = None,
|
| 194 |
+
) -> tuple[np.ndarray, np.ndarray, dict]:
|
| 195 |
+
"""Return dynamic member displacements, weights, and causal diagnostics.
|
| 196 |
+
|
| 197 |
+
The current analysis is advanced with a clipped tendency estimated only
|
| 198 |
+
from the two preceding analysis frames. The fields are never replaced by
|
| 199 |
+
a positive-lead forecast. Every member still starts at the same issue
|
| 200 |
+
position, but its broad steering environment can evolve with lead.
|
| 201 |
+
"""
|
| 202 |
+
|
| 203 |
+
fields = np.asarray(fields, dtype="float32")
|
| 204 |
+
if fields.ndim != 4 or fields.shape[0] != 3 or fields.shape[1] != 7:
|
| 205 |
+
raise ValueError(f"expected fields (3,7,H,W), got {fields.shape}")
|
| 206 |
+
latitude = np.asarray(latitude, dtype="float32")
|
| 207 |
+
longitude = np.asarray(longitude, dtype="float32")
|
| 208 |
+
if pressure is None:
|
| 209 |
+
pressure = np.zeros((3, len(latitude), len(longitude)), dtype="float32")
|
| 210 |
+
pressure_available = np.zeros(3, dtype=bool)
|
| 211 |
+
else:
|
| 212 |
+
pressure = np.asarray(pressure, dtype="float32")
|
| 213 |
+
if pressure.shape != (3, len(latitude), len(longitude)):
|
| 214 |
+
raise ValueError(f"expected pressure (3,H,W), got {pressure.shape}")
|
| 215 |
+
pressure_available = np.isfinite(pressure).all(axis=(1, 2))
|
| 216 |
+
valid_snapshot = np.ones(3, dtype=bool)
|
| 217 |
+
avail = np.asarray(available, dtype="float64").reshape(-1)
|
| 218 |
+
if avail.size >= 1:
|
| 219 |
+
valid_snapshot[1] = bool(avail[0] > 0.5)
|
| 220 |
+
if avail.size >= 2:
|
| 221 |
+
valid_snapshot[2] = bool(avail[1] > 0.5)
|
| 222 |
+
valid_snapshot &= np.isfinite(fields).all(axis=(1, 2, 3))
|
| 223 |
+
if not valid_snapshot[0]:
|
| 224 |
+
raise RuntimeError("current causal analysis snapshot is unavailable")
|
| 225 |
+
snap_weights = np.asarray(snapshot_weights, dtype="float64") * valid_snapshot
|
| 226 |
+
snap_weights /= snap_weights.sum()
|
| 227 |
+
curve_values = np.asarray(tuple(curvature_variants), dtype="float64")
|
| 228 |
+
curve_values = np.clip(curve_values[np.isfinite(curve_values)], 0.0, 0.60)
|
| 229 |
+
if not len(curve_values):
|
| 230 |
+
raise ValueError("curvature_variants is empty")
|
| 231 |
+
tendency_values = np.asarray(tuple(tendency_scales), dtype="float64")
|
| 232 |
+
tendency_weights = np.asarray(tuple(TENDENCY_SCALE_WEIGHTS), dtype="float64")
|
| 233 |
+
if tendency_values.shape != tendency_weights.shape or np.any(~np.isfinite(tendency_values)):
|
| 234 |
+
raise ValueError("tendency_scales must match the fixed tendency weight table")
|
| 235 |
+
tendency_values = np.maximum(tendency_values, 0.0)
|
| 236 |
+
tendency_weights = np.maximum(tendency_weights, 0.0)
|
| 237 |
+
tendency_weights /= tendency_weights.sum()
|
| 238 |
+
variant_weights = np.asarray([float(item["weight"]) for item in route_variants], dtype="float64")
|
| 239 |
+
variant_weights = np.maximum(variant_weights, 0.0)
|
| 240 |
+
variant_weights /= variant_weights.sum()
|
| 241 |
+
geo_fields = np.zeros((3, 2, len(latitude), len(longitude)), dtype="float32")
|
| 242 |
+
for index in range(3):
|
| 243 |
+
if pressure_available[index]:
|
| 244 |
+
geo_fields[index] = _geostrophic_field(pressure[index], latitude, longitude)
|
| 245 |
+
|
| 246 |
+
current_fields = fields[0]
|
| 247 |
+
recent_delta = _clip_tendency(fields[0] - fields[1]) if valid_snapshot[1] else np.zeros_like(current_fields)
|
| 248 |
+
older_delta = _clip_tendency(fields[1] - fields[2]) if valid_snapshot[2] else recent_delta.copy()
|
| 249 |
+
tendency_views = (
|
| 250 |
+
recent_delta,
|
| 251 |
+
_clip_tendency(0.5 * (recent_delta + older_delta)),
|
| 252 |
+
older_delta,
|
| 253 |
+
)
|
| 254 |
+
current_geo = geo_fields[0]
|
| 255 |
+
recent_geo_delta = _clip_tendency(geo_fields[0] - geo_fields[1]) if pressure_available[1] else np.zeros_like(current_geo)
|
| 256 |
+
older_geo_delta = _clip_tendency(geo_fields[1] - geo_fields[2]) if pressure_available[2] else recent_geo_delta.copy()
|
| 257 |
+
geo_tendency_views = (
|
| 258 |
+
recent_geo_delta,
|
| 259 |
+
_clip_tendency(0.5 * (recent_geo_delta + older_geo_delta)),
|
| 260 |
+
older_geo_delta,
|
| 261 |
+
)
|
| 262 |
+
history_step = None
|
| 263 |
+
if history_motion_km_per_6h is not None:
|
| 264 |
+
candidate = np.asarray(history_motion_km_per_6h, dtype="float32").reshape(-1)
|
| 265 |
+
if candidate.size == 2 and np.isfinite(candidate).all():
|
| 266 |
+
history_step = candidate
|
| 267 |
+
|
| 268 |
+
members: list[np.ndarray] = []
|
| 269 |
+
weights: list[float] = []
|
| 270 |
+
member_rows: list[dict] = []
|
| 271 |
+
current_wind_levels = np.stack(
|
| 272 |
+
[current_fields[1:3], current_fields[3:5], current_fields[5:7]],
|
| 273 |
+
axis=0,
|
| 274 |
+
)
|
| 275 |
+
for snapshot_index in range(3):
|
| 276 |
+
if snap_weights[snapshot_index] <= 0.0:
|
| 277 |
+
continue
|
| 278 |
+
for variant_index, variant in enumerate(route_variants):
|
| 279 |
+
level_weights = np.asarray(variant["level_weights"], dtype="float32")
|
| 280 |
+
level_weights /= level_weights.sum()
|
| 281 |
+
ring_north, ring_east = _ring(tuple(variant["ring_degrees"]))
|
| 282 |
+
wind_field = np.tensordot(level_weights, current_wind_levels, axes=(0, 0)).astype("float32")
|
| 283 |
+
wind_tendency = np.tensordot(
|
| 284 |
+
level_weights,
|
| 285 |
+
np.stack(
|
| 286 |
+
[
|
| 287 |
+
tendency_views[snapshot_index][1:3],
|
| 288 |
+
tendency_views[snapshot_index][3:5],
|
| 289 |
+
tendency_views[snapshot_index][5:7],
|
| 290 |
+
],
|
| 291 |
+
axis=0,
|
| 292 |
+
),
|
| 293 |
+
axes=(0, 0),
|
| 294 |
+
).astype("float32")
|
| 295 |
+
pressure_field = current_geo
|
| 296 |
+
geo_tendency = geo_tendency_views[snapshot_index]
|
| 297 |
+
pressure_fraction = float(variant.get("pressure_fraction", 0.0)) if pressure_available[0] else 0.0
|
| 298 |
+
|
| 299 |
+
def flow_from(field: np.ndarray, lat: float, lon: float) -> tuple[float, float]:
|
| 300 |
+
sample_lat = float(lat) + ring_north
|
| 301 |
+
sample_lon = float(lon) + ring_east / max(math.cos(math.radians(float(lat))), 0.20)
|
| 302 |
+
samples = _bilinear(field, latitude, longitude, sample_lat, sample_lon)
|
| 303 |
+
return float(np.nanmean(samples[:, 0])), float(np.nanmean(samples[:, 1]))
|
| 304 |
+
|
| 305 |
+
for tendency_index, tendency_scale in enumerate(tendency_values):
|
| 306 |
+
for curvature in curve_values:
|
| 307 |
+
lat = float(base_latitude)
|
| 308 |
+
lon = float(base_longitude)
|
| 309 |
+
previous_step = _step(flow_from(wind_field, lat, lon))
|
| 310 |
+
steps = np.zeros((LEADS, 2), dtype="float32")
|
| 311 |
+
waypoints = []
|
| 312 |
+
for lead in range(LEADS):
|
| 313 |
+
# A bounded extrapolation of the observed analysis
|
| 314 |
+
# tendency lets the steering regime change with lead.
|
| 315 |
+
progress = min(2.0, 0.5 * ((lead + 1) * 6.0 / 12.0))
|
| 316 |
+
dynamic_wind = wind_field + float(tendency_scale) * progress * wind_tendency
|
| 317 |
+
dynamic_geo = pressure_field + float(tendency_scale) * progress * geo_tendency
|
| 318 |
+
local_u, local_v = flow_from(dynamic_wind, lat, lon)
|
| 319 |
+
local_geo_u, local_geo_v = flow_from(dynamic_geo, lat, lon) if pressure_fraction else (0.0, 0.0)
|
| 320 |
+
local_flow = (
|
| 321 |
+
(1.0 - pressure_fraction) * local_u + pressure_fraction * local_geo_u,
|
| 322 |
+
(1.0 - pressure_fraction) * local_v + pressure_fraction * local_geo_v,
|
| 323 |
+
)
|
| 324 |
+
weather_step = _step(local_flow)
|
| 325 |
+
if history_step is not None:
|
| 326 |
+
history_weight = 0.20 * math.exp(-((lead + 1) * 6.0) / 36.0)
|
| 327 |
+
weather_step = (1.0 - history_weight) * weather_step + history_weight * history_step
|
| 328 |
+
inertia = min(0.18, 0.04 + 0.08 * float(curvature)) if lead else 0.0
|
| 329 |
+
step = (1.0 - inertia) * weather_step + inertia * previous_step
|
| 330 |
+
previous_step = step
|
| 331 |
+
steps[lead] = step
|
| 332 |
+
lat += float(step[1]) / 111.2
|
| 333 |
+
lon += float(step[0]) / (111.2 * max(math.cos(math.radians(lat)), 0.20))
|
| 334 |
+
lon %= 360.0
|
| 335 |
+
if lead in (0, 3, 7, 11, 15, 19):
|
| 336 |
+
waypoints.append({
|
| 337 |
+
"lead_hours": (lead + 1) * 6,
|
| 338 |
+
"latitude": round(lat, 4),
|
| 339 |
+
"longitude": round(lon, 4),
|
| 340 |
+
"u_mean_mps": round(local_flow[0], 4),
|
| 341 |
+
"v_mean_mps": round(local_flow[1], 4),
|
| 342 |
+
"tendency_progress": round(progress, 4),
|
| 343 |
+
})
|
| 344 |
+
members.append(steps)
|
| 345 |
+
weights.append(
|
| 346 |
+
float(snap_weights[snapshot_index])
|
| 347 |
+
* float(variant_weights[variant_index])
|
| 348 |
+
* float(tendency_weights[tendency_index])
|
| 349 |
+
/ float(len(curve_values))
|
| 350 |
+
)
|
| 351 |
+
member_rows.append({
|
| 352 |
+
"member_index": len(members) - 1,
|
| 353 |
+
"snapshot_index": snapshot_index,
|
| 354 |
+
"variant": str(variant["name"]),
|
| 355 |
+
"ring_degrees": list(variant["ring_degrees"]),
|
| 356 |
+
"level_weights": level_weights.tolist(),
|
| 357 |
+
"pressure_fraction": pressure_fraction,
|
| 358 |
+
"curvature_fraction": round(float(curvature), 4),
|
| 359 |
+
"tendency_scale": round(float(tendency_scale), 4),
|
| 360 |
+
"sampled_waypoints": waypoints,
|
| 361 |
+
})
|
| 362 |
+
member_weights = np.asarray(weights, dtype="float64")
|
| 363 |
+
member_weights /= member_weights.sum()
|
| 364 |
+
return np.stack(members).astype("float32"), member_weights, {
|
| 365 |
+
"version": VERSION,
|
| 366 |
+
"policy": "causal dynamic multi-level large-system analysis ensemble",
|
| 367 |
+
"input_policy": "current, t-12, and t-24 analysis fields only; the lead evolution is a bounded extrapolation of their observed tendency; no future analysis, forecast product, official track, or future observed row",
|
| 368 |
+
"channels": ["hgt500", "u850", "v850", "u500", "v500", "u200", "v200"],
|
| 369 |
+
"snapshot_weights": snap_weights.tolist(),
|
| 370 |
+
"member_weights": member_weights.tolist(),
|
| 371 |
+
"member_count": len(member_rows),
|
| 372 |
+
"route_variants": [dict(item) for item in route_variants],
|
| 373 |
+
"curvature_variants": [float(value) for value in curve_values],
|
| 374 |
+
"tendency_scales": [float(value) for value in tendency_values],
|
| 375 |
+
"tendency_weights": tendency_weights.tolist(),
|
| 376 |
+
"tendency_method": "bounded current-minus-past analysis tendency, capped at two 12-hour differences",
|
| 377 |
+
"history_motion_km_per_6h": None if history_step is None else history_step.tolist(),
|
| 378 |
+
"member_rows": member_rows,
|
| 379 |
+
"large_systems": [
|
| 380 |
+
"Pacific subtropical ridge represented by broad 850/500-hPa flow and 500-hPa height field",
|
| 381 |
+
"midlatitude trough and jet influence represented by broad 500/200-hPa rings",
|
| 382 |
+
"pressure-gradient steering represented by causal SLP geostrophic component when supplied",
|
| 383 |
+
],
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
def integrate_from_issue(member_displacements: np.ndarray, base_latitude: float, base_longitude: float) -> list[list[dict]]:
|
| 388 |
+
"""Convert member km steps to serializable geographic paths."""
|
| 389 |
+
|
| 390 |
+
paths = []
|
| 391 |
+
for member in np.asarray(member_displacements, dtype="float32"):
|
| 392 |
+
lat, lon = float(base_latitude), float(base_longitude)
|
| 393 |
+
points = []
|
| 394 |
+
for lead, step in enumerate(member, start=1):
|
| 395 |
+
lon += float(step[0]) / (111.2 * max(math.cos(math.radians(lat)), 0.20))
|
| 396 |
+
lat += float(step[1]) / 111.2
|
| 397 |
+
lon %= 360.0
|
| 398 |
+
points.append({
|
| 399 |
+
"lead_hours": lead * 6,
|
| 400 |
+
"latitude": round(lat, 4),
|
| 401 |
+
"longitude": round(lon, 4),
|
| 402 |
+
})
|
| 403 |
+
paths.append(points)
|
| 404 |
+
return paths
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
def weighted_route(member_displacements: np.ndarray, member_weights: np.ndarray) -> np.ndarray:
|
| 408 |
+
return np.tensordot(np.asarray(member_weights, dtype="float32"), np.asarray(member_displacements, dtype="float32"), axes=(0, 0)).astype("float32")
|
| 409 |
+
|
| 410 |
+
|
| 411 |
+
__all__ = [
|
| 412 |
+
"VERSION",
|
| 413 |
+
"ROUTE_VARIANTS",
|
| 414 |
+
"CURVATURE_VARIANTS",
|
| 415 |
+
"SNAPSHOT_WEIGHTS",
|
| 416 |
+
"build_route",
|
| 417 |
+
"integrate_from_issue",
|
| 418 |
+
"weighted_route",
|
| 419 |
+
]
|
trackformer_1_1_intensity.py
ADDED
|
@@ -0,0 +1,748 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Causal Trackformer1.1 intensity and wind-structure inference.
|
| 3 |
+
|
| 4 |
+
The Trackformer1.1 route is responsible for position and the western-Pacific pressure
|
| 5 |
+
state. This module supplies the previously missing storm-structure outputs
|
| 6 |
+
without changing that route: maximum sustained wind, central pressure, radius
|
| 7 |
+
of maximum wind, and the four-quadrant R34/R50/R64 radii.
|
| 8 |
+
|
| 9 |
+
The default weights are a validated residual-anchor spatial ensemble with a
|
| 10 |
+
secondary structure expert and causal temporal branch. They are loaded only
|
| 11 |
+
for inference and are conditioned on the same nine-step observed track window
|
| 12 |
+
plus the current four-channel analysis patch.
|
| 13 |
+
No positive-lead atmospheric field or official agency forecast is consumed.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
import json
|
| 19 |
+
import os
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
|
| 22 |
+
import numpy as np
|
| 23 |
+
import torch
|
| 24 |
+
import torch.nn as nn
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
LEADS = 20
|
| 28 |
+
NM_TO_KM = 1.852
|
| 29 |
+
TARGET_SCALE = np.asarray([100.0, 100.0, 35.0, 20.0, 50.0] + [50.0] * 12, dtype="float32")
|
| 30 |
+
STRUCTURE_SCALE = TARGET_SCALE[2:]
|
| 31 |
+
THERMO_ENV_COLS = (
|
| 32 |
+
[4, 5, 6, 7]
|
| 33 |
+
+ list(range(8, 20))
|
| 34 |
+
+ list(range(24, 40))
|
| 35 |
+
+ [44, 45, 46, 47, 48, 49, 50, 51, 52, 53]
|
| 36 |
+
)
|
| 37 |
+
# The 90th percentile of positive six-hour central-pressure changes in the
|
| 38 |
+
# training split is 6 hPa. Use that train-only statistic to stop a coarse
|
| 39 |
+
# forecast-map minimum from disappearing in one step and being replaced by a
|
| 40 |
+
# different synoptic cell.
|
| 41 |
+
MAP_PRESSURE_RECOVERY_LIMIT_HPA = 6.0
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def sinusoidal(length: int, width: int) -> torch.Tensor:
|
| 45 |
+
position = torch.arange(length).unsqueeze(1).float()
|
| 46 |
+
divisor = torch.exp(torch.arange(0, width, 2).float() * (-np.log(10000.0) / width))
|
| 47 |
+
result = torch.zeros(length, width)
|
| 48 |
+
result[:, 0::2] = torch.sin(position * divisor)
|
| 49 |
+
result[:, 1::2] = torch.cos(position * divisor)
|
| 50 |
+
return result
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class StructureSpatialExpert(nn.Module):
|
| 54 |
+
"""Inference copy of the architecture used by the frozen experts."""
|
| 55 |
+
|
| 56 |
+
def __init__(self, width: int, layers: int, heads: int, structure_residual: bool = False):
|
| 57 |
+
super().__init__()
|
| 58 |
+
self.structure_residual = bool(structure_residual)
|
| 59 |
+
self.track_proj = nn.Linear(len(THERMO_ENV_COLS), width)
|
| 60 |
+
self.register_buffer("track_time", sinusoidal(9, width).unsqueeze(0))
|
| 61 |
+
track_layer = nn.TransformerEncoderLayer(
|
| 62 |
+
width,
|
| 63 |
+
heads,
|
| 64 |
+
width * 4,
|
| 65 |
+
0.12,
|
| 66 |
+
batch_first=True,
|
| 67 |
+
norm_first=True,
|
| 68 |
+
activation="gelu",
|
| 69 |
+
)
|
| 70 |
+
self.track_encoder = nn.TransformerEncoder(track_layer, layers)
|
| 71 |
+
self.field_encoder = nn.Sequential(
|
| 72 |
+
nn.Conv2d(4, 64, 3, padding=1),
|
| 73 |
+
nn.GroupNorm(8, 64),
|
| 74 |
+
nn.SiLU(),
|
| 75 |
+
nn.Conv2d(64, width, 3, stride=2, padding=1),
|
| 76 |
+
nn.GroupNorm(8, width),
|
| 77 |
+
nn.SiLU(),
|
| 78 |
+
)
|
| 79 |
+
self.field_pool = nn.AvgPool2d(kernel_size=2, stride=2)
|
| 80 |
+
self.field_norm = nn.LayerNorm(width)
|
| 81 |
+
self.field_pos = nn.Parameter(torch.randn(1, 16, width) * 0.02)
|
| 82 |
+
decoder_layer = nn.TransformerDecoderLayer(
|
| 83 |
+
width,
|
| 84 |
+
heads,
|
| 85 |
+
width * 4,
|
| 86 |
+
0.12,
|
| 87 |
+
batch_first=True,
|
| 88 |
+
norm_first=True,
|
| 89 |
+
activation="gelu",
|
| 90 |
+
)
|
| 91 |
+
self.query = nn.Parameter(torch.randn(1, LEADS, width) * 0.02)
|
| 92 |
+
self.register_buffer("lead_time", sinusoidal(LEADS, width).unsqueeze(0))
|
| 93 |
+
self.decoder = nn.TransformerDecoder(decoder_layer, layers)
|
| 94 |
+
self.state = nn.Linear(width, 15)
|
| 95 |
+
self.log_scale = nn.Linear(width, 15)
|
| 96 |
+
|
| 97 |
+
def forward(
|
| 98 |
+
self,
|
| 99 |
+
track: torch.Tensor,
|
| 100 |
+
field: torch.Tensor,
|
| 101 |
+
current: torch.Tensor,
|
| 102 |
+
available: torch.Tensor,
|
| 103 |
+
current_structure: torch.Tensor | None = None,
|
| 104 |
+
structure_available: torch.Tensor | None = None,
|
| 105 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 106 |
+
track_tokens = self.track_encoder(self.track_proj(track[:, :, THERMO_ENV_COLS]) + self.track_time)
|
| 107 |
+
field_tokens = self.field_pool(self.field_encoder(field)).flatten(2).transpose(1, 2)
|
| 108 |
+
field_tokens = self.field_norm(field_tokens + self.field_pos)
|
| 109 |
+
memory = torch.cat([track_tokens, field_tokens], dim=1)
|
| 110 |
+
query = (self.query + self.lead_time).expand(track.shape[0], -1, -1)
|
| 111 |
+
hidden = self.decoder(query, memory)
|
| 112 |
+
state = self.state(hidden)
|
| 113 |
+
state = state.clone()
|
| 114 |
+
state[:, :, :2] = state[:, :, :2] + (current * available)[:, None, :]
|
| 115 |
+
if self.structure_residual:
|
| 116 |
+
if current_structure is None or structure_available is None:
|
| 117 |
+
raise ValueError("structure residual mode requires current structure and availability tensors")
|
| 118 |
+
state[:, :, 2:] = state[:, :, 2:] + current_structure[:, None, :] * structure_available[:, None, :]
|
| 119 |
+
return state, self.log_scale(hidden)
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def _device(requested: str | None) -> torch.device:
|
| 123 |
+
value = requested or os.environ.get("TRACKFORMER_1_1_DEVICE")
|
| 124 |
+
if value:
|
| 125 |
+
return torch.device(value)
|
| 126 |
+
if torch.cuda.is_available():
|
| 127 |
+
return torch.device("cuda")
|
| 128 |
+
if torch.backends.mps.is_available():
|
| 129 |
+
return torch.device("mps")
|
| 130 |
+
return torch.device("cpu")
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def _calibrated_wind(states: np.ndarray, current_wind: float, calibration: dict) -> np.ndarray:
|
| 134 |
+
alphas = calibration.get("wind_blend_alpha")
|
| 135 |
+
if not alphas:
|
| 136 |
+
return states[:, :, 0]
|
| 137 |
+
predicted = states[:, :, 0]
|
| 138 |
+
result = np.empty_like(predicted)
|
| 139 |
+
for lead, alpha in enumerate(alphas[:LEADS]):
|
| 140 |
+
result[:, lead] = float(alpha) * predicted[:, lead] + (1.0 - float(alpha)) * current_wind
|
| 141 |
+
return np.clip(result, 0.0, 190.0)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def _calibrated_pressure(
|
| 145 |
+
states: np.ndarray,
|
| 146 |
+
current_wind: float,
|
| 147 |
+
current_pressure: float,
|
| 148 |
+
previous_wind: float,
|
| 149 |
+
previous_pressure: float,
|
| 150 |
+
calibrated_wind: np.ndarray,
|
| 151 |
+
calibration: dict,
|
| 152 |
+
) -> np.ndarray:
|
| 153 |
+
joint = calibration.get("pressure_joint_calibrations")
|
| 154 |
+
if not joint:
|
| 155 |
+
return np.clip(states[:, :, 1], 850.0, 1025.0)
|
| 156 |
+
predicted_pressure = states[:, :, 1]
|
| 157 |
+
result = np.empty_like(predicted_pressure)
|
| 158 |
+
for lead, item in enumerate(joint[:LEADS]):
|
| 159 |
+
features = np.column_stack([
|
| 160 |
+
np.full(len(predicted_pressure), 1.0, dtype="float32"),
|
| 161 |
+
np.full(len(predicted_pressure), current_pressure, dtype="float32"),
|
| 162 |
+
np.full(len(predicted_pressure), current_wind, dtype="float32"),
|
| 163 |
+
calibrated_wind[:, lead],
|
| 164 |
+
predicted_pressure[:, lead],
|
| 165 |
+
predicted_pressure[:, lead] - current_pressure,
|
| 166 |
+
calibrated_wind[:, lead] - current_wind,
|
| 167 |
+
np.full(len(predicted_pressure), current_pressure - previous_pressure, dtype="float32"),
|
| 168 |
+
np.full(len(predicted_pressure), current_wind - previous_wind, dtype="float32"),
|
| 169 |
+
])
|
| 170 |
+
mean = np.asarray(item["mean"], dtype="float32")
|
| 171 |
+
scale = np.maximum(np.asarray(item["scale"], dtype="float32"), 1e-6)
|
| 172 |
+
normalized = (features - mean) / scale
|
| 173 |
+
normalized[:, 0] = 1.0
|
| 174 |
+
result[:, lead] = normalized @ np.asarray(item["beta"], dtype="float32")
|
| 175 |
+
anchor_alpha = calibration.get("pressure_anchor_alpha")
|
| 176 |
+
if anchor_alpha and np.isfinite(current_pressure) and current_pressure > 0.0:
|
| 177 |
+
for lead, alpha in enumerate(anchor_alpha[:LEADS]):
|
| 178 |
+
alpha = float(np.clip(alpha, 0.0, 2.0))
|
| 179 |
+
result[:, lead] = alpha * result[:, lead] + (1.0 - alpha) * float(current_pressure)
|
| 180 |
+
return np.clip(result, 850.0, 1025.0)
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def _sanitize(states: np.ndarray) -> np.ndarray:
|
| 184 |
+
"""Apply output bounds and preserve the R34 >= R50 >= R64 ordering."""
|
| 185 |
+
|
| 186 |
+
result = np.asarray(states, dtype="float32").copy()
|
| 187 |
+
result[:, :, 0] = np.clip(result[:, :, 0], 0.0, 190.0)
|
| 188 |
+
result[:, :, 1] = np.clip(result[:, :, 1], 850.0, 1025.0)
|
| 189 |
+
result[:, :, 2] = np.clip(result[:, :, 2], 0.0, 300.0)
|
| 190 |
+
radii = np.clip(result[:, :, 3:15], 0.0, 1000.0)
|
| 191 |
+
for offset in range(4):
|
| 192 |
+
radii[:, :, 4 + offset] = np.minimum(radii[:, :, 4 + offset], radii[:, :, offset])
|
| 193 |
+
radii[:, :, 8 + offset] = np.minimum(radii[:, :, 8 + offset], radii[:, :, 4 + offset])
|
| 194 |
+
result[:, :, 3:15] = radii
|
| 195 |
+
return result
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def _row(mean: np.ndarray, spread: np.ndarray, lead: int) -> dict:
|
| 199 |
+
# The historical IBTrACS structure labels are nautical miles. Keep the
|
| 200 |
+
# neural/calibration state in that native unit, but expose all distance
|
| 201 |
+
# fields in kilometres because the map and route use kilometres.
|
| 202 |
+
radii = mean[lead, 3:15] * NM_TO_KM
|
| 203 |
+
radius_spread = spread[lead, 3:15] * NM_TO_KM
|
| 204 |
+
return {
|
| 205 |
+
"vmax_kt": round(float(mean[lead, 0]), 2),
|
| 206 |
+
"vmax_spread_kt": round(float(spread[lead, 0]), 2),
|
| 207 |
+
"central_pressure_hpa": round(float(mean[lead, 1]), 2),
|
| 208 |
+
"pressure_spread_hpa": round(float(spread[lead, 1]), 2),
|
| 209 |
+
"rmw_km": round(float(mean[lead, 2] * NM_TO_KM), 2),
|
| 210 |
+
"rmw_spread_km": round(float(spread[lead, 2] * NM_TO_KM), 2),
|
| 211 |
+
"wind_radii_km": [round(float(value), 2) for value in radii],
|
| 212 |
+
"wind_radii_spread_km": [round(float(value), 2) for value in radius_spread],
|
| 213 |
+
# Keep the map renderer's generic pressure field alias as well.
|
| 214 |
+
"pressure_hpa": round(float(mean[lead, 1]), 2),
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
def _map_grid_feature(
|
| 219 |
+
pressure: np.ndarray,
|
| 220 |
+
fields: np.ndarray,
|
| 221 |
+
latitude: np.ndarray,
|
| 222 |
+
longitude: np.ndarray,
|
| 223 |
+
query_latitude: float,
|
| 224 |
+
query_longitude: float,
|
| 225 |
+
) -> dict:
|
| 226 |
+
"""Extract a storm-relative signal from one forecast pressure map.
|
| 227 |
+
|
| 228 |
+
The map can be much coarser than the route grid, especially for the Tip
|
| 229 |
+
reanalysis. We therefore use a nearby pressure minimum, a broad annulus
|
| 230 |
+
environment, and quadrant-wise anomaly extents rather than pretending that
|
| 231 |
+
a single grid cell is an exact storm center.
|
| 232 |
+
"""
|
| 233 |
+
|
| 234 |
+
pressure = np.asarray(pressure, dtype="float32")
|
| 235 |
+
fields = np.asarray(fields, dtype="float32")
|
| 236 |
+
latitude = np.asarray(latitude, dtype="float32").reshape(-1)
|
| 237 |
+
longitude = np.asarray(longitude, dtype="float32").reshape(-1)
|
| 238 |
+
lat_order = np.argsort(latitude)
|
| 239 |
+
lon_order = np.argsort(longitude)
|
| 240 |
+
latitude = latitude[lat_order]
|
| 241 |
+
longitude = longitude[lon_order]
|
| 242 |
+
pressure = pressure[np.ix_(lat_order, lon_order)]
|
| 243 |
+
fields = fields[:, lat_order, :][:, :, lon_order]
|
| 244 |
+
lat_grid, lon_grid = np.meshgrid(latitude, longitude, indexing="ij")
|
| 245 |
+
delta_lon = ((lon_grid - float(query_longitude) + 180.0) % 360.0) - 180.0
|
| 246 |
+
delta_lat = lat_grid - float(query_latitude)
|
| 247 |
+
distance_deg = np.hypot(delta_lat, delta_lon * np.cos(np.deg2rad(float(query_latitude))))
|
| 248 |
+
query_row, query_column = np.unravel_index(int(np.nanargmin(distance_deg)), distance_deg.shape)
|
| 249 |
+
query_pressure = float(pressure[query_row, query_column])
|
| 250 |
+
query_wind850 = float(np.hypot(fields[1, query_row, query_column], fields[2, query_row, query_column]) * 1.94384) if fields.shape[0] >= 3 else float("nan")
|
| 251 |
+
# A minimum farther than this is likely a separate synoptic system rather
|
| 252 |
+
# than the cyclone represented by the route point. Keep the local query
|
| 253 |
+
# as a fallback instead of allowing a distant low to control intensity.
|
| 254 |
+
nearby = distance_deg <= 6.0
|
| 255 |
+
finite = np.isfinite(pressure)
|
| 256 |
+
candidate = np.where(nearby & finite, pressure, np.inf)
|
| 257 |
+
if not np.isfinite(candidate).any():
|
| 258 |
+
row, column = query_row, query_column
|
| 259 |
+
else:
|
| 260 |
+
row, column = np.unravel_index(int(np.argmin(candidate)), candidate.shape)
|
| 261 |
+
center_latitude = float(latitude[row])
|
| 262 |
+
center_longitude = float(longitude[column])
|
| 263 |
+
center_pressure = float(pressure[row, column])
|
| 264 |
+
center_delta_lon = ((lon_grid - center_longitude + 180.0) % 360.0) - 180.0
|
| 265 |
+
center_delta_lat = lat_grid - center_latitude
|
| 266 |
+
center_distance_deg = np.hypot(
|
| 267 |
+
center_delta_lat,
|
| 268 |
+
center_delta_lon * np.cos(np.deg2rad(center_latitude)),
|
| 269 |
+
)
|
| 270 |
+
annulus = (
|
| 271 |
+
(center_distance_deg >= 2.5)
|
| 272 |
+
& (center_distance_deg <= 6.0)
|
| 273 |
+
& np.isfinite(pressure)
|
| 274 |
+
)
|
| 275 |
+
environment = float(np.nanmedian(pressure[annulus])) if annulus.any() else center_pressure + 15.0
|
| 276 |
+
anomaly = np.maximum(environment - pressure, 0.0)
|
| 277 |
+
# Four degrees is a conservative upper bound for a pressure-derived
|
| 278 |
+
# tropical wind footprint on this coarse map. Without it, a weak broad
|
| 279 |
+
# gradient would be misreported as an 800-km R34.
|
| 280 |
+
core = (center_distance_deg <= 4.0) & np.isfinite(anomaly)
|
| 281 |
+
peak_anomaly = float(np.nanmax(anomaly[core])) if core.any() else 0.0
|
| 282 |
+
radii: list[float] = []
|
| 283 |
+
# These are pressure-anomaly fractions used as a stable proxy for the
|
| 284 |
+
# R34/R50/R64 shape. The learned radius output remains the absolute
|
| 285 |
+
# baseline; only its map-observed expansion/contraction is applied.
|
| 286 |
+
for fraction in (0.25, 0.50, 0.70):
|
| 287 |
+
for quadrant in ((1.0, 1.0), (1.0, -1.0), (-1.0, -1.0), (-1.0, 1.0)):
|
| 288 |
+
north_sign, east_sign = quadrant
|
| 289 |
+
quadrant_mask = core & (center_delta_lat * north_sign >= 0.0) & (center_delta_lon * east_sign >= 0.0)
|
| 290 |
+
threshold = peak_anomaly * fraction
|
| 291 |
+
valid = quadrant_mask & (anomaly >= threshold) if peak_anomaly > 0.0 else np.zeros_like(core)
|
| 292 |
+
if valid.any():
|
| 293 |
+
radii.append(float(np.nanmax(center_distance_deg[valid]) * 111.2))
|
| 294 |
+
else:
|
| 295 |
+
radii.append(float("nan"))
|
| 296 |
+
wind850 = float(np.hypot(fields[1, row, column], fields[2, row, column]) * 1.94384) if fields.shape[0] >= 3 else float("nan")
|
| 297 |
+
center_offset_km = float(distance_deg[row, column] * 111.2)
|
| 298 |
+
center_trusted = bool(center_offset_km <= 333.6 and (environment - center_pressure) >= 4.0)
|
| 299 |
+
offset_degrees = center_offset_km / 111.2
|
| 300 |
+
center_confidence = 1.0 if center_trusted else float(np.clip(1.0 - 0.75 * (offset_degrees - 3.0) / 3.0, 0.25, 0.75))
|
| 301 |
+
return {
|
| 302 |
+
"map_min_pressure_hpa": round(center_pressure, 2),
|
| 303 |
+
"map_query_pressure_hpa": round(query_pressure, 2) if np.isfinite(query_pressure) else None,
|
| 304 |
+
"map_environment_pressure_hpa": round(environment, 2),
|
| 305 |
+
"map_pressure_deficit_hpa": round(max(0.0, environment - center_pressure), 2),
|
| 306 |
+
"map_center_latitude": round(center_latitude, 3),
|
| 307 |
+
"map_center_longitude": round(center_longitude % 360.0, 3),
|
| 308 |
+
"map_center_offset_km": round(center_offset_km, 2),
|
| 309 |
+
"map_center_trusted": center_trusted,
|
| 310 |
+
"map_center_confidence": round(center_confidence, 3),
|
| 311 |
+
"map_wind850_kt": round(wind850, 2) if np.isfinite(wind850) else None,
|
| 312 |
+
"map_query_wind850_kt": round(query_wind850, 2) if np.isfinite(query_wind850) else None,
|
| 313 |
+
"map_pressure_radii_km": [round(value, 2) if np.isfinite(value) else None for value in radii],
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
def couple_forecast_to_pressure_map(
|
| 318 |
+
structure_rows: list[dict],
|
| 319 |
+
pressure_states: np.ndarray,
|
| 320 |
+
field_states: np.ndarray,
|
| 321 |
+
latitude: np.ndarray,
|
| 322 |
+
longitude: np.ndarray,
|
| 323 |
+
base_latitude: float,
|
| 324 |
+
base_longitude: float,
|
| 325 |
+
forecast_points: list[dict],
|
| 326 |
+
current_wind: float,
|
| 327 |
+
current_pressure: float,
|
| 328 |
+
) -> tuple[list[dict], dict]:
|
| 329 |
+
"""Use the causal pressure-map trajectory to correct structure forecasts.
|
| 330 |
+
|
| 331 |
+
The correction is anchored to the observed current wind and pressure, so
|
| 332 |
+
coarse reanalysis cannot replace a known storm intensity. Future changes
|
| 333 |
+
in the map minimum drive bounded pressure/wind changes, and changes in the
|
| 334 |
+
map's quadrant anomaly extent rescale the learned radii. This is the same
|
| 335 |
+
forecast-state family used by the track route, not a future official field.
|
| 336 |
+
"""
|
| 337 |
+
|
| 338 |
+
pressure_states = np.asarray(pressure_states, dtype="float32")
|
| 339 |
+
field_states = np.asarray(field_states, dtype="float32")
|
| 340 |
+
if pressure_states.ndim != 3 or field_states.ndim != 4 or field_states.shape[1] < 3:
|
| 341 |
+
raise ValueError(f"invalid map state shapes: {pressure_states.shape}, {field_states.shape}")
|
| 342 |
+
if len(structure_rows) != len(forecast_points) or len(pressure_states) < len(structure_rows) + 1:
|
| 343 |
+
raise ValueError("map states, route points, and structure rows have incompatible lengths")
|
| 344 |
+
if not np.isfinite(current_wind) or not np.isfinite(current_pressure):
|
| 345 |
+
return structure_rows, {
|
| 346 |
+
"enabled": False,
|
| 347 |
+
"reason": "current observed wind and pressure are unavailable",
|
| 348 |
+
"official_forecasts_used": False,
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
features = [_map_grid_feature(
|
| 352 |
+
pressure_states[index],
|
| 353 |
+
field_states[index],
|
| 354 |
+
latitude,
|
| 355 |
+
longitude,
|
| 356 |
+
base_latitude if index == 0 else float(forecast_points[index - 1]["lat"]),
|
| 357 |
+
base_longitude if index == 0 else float(forecast_points[index - 1]["lon"]),
|
| 358 |
+
) for index in range(len(structure_rows) + 1)]
|
| 359 |
+
# Use a trusted nearby minimum when available. Otherwise sample the map
|
| 360 |
+
# at the route point; this keeps another low/typhoon from hijacking the
|
| 361 |
+
# structure forecast merely because it is the strongest minimum nearby.
|
| 362 |
+
effective_minimum = np.asarray([
|
| 363 |
+
float(item["map_query_pressure_hpa"])
|
| 364 |
+
+ float(item["map_center_confidence"]) * (
|
| 365 |
+
float(item["map_min_pressure_hpa"]) - float(item["map_query_pressure_hpa"])
|
| 366 |
+
)
|
| 367 |
+
for item in features
|
| 368 |
+
], dtype="float32")
|
| 369 |
+
effective_deficit = np.asarray([
|
| 370 |
+
float(item["map_pressure_deficit_hpa"]) * float(item["map_center_confidence"])
|
| 371 |
+
for item in features
|
| 372 |
+
], dtype="float32")
|
| 373 |
+
effective_wind850 = np.asarray([
|
| 374 |
+
float(item["map_query_wind850_kt"])
|
| 375 |
+
+ float(item["map_center_confidence"]) * (
|
| 376 |
+
float(item["map_wind850_kt"]) - float(item["map_query_wind850_kt"])
|
| 377 |
+
)
|
| 378 |
+
for item in features
|
| 379 |
+
], dtype="float32")
|
| 380 |
+
# A short causal smoother prevents a one-cell minimum handoff from making
|
| 381 |
+
# a six-hour intensity jump while retaining the map's lead-time trend.
|
| 382 |
+
smooth_minimum = effective_minimum.copy()
|
| 383 |
+
smooth_deficit = effective_deficit.copy()
|
| 384 |
+
smooth_wind850 = effective_wind850.copy()
|
| 385 |
+
for index in range(1, len(smooth_minimum)):
|
| 386 |
+
smooth_minimum[index] = 0.65 * smooth_minimum[index - 1] + 0.35 * effective_minimum[index]
|
| 387 |
+
smooth_deficit[index] = 0.65 * smooth_deficit[index - 1] + 0.35 * effective_deficit[index]
|
| 388 |
+
if np.isfinite(effective_wind850[index]) and np.isfinite(smooth_wind850[index - 1]):
|
| 389 |
+
smooth_wind850[index] = 0.65 * smooth_wind850[index - 1] + 0.35 * effective_wind850[index]
|
| 390 |
+
if np.isfinite(smooth_minimum[index - 1]) and np.isfinite(smooth_minimum[index]):
|
| 391 |
+
smooth_minimum[index] = min(
|
| 392 |
+
smooth_minimum[index],
|
| 393 |
+
smooth_minimum[index - 1] + MAP_PRESSURE_RECOVERY_LIMIT_HPA,
|
| 394 |
+
)
|
| 395 |
+
reference_radii = np.asarray([
|
| 396 |
+
float(value) if value is not None else np.nan
|
| 397 |
+
for value in features[0]["map_pressure_radii_km"]
|
| 398 |
+
], dtype="float32")
|
| 399 |
+
corrected: list[dict] = []
|
| 400 |
+
map_weight = 0.35
|
| 401 |
+
for index, base in enumerate(structure_rows, start=1):
|
| 402 |
+
row = dict(base)
|
| 403 |
+
# A distant minimum is an environmental candidate, not a reliable
|
| 404 |
+
# storm-center intensity observation. The previous floor kept more
|
| 405 |
+
# than one-fifth of the map correction active even when confidence
|
| 406 |
+
# had fallen to 0.25, which made another synoptic cell rewrite the
|
| 407 |
+
# central pressure and wind. Let confidence directly control the
|
| 408 |
+
# intensity correction; the map remains available for diagnostics and
|
| 409 |
+
# trusted radius adjustments.
|
| 410 |
+
row_weight = map_weight * float(np.clip(features[index]["map_center_confidence"], 0.0, 1.0))
|
| 411 |
+
map_pressure = float(current_pressure + smooth_minimum[index] - smooth_minimum[0])
|
| 412 |
+
pressure_signal = float(np.clip(
|
| 413 |
+
0.75 * (smooth_minimum[0] - smooth_minimum[index])
|
| 414 |
+
+ 0.25 * (smooth_deficit[index] - smooth_deficit[0]),
|
| 415 |
+
-30.0,
|
| 416 |
+
30.0,
|
| 417 |
+
))
|
| 418 |
+
wind_signal = pressure_signal
|
| 419 |
+
if np.isfinite(smooth_wind850[index]) and np.isfinite(smooth_wind850[0]):
|
| 420 |
+
wind_signal += float(np.clip(0.18 * (smooth_wind850[index] - smooth_wind850[0]), -8.0, 8.0))
|
| 421 |
+
map_wind = float(current_wind + 0.70 * wind_signal)
|
| 422 |
+
row["central_pressure_hpa"] = round(float(np.clip(
|
| 423 |
+
(1.0 - row_weight) * float(base["central_pressure_hpa"]) + row_weight * map_pressure,
|
| 424 |
+
850.0,
|
| 425 |
+
1025.0,
|
| 426 |
+
)), 2)
|
| 427 |
+
row["pressure_hpa"] = row["central_pressure_hpa"]
|
| 428 |
+
row["vmax_kt"] = round(float(np.clip(
|
| 429 |
+
(1.0 - row_weight) * float(base["vmax_kt"]) + row_weight * map_wind,
|
| 430 |
+
0.0,
|
| 431 |
+
190.0,
|
| 432 |
+
)), 2)
|
| 433 |
+
row["pressure_spread_hpa"] = round(float(np.hypot(
|
| 434 |
+
float(base.get("pressure_spread_hpa", 0.0)),
|
| 435 |
+
row_weight * abs(map_pressure - float(base["central_pressure_hpa"])),
|
| 436 |
+
)), 2)
|
| 437 |
+
row["vmax_spread_kt"] = round(float(np.hypot(
|
| 438 |
+
float(base.get("vmax_spread_kt", 0.0)),
|
| 439 |
+
row_weight * abs(map_wind - float(base["vmax_kt"])),
|
| 440 |
+
)), 2)
|
| 441 |
+
map_radii = np.asarray([
|
| 442 |
+
float(value) if value is not None else np.nan
|
| 443 |
+
for value in features[index]["map_pressure_radii_km"]
|
| 444 |
+
], dtype="float32")
|
| 445 |
+
base_radii = np.asarray(base["wind_radii_km"], dtype="float32")
|
| 446 |
+
if not features[0]["map_center_trusted"] or not features[index]["map_center_trusted"]:
|
| 447 |
+
map_radii[:] = np.nan
|
| 448 |
+
if np.isfinite(reference_radii).any() and np.isfinite(map_radii).any():
|
| 449 |
+
valid = np.isfinite(reference_radii) & np.isfinite(map_radii) & (reference_radii >= 20.0)
|
| 450 |
+
ratios = np.ones(12, dtype="float32")
|
| 451 |
+
ratios[valid] = np.clip(map_radii[valid] / reference_radii[valid], 0.60, 1.50)
|
| 452 |
+
if valid.any():
|
| 453 |
+
ratios[~valid] = float(np.clip(np.nanmedian(ratios[valid]), 0.60, 1.50))
|
| 454 |
+
radius_factor = 1.0 + 0.25 * (ratios - 1.0)
|
| 455 |
+
adjusted_radii = base_radii * radius_factor
|
| 456 |
+
else:
|
| 457 |
+
adjusted_radii = base_radii
|
| 458 |
+
adjusted_radii = np.clip(adjusted_radii, 0.0, 1000.0)
|
| 459 |
+
for quadrant in range(4):
|
| 460 |
+
adjusted_radii[4 + quadrant] = min(adjusted_radii[4 + quadrant], adjusted_radii[quadrant])
|
| 461 |
+
adjusted_radii[8 + quadrant] = min(adjusted_radii[8 + quadrant], adjusted_radii[4 + quadrant])
|
| 462 |
+
row["wind_radii_km"] = [round(float(value), 2) for value in adjusted_radii]
|
| 463 |
+
map_rmw_ratio = 1.0
|
| 464 |
+
if features[0]["map_center_trusted"] and features[index]["map_center_trusted"]:
|
| 465 |
+
ref_r64 = float(np.nanmedian(reference_radii[8:])) if np.isfinite(reference_radii[8:]).any() else np.nan
|
| 466 |
+
future_r64 = float(np.nanmedian(map_radii[8:])) if np.isfinite(map_radii[8:]).any() else np.nan
|
| 467 |
+
if np.isfinite(ref_r64) and np.isfinite(future_r64) and ref_r64 >= 20.0:
|
| 468 |
+
map_rmw_ratio = float(np.clip(future_r64 / ref_r64, 0.75, 1.25))
|
| 469 |
+
row["rmw_km"] = round(float(np.clip(
|
| 470 |
+
float(base["rmw_km"]) * (1.0 + 0.15 * (map_rmw_ratio - 1.0)),
|
| 471 |
+
0.0,
|
| 472 |
+
300.0,
|
| 473 |
+
)), 2)
|
| 474 |
+
row["pressure_map_features"] = features[index]
|
| 475 |
+
corrected.append(row)
|
| 476 |
+
metadata = {
|
| 477 |
+
"enabled": True,
|
| 478 |
+
"method": "causal forecast pressure-map query plus confidence-weighted local-minimum/anomaly-radius coupling",
|
| 479 |
+
"map_weight": map_weight,
|
| 480 |
+
"minimum_confidence": "local minimum contribution decays directly with route-to-minimum confidence; untrusted minima have near-zero central-intensity weight",
|
| 481 |
+
"pressure_recovery_limit_hpa_per_6h": MAP_PRESSURE_RECOVERY_LIMIT_HPA,
|
| 482 |
+
"pressure_recovery_limit_source": "training-split 90th percentile of positive six-hour pressure changes",
|
| 483 |
+
"pressure_signal_weights": {
|
| 484 |
+
"tracked_map_minimum": 0.75,
|
| 485 |
+
"map_anomaly_extent": 0.25,
|
| 486 |
+
},
|
| 487 |
+
"wind_pressure_anchor": "observed current wind/pressure; map drives only future changes",
|
| 488 |
+
"radius_method": "learned radius baseline rescaled by trusted local quadrant pressure-anomaly extent; max four-degree footprint",
|
| 489 |
+
"map_features": features,
|
| 490 |
+
"official_forecasts_used": False,
|
| 491 |
+
"positive_lead_weather_product_used": False,
|
| 492 |
+
}
|
| 493 |
+
return corrected, metadata
|
| 494 |
+
|
| 495 |
+
|
| 496 |
+
class Trackformer11IntensityEnsemble:
|
| 497 |
+
"""Load frozen Trackformer1.1 experts and emit calibrated structure rows."""
|
| 498 |
+
|
| 499 |
+
def __init__(
|
| 500 |
+
self,
|
| 501 |
+
checkpoint_root: Path,
|
| 502 |
+
calibration_path: Path | None = None,
|
| 503 |
+
device: str | None = None,
|
| 504 |
+
):
|
| 505 |
+
self.checkpoint_root = Path(checkpoint_root)
|
| 506 |
+
self.calibration_path = Path(calibration_path) if calibration_path else None
|
| 507 |
+
self.device = _device(device)
|
| 508 |
+
paths = sorted(self.checkpoint_root.glob("trackformer_1_1_intensity_seed*.pt"))
|
| 509 |
+
if len(paths) < 3:
|
| 510 |
+
raise FileNotFoundError(
|
| 511 |
+
f"expected three Trackformer1.1 intensity checkpoints in {self.checkpoint_root}; found {len(paths)}"
|
| 512 |
+
)
|
| 513 |
+
self.models: list[StructureSpatialExpert] = []
|
| 514 |
+
for path in paths[:3]:
|
| 515 |
+
payload = torch.load(path, map_location="cpu", weights_only=False)
|
| 516 |
+
config = payload["config"]
|
| 517 |
+
model = StructureSpatialExpert(
|
| 518 |
+
config["width"],
|
| 519 |
+
config["layers"],
|
| 520 |
+
config["heads"],
|
| 521 |
+
structure_residual=bool(config.get("structure_residual", False)),
|
| 522 |
+
)
|
| 523 |
+
model.load_state_dict(payload["model"])
|
| 524 |
+
self.models.append(model.to(self.device).eval())
|
| 525 |
+
self.calibration = {}
|
| 526 |
+
if self.calibration_path and self.calibration_path.exists():
|
| 527 |
+
self.calibration = json.loads(self.calibration_path.read_text(encoding="utf-8"))
|
| 528 |
+
self.structure_models: list[StructureSpatialExpert] = []
|
| 529 |
+
structure_root = self.calibration.get("structure_checkpoint_root")
|
| 530 |
+
if structure_root:
|
| 531 |
+
structure_root_path = self._resolve_root(structure_root)
|
| 532 |
+
structure_paths = sorted(structure_root_path.glob("trackformer_1_1_structure_seed*.pt"))
|
| 533 |
+
for path in structure_paths[:3]:
|
| 534 |
+
payload = torch.load(path, map_location="cpu", weights_only=False)
|
| 535 |
+
config = payload["config"]
|
| 536 |
+
model = StructureSpatialExpert(
|
| 537 |
+
config["width"], config["layers"], config["heads"],
|
| 538 |
+
structure_residual=bool(config.get("structure_residual", False)),
|
| 539 |
+
)
|
| 540 |
+
model.load_state_dict(payload["model"])
|
| 541 |
+
self.structure_models.append(model.to(self.device).eval())
|
| 542 |
+
self.temporal_models = []
|
| 543 |
+
temporal_root = self.calibration.get("temporal_checkpoint_root")
|
| 544 |
+
self.temporal_calibration = self.calibration
|
| 545 |
+
if temporal_root:
|
| 546 |
+
from trackformer_1_1_temporal import TemporalStructureSpatial
|
| 547 |
+
|
| 548 |
+
temporal_root_path = self._resolve_root(temporal_root)
|
| 549 |
+
temporal_paths = sorted(temporal_root_path.glob("trackformer_1_1_temporal_seed*.pt"))
|
| 550 |
+
for path in temporal_paths[:3]:
|
| 551 |
+
payload = torch.load(path, map_location="cpu", weights_only=False)
|
| 552 |
+
config = payload["config"]
|
| 553 |
+
model = TemporalStructureSpatial(
|
| 554 |
+
config["width"], config["layers"], config["heads"]
|
| 555 |
+
)
|
| 556 |
+
model.load_state_dict(payload["model"])
|
| 557 |
+
self.temporal_models.append(model.to(self.device).eval())
|
| 558 |
+
|
| 559 |
+
@staticmethod
|
| 560 |
+
def _resolve_root(value: str | Path) -> Path:
|
| 561 |
+
path = Path(value)
|
| 562 |
+
return path if path.is_absolute() else Path(__file__).resolve().parent / path
|
| 563 |
+
|
| 564 |
+
@torch.no_grad()
|
| 565 |
+
def predict(
|
| 566 |
+
self,
|
| 567 |
+
track: np.ndarray,
|
| 568 |
+
field: np.ndarray,
|
| 569 |
+
current_wind: float,
|
| 570 |
+
current_pressure: float,
|
| 571 |
+
previous_wind: float,
|
| 572 |
+
previous_pressure: float,
|
| 573 |
+
current_structure: np.ndarray | None = None,
|
| 574 |
+
history_field: np.ndarray | None = None,
|
| 575 |
+
history_available: np.ndarray | None = None,
|
| 576 |
+
) -> tuple[list[dict], dict]:
|
| 577 |
+
track = np.asarray(track, dtype="float32")
|
| 578 |
+
field = np.asarray(field, dtype="float32")
|
| 579 |
+
if track.shape != (9, 54):
|
| 580 |
+
raise ValueError(f"Trackformer1.1 track must have shape (9, 54), got {track.shape}")
|
| 581 |
+
if field.shape != (4, 17, 17):
|
| 582 |
+
raise ValueError(f"Trackformer1.1 field must have shape (4, 17, 17), got {field.shape}")
|
| 583 |
+
if history_field is not None:
|
| 584 |
+
history_field = np.asarray(history_field, dtype="float32")
|
| 585 |
+
if history_field.shape != (8, 17, 17):
|
| 586 |
+
raise ValueError(
|
| 587 |
+
f"Trackformer1.1 history field must have shape (8, 17, 17), got {history_field.shape}"
|
| 588 |
+
)
|
| 589 |
+
if history_available is None:
|
| 590 |
+
history_available = np.ones(2, dtype="float32")
|
| 591 |
+
history_available = np.asarray(history_available, dtype="float32").reshape(-1)
|
| 592 |
+
if history_available.shape != (2,):
|
| 593 |
+
raise ValueError("Trackformer1.1 history availability must have shape (2,)")
|
| 594 |
+
if not np.isfinite(track).all() or not np.isfinite(field).all():
|
| 595 |
+
raise ValueError("Trackformer1.1 inputs contain non-finite values")
|
| 596 |
+
track_clip = self.calibration.get("track_input_clip", {})
|
| 597 |
+
lower = np.asarray(track_clip.get("lower", []), dtype="float32")
|
| 598 |
+
upper = np.asarray(track_clip.get("upper", []), dtype="float32")
|
| 599 |
+
if lower.shape == (track.shape[1],) and upper.shape == (track.shape[1],):
|
| 600 |
+
track = np.clip(track, lower[None, :], upper[None, :])
|
| 601 |
+
if current_structure is not None:
|
| 602 |
+
current_structure = np.asarray(current_structure, dtype="float32").reshape(-1)
|
| 603 |
+
if current_structure.shape != (13,):
|
| 604 |
+
raise ValueError(
|
| 605 |
+
"Trackformer1.1 current structure must contain RMW plus twelve radii in native nautical miles"
|
| 606 |
+
)
|
| 607 |
+
structure_available = (
|
| 608 |
+
np.isfinite(current_structure).astype("float32")
|
| 609 |
+
if current_structure is not None
|
| 610 |
+
else None
|
| 611 |
+
)
|
| 612 |
+
current_available = float(np.isfinite(current_wind) and np.isfinite(current_pressure))
|
| 613 |
+
current_values = np.nan_to_num(
|
| 614 |
+
np.asarray([current_wind, current_pressure], dtype="float32") / TARGET_SCALE[2:4],
|
| 615 |
+
nan=0.0,
|
| 616 |
+
)
|
| 617 |
+
available = np.asarray([current_available, current_available], dtype="float32")
|
| 618 |
+
track_tensor = torch.from_numpy(track[None]).to(self.device)
|
| 619 |
+
field_tensor = torch.from_numpy(field[None]).to(self.device)
|
| 620 |
+
current_tensor = torch.from_numpy(current_values[None]).to(self.device)
|
| 621 |
+
available_tensor = torch.from_numpy(available[None]).to(self.device)
|
| 622 |
+
structure_tensor = None
|
| 623 |
+
structure_available_tensor = None
|
| 624 |
+
if current_structure is not None:
|
| 625 |
+
structure_tensor = torch.from_numpy(
|
| 626 |
+
np.nan_to_num(
|
| 627 |
+
current_structure / TARGET_SCALE[4:],
|
| 628 |
+
nan=0.0,
|
| 629 |
+
posinf=0.0,
|
| 630 |
+
neginf=0.0,
|
| 631 |
+
)[None]
|
| 632 |
+
).to(self.device)
|
| 633 |
+
structure_available_tensor = torch.from_numpy(structure_available[None]).to(self.device)
|
| 634 |
+
states = np.stack([
|
| 635 |
+
model(
|
| 636 |
+
track_tensor,
|
| 637 |
+
field_tensor,
|
| 638 |
+
current_tensor,
|
| 639 |
+
available_tensor,
|
| 640 |
+
structure_tensor,
|
| 641 |
+
structure_available_tensor,
|
| 642 |
+
)[0][0].detach().cpu().numpy()
|
| 643 |
+
for model in self.models
|
| 644 |
+
]).astype("float32")
|
| 645 |
+
states *= STRUCTURE_SCALE[None, None, :]
|
| 646 |
+
if self.structure_models:
|
| 647 |
+
structure_states = np.stack([
|
| 648 |
+
model(track_tensor, field_tensor, current_tensor, available_tensor)[0][0].detach().cpu().numpy()
|
| 649 |
+
for model in self.structure_models
|
| 650 |
+
]).astype("float32") * STRUCTURE_SCALE[None, None, :]
|
| 651 |
+
expert_alpha = np.asarray(
|
| 652 |
+
self.calibration.get("structure_expert_alpha", []), dtype="float32"
|
| 653 |
+
)
|
| 654 |
+
if expert_alpha.shape != (LEADS, 13):
|
| 655 |
+
expert_alpha = np.zeros((LEADS, 13), dtype="float32")
|
| 656 |
+
for lead in range(LEADS):
|
| 657 |
+
states[:, lead, 2:] = (
|
| 658 |
+
expert_alpha[lead][None, :] * states[:, lead, 2:]
|
| 659 |
+
+ (1.0 - expert_alpha[lead][None, :]) * structure_states[:, lead, 2:]
|
| 660 |
+
)
|
| 661 |
+
temporal_states = None
|
| 662 |
+
if self.temporal_models and history_field is not None and current_structure is not None:
|
| 663 |
+
history_tensor = torch.from_numpy(history_field[None]).to(self.device)
|
| 664 |
+
history_available_tensor = torch.from_numpy(history_available[None]).to(self.device)
|
| 665 |
+
temporal_states = np.stack([
|
| 666 |
+
model(
|
| 667 |
+
track_tensor,
|
| 668 |
+
field_tensor,
|
| 669 |
+
current_tensor,
|
| 670 |
+
available_tensor,
|
| 671 |
+
structure_tensor,
|
| 672 |
+
structure_available_tensor,
|
| 673 |
+
history_tensor,
|
| 674 |
+
history_available_tensor,
|
| 675 |
+
)[0][0].detach().cpu().numpy()
|
| 676 |
+
for model in self.temporal_models
|
| 677 |
+
]).astype("float32") * STRUCTURE_SCALE[None, None, :]
|
| 678 |
+
if current_available:
|
| 679 |
+
calibrated_wind = _calibrated_wind(states, float(current_wind), self.calibration)
|
| 680 |
+
states[:, :, 1] = _calibrated_pressure(
|
| 681 |
+
states,
|
| 682 |
+
float(current_wind),
|
| 683 |
+
float(current_pressure),
|
| 684 |
+
float(previous_wind) if np.isfinite(previous_wind) else float(current_wind),
|
| 685 |
+
float(previous_pressure) if np.isfinite(previous_pressure) else float(current_pressure),
|
| 686 |
+
calibrated_wind,
|
| 687 |
+
self.calibration,
|
| 688 |
+
)
|
| 689 |
+
states[:, :, 0] = calibrated_wind
|
| 690 |
+
if temporal_states is not None:
|
| 691 |
+
temporal_alpha = self.calibration.get("temporal_wind_blend_alpha")
|
| 692 |
+
temporal_min_wind = float(self.calibration.get("temporal_wind_min_current_kt", 0.0))
|
| 693 |
+
if temporal_alpha and float(current_wind) >= temporal_min_wind:
|
| 694 |
+
temporal_calibration = {"wind_blend_alpha": temporal_alpha}
|
| 695 |
+
states[:, :, 0] = _calibrated_wind(
|
| 696 |
+
temporal_states,
|
| 697 |
+
float(current_wind),
|
| 698 |
+
temporal_calibration,
|
| 699 |
+
)
|
| 700 |
+
structure_alpha = np.asarray(
|
| 701 |
+
self.calibration.get("structure_blend_alpha", []), dtype="float32"
|
| 702 |
+
)
|
| 703 |
+
if current_structure is not None and structure_alpha.shape == (LEADS, 13):
|
| 704 |
+
available_structure = np.isfinite(current_structure)
|
| 705 |
+
anchor = np.nan_to_num(current_structure, nan=0.0, posinf=0.0, neginf=0.0)
|
| 706 |
+
for lead in range(LEADS):
|
| 707 |
+
alpha = np.clip(structure_alpha[lead], 0.0, 1.0)
|
| 708 |
+
for offset in np.flatnonzero(available_structure):
|
| 709 |
+
channel = 2 + int(offset)
|
| 710 |
+
states[:, lead, channel] = (
|
| 711 |
+
alpha[int(offset)] * states[:, lead, channel]
|
| 712 |
+
+ (1.0 - alpha[int(offset)]) * anchor[int(offset)]
|
| 713 |
+
)
|
| 714 |
+
states = _sanitize(states)
|
| 715 |
+
mean = states.mean(axis=0)
|
| 716 |
+
spread = states.std(axis=0)
|
| 717 |
+
model_label = (
|
| 718 |
+
"Trackformer1.1 residual-anchor spatial ensemble"
|
| 719 |
+
if self.models and self.models[0].structure_residual
|
| 720 |
+
else "frozen spatial structure ensemble"
|
| 721 |
+
)
|
| 722 |
+
rows = [_row(mean, spread, lead) for lead in range(LEADS)]
|
| 723 |
+
metadata = {
|
| 724 |
+
"model": f"{model_label} used as the Trackformer1.1 intensity head",
|
| 725 |
+
"checkpoint_count": len(self.models),
|
| 726 |
+
"calibration": str(self.calibration_path) if self.calibration_path and self.calibration_path.exists() else None,
|
| 727 |
+
"field_contract": "4x17x17 analysis patch, q/31.75 then clipped to [-4,4]",
|
| 728 |
+
"outputs": ["vmax_kt", "central_pressure_hpa", "rmw_km", "wind_radii_km"],
|
| 729 |
+
"native_structure_unit": "nautical_miles",
|
| 730 |
+
"output_distance_unit": "kilometres",
|
| 731 |
+
"ensemble_spread": "standard deviation across the three frozen spatial experts",
|
| 732 |
+
"structure_calibration": "lead- and component-wise validation blend with observed current RMW/radii when available",
|
| 733 |
+
"structure_expert_blend": bool(self.structure_models),
|
| 734 |
+
"temporal_wind_branch": bool(
|
| 735 |
+
self.temporal_models
|
| 736 |
+
and history_field is not None
|
| 737 |
+
and current_available
|
| 738 |
+
and float(current_wind) >= float(self.calibration.get("temporal_wind_min_current_kt", 0.0))
|
| 739 |
+
),
|
| 740 |
+
"temporal_wind_branch_policy": "same-storm t-12/t-24 analysis patches; wind only; enabled above the validation-selected current-wind gate; base pressure calibration retained",
|
| 741 |
+
"pressure_anchor_alpha": self.calibration.get("pressure_anchor_alpha"),
|
| 742 |
+
"pressure_anchor_policy": self.calibration.get("pressure_anchor_policy"),
|
| 743 |
+
"track_input_policy": self.calibration.get("track_input_clip", {}).get("method"),
|
| 744 |
+
"official_forecasts_used": False,
|
| 745 |
+
"positive_lead_weather_used": False,
|
| 746 |
+
"device": str(self.device),
|
| 747 |
+
}
|
| 748 |
+
return rows, metadata
|
trackformer_1_1_route.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Causal western-Pacific state and steering route.
|
| 3 |
+
|
| 4 |
+
This module first extrapolates a bounded regional atmospheric state from the
|
| 5 |
+
current, t-12, and t-24 analysis fields. It then integrates the track using
|
| 6 |
+
steering views that reach across the western Pacific rather than only a small
|
| 7 |
+
storm-centered ring. The pressure map and low-center diagnostics are derived
|
| 8 |
+
from the same analysis-only state.
|
| 9 |
+
|
| 10 |
+
No positive-lead weather field, official forecast track, or future observation
|
| 11 |
+
is accepted as an input.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
import math
|
| 17 |
+
from typing import Sequence
|
| 18 |
+
|
| 19 |
+
import numpy as np
|
| 20 |
+
|
| 21 |
+
from trackformer_1_1_base_route import (
|
| 22 |
+
CURVATURE_VARIANTS,
|
| 23 |
+
SNAPSHOT_WEIGHTS,
|
| 24 |
+
TENDENCY_SCALES,
|
| 25 |
+
build_route,
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
VERSION = "Trackformer1.1-causal-western-Pacific-state-route"
|
| 30 |
+
CAUSAL_ONLY = True
|
| 31 |
+
PACIFIC_LON_RANGE = (100.0, 190.0)
|
| 32 |
+
PACIFIC_LAT_RANGE = (0.0, 60.0)
|
| 33 |
+
LEAD_HOURS = tuple(range(0, 121, 6))
|
| 34 |
+
|
| 35 |
+
# The outer views make Japan, the East China Sea, Taiwan, the Philippines,
|
| 36 |
+
# the subtropical ridge, and the western Pacific trough visible together.
|
| 37 |
+
PACIFIC_ROUTE_VARIANTS = (
|
| 38 |
+
{"name": "inner_850", "level_weights": (1.0, 0.0, 0.0), "ring_degrees": (3.0, 6.0), "pressure_fraction": 0.05, "weight": 0.10},
|
| 39 |
+
{"name": "deep_inner", "level_weights": (0.269, 0.500, 0.231), "ring_degrees": (4.0, 10.0), "pressure_fraction": 0.10, "weight": 0.14},
|
| 40 |
+
{"name": "broad_850_ridge", "level_weights": (1.0, 0.0, 0.0), "ring_degrees": (8.0, 20.0), "pressure_fraction": 0.15, "weight": 0.18},
|
| 41 |
+
{"name": "pacific_850_environment", "level_weights": (1.0, 0.0, 0.0), "ring_degrees": (12.0, 32.0), "pressure_fraction": 0.18, "weight": 0.18},
|
| 42 |
+
{"name": "pacific_deep_environment", "level_weights": (0.269, 0.500, 0.231), "ring_degrees": (10.0, 30.0), "pressure_fraction": 0.22, "weight": 0.20},
|
| 43 |
+
{"name": "broad_500_trough", "level_weights": (0.0, 1.0, 0.0), "ring_degrees": (12.0, 34.0), "pressure_fraction": 0.16, "weight": 0.11},
|
| 44 |
+
{"name": "outer_200_jet", "level_weights": (0.0, 0.0, 1.0), "ring_degrees": (15.0, 35.0), "pressure_fraction": 0.08, "weight": 0.09},
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def _clip_delta(values: np.ndarray) -> np.ndarray:
|
| 49 |
+
values = np.nan_to_num(np.asarray(values, dtype="float32"), copy=True)
|
| 50 |
+
for channel in range(values.shape[0]):
|
| 51 |
+
scale = float(np.nanpercentile(np.abs(values[channel]), 98.0))
|
| 52 |
+
if scale > 0.0 and math.isfinite(scale):
|
| 53 |
+
values[channel] = np.clip(values[channel], -2.0 * scale, 2.0 * scale)
|
| 54 |
+
return values
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def build_pacific_route(
|
| 58 |
+
fields: np.ndarray,
|
| 59 |
+
pressure: np.ndarray,
|
| 60 |
+
latitude: np.ndarray,
|
| 61 |
+
longitude: np.ndarray,
|
| 62 |
+
base_latitude: float,
|
| 63 |
+
base_longitude: float,
|
| 64 |
+
history_motion_km_per_6h: tuple[float, float] | None = None,
|
| 65 |
+
) -> tuple[np.ndarray, np.ndarray, dict]:
|
| 66 |
+
"""Build a broad-domain causal route from analysis-only inputs."""
|
| 67 |
+
|
| 68 |
+
members, weights, metadata = build_route(
|
| 69 |
+
fields,
|
| 70 |
+
latitude,
|
| 71 |
+
longitude,
|
| 72 |
+
base_latitude,
|
| 73 |
+
base_longitude,
|
| 74 |
+
pressure,
|
| 75 |
+
available=(1.0, 1.0),
|
| 76 |
+
route_variants=PACIFIC_ROUTE_VARIANTS,
|
| 77 |
+
curvature_variants=CURVATURE_VARIANTS,
|
| 78 |
+
snapshot_weights=SNAPSHOT_WEIGHTS,
|
| 79 |
+
tendency_scales=TENDENCY_SCALES,
|
| 80 |
+
history_motion_km_per_6h=history_motion_km_per_6h,
|
| 81 |
+
)
|
| 82 |
+
metadata = {
|
| 83 |
+
**metadata,
|
| 84 |
+
"version": VERSION,
|
| 85 |
+
"domain": {
|
| 86 |
+
"longitude_east": list(PACIFIC_LON_RANGE),
|
| 87 |
+
"latitude_north": list(PACIFIC_LAT_RANGE),
|
| 88 |
+
"steering_ring_max_degrees": 35.0,
|
| 89 |
+
},
|
| 90 |
+
"large_system_policy": "The route samples the complete analysis grid through broad 850/500/200-hPa and SLP-gradient views; nearby lows are represented by the same causal pressure field.",
|
| 91 |
+
}
|
| 92 |
+
return members, weights, metadata
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def forecast_pacific_state(
|
| 96 |
+
fields: np.ndarray,
|
| 97 |
+
pressure: np.ndarray,
|
| 98 |
+
lead_hours: Sequence[int] = LEAD_HOURS,
|
| 99 |
+
) -> tuple[np.ndarray, np.ndarray, dict]:
|
| 100 |
+
"""Return causal whole-domain pressure and multilevel states.
|
| 101 |
+
|
| 102 |
+
The forecast state is a bounded extrapolation of analysis tendency. It is
|
| 103 |
+
deliberately not a claimed NWP forecast: no future weather product is
|
| 104 |
+
read, and no official forecast field is substituted.
|
| 105 |
+
"""
|
| 106 |
+
|
| 107 |
+
fields = np.asarray(fields, dtype="float32")
|
| 108 |
+
pressure = np.asarray(pressure, dtype="float32")
|
| 109 |
+
if fields.shape[0] != 3 or pressure.shape[0] != 3:
|
| 110 |
+
raise ValueError(f"expected three causal snapshots, got {fields.shape} and {pressure.shape}")
|
| 111 |
+
recent_fields = fields[0] - fields[1]
|
| 112 |
+
older_fields = fields[1] - fields[2]
|
| 113 |
+
field_tendency = _clip_delta(0.6 * recent_fields + 0.4 * older_fields)
|
| 114 |
+
recent_pressure = pressure[0] - pressure[1]
|
| 115 |
+
older_pressure = pressure[1] - pressure[2]
|
| 116 |
+
pressure_tendency = _clip_delta(0.6 * recent_pressure[None, ...] + 0.4 * older_pressure[None, ...])[0]
|
| 117 |
+
scale = float(np.dot(np.asarray(TENDENCY_SCALES), np.asarray((0.15, 0.45, 0.40))))
|
| 118 |
+
state_fields = []
|
| 119 |
+
state_pressure = []
|
| 120 |
+
for hours in lead_hours:
|
| 121 |
+
progress = min(2.0, 0.5 * max(float(hours), 0.0) / 12.0)
|
| 122 |
+
state_fields.append(fields[0] + scale * progress * field_tendency)
|
| 123 |
+
state_pressure.append(pressure[0] + scale * progress * pressure_tendency)
|
| 124 |
+
return np.stack(state_fields).astype("float32"), np.stack(state_pressure).astype("float32"), {
|
| 125 |
+
"version": VERSION,
|
| 126 |
+
"causal_only": CAUSAL_ONLY,
|
| 127 |
+
"lead_hours": [int(value) for value in lead_hours],
|
| 128 |
+
"analysis_tendency_scale": scale,
|
| 129 |
+
"tendency_method": "0.6 * (current - t-12) + 0.4 * (t-12 - t-24), clipped per channel at the 98th percentile",
|
| 130 |
+
"input_policy": "current, t-12, and t-24 analysis fields only; no positive-lead or official forecast field",
|
| 131 |
+
"domain": {
|
| 132 |
+
"longitude_east": list(PACIFIC_LON_RANGE),
|
| 133 |
+
"latitude_north": list(PACIFIC_LAT_RANGE),
|
| 134 |
+
},
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def _distance_degrees(lat_a: float, lon_a: float, lat_b: float, lon_b: float) -> float:
|
| 139 |
+
delta_lon = ((lon_a - lon_b + 180.0) % 360.0) - 180.0
|
| 140 |
+
return math.hypot(lat_a - lat_b, delta_lon * math.cos(math.radians(0.5 * (lat_a + lat_b))))
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def detect_pressure_systems(
|
| 144 |
+
pressure: np.ndarray,
|
| 145 |
+
latitude: np.ndarray,
|
| 146 |
+
longitude: np.ndarray,
|
| 147 |
+
storm_latitude: float,
|
| 148 |
+
storm_longitude: float,
|
| 149 |
+
maximum: int = 8,
|
| 150 |
+
) -> list[dict]:
|
| 151 |
+
"""Find candidate closed lows from an analysis-only SLP field.
|
| 152 |
+
|
| 153 |
+
These are weather-field vortices, not labels imported from a typhoon
|
| 154 |
+
warning center. They are used for diagnostics and route context.
|
| 155 |
+
"""
|
| 156 |
+
|
| 157 |
+
try:
|
| 158 |
+
from scipy.ndimage import maximum_filter, minimum_filter
|
| 159 |
+
except ImportError:
|
| 160 |
+
return []
|
| 161 |
+
latitude = np.asarray(latitude, dtype="float32")
|
| 162 |
+
longitude = np.asarray(longitude, dtype="float32")
|
| 163 |
+
pressure = np.asarray(pressure, dtype="float32")
|
| 164 |
+
lat_mask = (latitude >= PACIFIC_LAT_RANGE[0]) & (latitude <= PACIFIC_LAT_RANGE[1])
|
| 165 |
+
lon_mask = (longitude >= PACIFIC_LON_RANGE[0]) & (longitude <= PACIFIC_LON_RANGE[1])
|
| 166 |
+
if not lat_mask.any() or not lon_mask.any():
|
| 167 |
+
return []
|
| 168 |
+
local = pressure[np.ix_(lat_mask, lon_mask)]
|
| 169 |
+
minimum = minimum_filter(local, size=13, mode="nearest")
|
| 170 |
+
surrounding_maximum = maximum_filter(local, size=41, mode="nearest")
|
| 171 |
+
threshold = float(np.nanpercentile(local, 18.0))
|
| 172 |
+
candidates = np.argwhere((local <= minimum + 0.05) & (local <= threshold) & ((surrounding_maximum - local) >= 2.0))
|
| 173 |
+
rows = []
|
| 174 |
+
lat_values = latitude[lat_mask]
|
| 175 |
+
lon_values = longitude[lon_mask]
|
| 176 |
+
for row, column in candidates:
|
| 177 |
+
lat = float(lat_values[row])
|
| 178 |
+
lon = float(lon_values[column])
|
| 179 |
+
if _distance_degrees(lat, lon, storm_latitude, storm_longitude) < 8.0:
|
| 180 |
+
continue
|
| 181 |
+
rows.append({
|
| 182 |
+
"latitude": round(lat, 3),
|
| 183 |
+
"longitude": round(lon, 3),
|
| 184 |
+
"pressure_hpa": round(float(local[row, column]), 2),
|
| 185 |
+
"local_low_prominence_hpa": round(float(surrounding_maximum[row, column] - local[row, column]), 2),
|
| 186 |
+
"kind": "analysis low/vortex candidate",
|
| 187 |
+
})
|
| 188 |
+
rows.sort(key=lambda item: (-item["local_low_prominence_hpa"], item["pressure_hpa"]))
|
| 189 |
+
selected = []
|
| 190 |
+
for row in rows:
|
| 191 |
+
if any(_distance_degrees(row["latitude"], row["longitude"], item["latitude"], item["longitude"]) < 4.0 for item in selected):
|
| 192 |
+
continue
|
| 193 |
+
selected.append(row)
|
| 194 |
+
if len(selected) >= maximum:
|
| 195 |
+
break
|
| 196 |
+
return selected
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
__all__ = [
|
| 200 |
+
"VERSION",
|
| 201 |
+
"CAUSAL_ONLY",
|
| 202 |
+
"PACIFIC_LON_RANGE",
|
| 203 |
+
"PACIFIC_LAT_RANGE",
|
| 204 |
+
"LEAD_HOURS",
|
| 205 |
+
"PACIFIC_ROUTE_VARIANTS",
|
| 206 |
+
"build_pacific_route",
|
| 207 |
+
"forecast_pacific_state",
|
| 208 |
+
"detect_pressure_systems",
|
| 209 |
+
]
|
trackformer_1_1_temporal.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Inference-only temporal branch used by Trackformer1.1.
|
| 2 |
+
|
| 3 |
+
The training implementation is intentionally not part of the public model
|
| 4 |
+
package. This module contains only the architecture needed to load the
|
| 5 |
+
frozen temporal expert checkpoints.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
|
| 13 |
+
from trackformer_1_1_intensity import StructureSpatialExpert
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class TemporalStructureSpatial(StructureSpatialExpert):
|
| 17 |
+
"""Spatial expert augmented with same-storm t-12/t-24 analysis fields."""
|
| 18 |
+
|
| 19 |
+
def __init__(self, width: int, layers: int, heads: int):
|
| 20 |
+
super().__init__(width, layers, heads, structure_residual=True)
|
| 21 |
+
self.history_encoder = nn.Sequential(
|
| 22 |
+
nn.Conv2d(10, 64, 3, padding=1),
|
| 23 |
+
nn.GroupNorm(8, 64),
|
| 24 |
+
nn.SiLU(),
|
| 25 |
+
nn.Conv2d(64, width, 3, stride=2, padding=1),
|
| 26 |
+
nn.GroupNorm(8, width),
|
| 27 |
+
nn.SiLU(),
|
| 28 |
+
)
|
| 29 |
+
self.history_pool = nn.AvgPool2d(kernel_size=2, stride=2)
|
| 30 |
+
self.history_norm = nn.LayerNorm(width)
|
| 31 |
+
self.history_pos = nn.Parameter(torch.randn(1, 16, width) * 0.02)
|
| 32 |
+
self.history_out = nn.Conv2d(width, width, 1)
|
| 33 |
+
nn.init.zeros_(self.history_out.weight)
|
| 34 |
+
nn.init.zeros_(self.history_out.bias)
|
| 35 |
+
|
| 36 |
+
def forward(
|
| 37 |
+
self,
|
| 38 |
+
track: torch.Tensor,
|
| 39 |
+
field: torch.Tensor,
|
| 40 |
+
current: torch.Tensor,
|
| 41 |
+
available: torch.Tensor,
|
| 42 |
+
current_structure: torch.Tensor | None = None,
|
| 43 |
+
structure_available: torch.Tensor | None = None,
|
| 44 |
+
history: torch.Tensor | None = None,
|
| 45 |
+
history_available: torch.Tensor | None = None,
|
| 46 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 47 |
+
track_tokens = self.track_encoder(
|
| 48 |
+
self.track_proj(track[:, :, self._thermo_cols]) + self.track_time
|
| 49 |
+
)
|
| 50 |
+
field_tokens = self.field_pool(self.field_encoder(field)).flatten(2).transpose(1, 2)
|
| 51 |
+
field_tokens = self.field_norm(field_tokens + self.field_pos)
|
| 52 |
+
if history is not None:
|
| 53 |
+
if history_available is None:
|
| 54 |
+
history_available = history.new_ones((history.shape[0], 2))
|
| 55 |
+
flags = history_available.view(-1, 2, 1, 1).expand(-1, 2, 17, 17)
|
| 56 |
+
history_tokens = self.history_pool(
|
| 57 |
+
self.history_encoder(torch.cat([history, flags], dim=1))
|
| 58 |
+
)
|
| 59 |
+
history_tokens = history_tokens + self.history_pos.permute(0, 2, 1).reshape(
|
| 60 |
+
1, history_tokens.shape[1], 4, 4
|
| 61 |
+
)
|
| 62 |
+
history_tokens = self.history_norm(history_tokens.flatten(2).transpose(1, 2))
|
| 63 |
+
history_tokens = history_tokens + self.history_out(
|
| 64 |
+
history_tokens.transpose(1, 2).reshape(
|
| 65 |
+
history_tokens.shape[0], history_tokens.shape[2], 4, 4
|
| 66 |
+
)
|
| 67 |
+
).flatten(2).transpose(1, 2)
|
| 68 |
+
field_tokens = field_tokens + history_tokens
|
| 69 |
+
memory = torch.cat([track_tokens, field_tokens], dim=1)
|
| 70 |
+
query = (self.query + self.lead_time).expand(track.shape[0], -1, -1)
|
| 71 |
+
hidden = self.decoder(query, memory)
|
| 72 |
+
state = self.state(hidden).clone()
|
| 73 |
+
state[:, :, :2] = state[:, :, :2] + (current * available)[:, None, :]
|
| 74 |
+
if current_structure is None or structure_available is None:
|
| 75 |
+
raise ValueError("Trackformer1.1 temporal expert requires current structure tensors")
|
| 76 |
+
state[:, :, 2:] = (
|
| 77 |
+
state[:, :, 2:]
|
| 78 |
+
+ current_structure[:, None, :] * structure_available[:, None, :]
|
| 79 |
+
)
|
| 80 |
+
return state, self.log_scale(hidden)
|
| 81 |
+
|
| 82 |
+
@property
|
| 83 |
+
def _thermo_cols(self):
|
| 84 |
+
return (
|
| 85 |
+
[4, 5, 6, 7]
|
| 86 |
+
+ list(range(8, 20))
|
| 87 |
+
+ list(range(24, 40))
|
| 88 |
+
+ [44, 45, 46, 47, 48, 49, 50, 51, 52, 53]
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
__all__ = ["TemporalStructureSpatial"]
|
trackformer_v23.py
DELETED
|
@@ -1,229 +0,0 @@
|
|
| 1 |
-
"""Standalone TrackFormer v23 architecture — the best model in this project (434.96 km RMS track
|
| 2 |
-
error, 10-seed ensemble, WP+EP 2020+ full-20-lead test set). Chain-of-thought (CoT) steering-flow
|
| 3 |
-
prediction (v21) plus a temporal history of that steering representation (v23's addition).
|
| 4 |
-
|
| 5 |
-
This file has zero notebook/exec tricks: every class below is copied verbatim from the training
|
| 6 |
-
scripts that produced the released checkpoints (colab_train_v17.ipynb for TrackFormerV17, the "Base"
|
| 7 |
-
that v21/v23 build on; colab_v26_train.py for TrackFormerCoT, v21's chain-of-thought forward pass;
|
| 8 |
-
colab_v28_train.py for HistStem/TrackFormerHist, v23's temporal-history addition) -- so this module
|
| 9 |
-
IS the architecture the checkpoints were trained with, not a reimplementation from memory. See
|
| 10 |
-
run_v23.py for how to load a checkpoint and get a forecast, in either IBTrACS-only or full-steering
|
| 11 |
-
mode.
|
| 12 |
-
"""
|
| 13 |
-
import math
|
| 14 |
-
import torch
|
| 15 |
-
import torch.nn as nn
|
| 16 |
-
|
| 17 |
-
# ---- input column layout (54-dim per-6h track/thermo/env feature row) -----------------------
|
| 18 |
-
KIN_COLS = [0, 1, 2, 3, 21, 22, 23, 40, 41, 42, 43]
|
| 19 |
-
THERMO_COLS = [4, 5, 6, 7] + list(range(8, 20)) + list(range(24, 40)) + [44, 45, 46, 47]
|
| 20 |
-
ENV_COLS = [48, 49, 50, 51, 52, 53]
|
| 21 |
-
KIN_DIM, THERMO_DIM, ENV_DIM = len(KIN_COLS), len(THERMO_COLS), len(ENV_COLS)
|
| 22 |
-
|
| 23 |
-
TARGET_SCALE = torch.tensor([100., 100., 35., 20., 50.] + [50.] * 12)
|
| 24 |
-
|
| 25 |
-
# eval-only: this dict form is a leftover of the training scripts' exec-in-a-dict pattern
|
| 26 |
-
# (TrackFormerCoT/TrackFormerHist index into it as G["..."], never as a bare global) -- kept as-is
|
| 27 |
-
# rather than rewritten, since these classes are pasted in verbatim from the scripts that actually
|
| 28 |
-
# produced the checkpoints. STEER_DROP only affects self.training branches, irrelevant at eval.
|
| 29 |
-
G = {"KIN_COLS": KIN_COLS, "THERMO_COLS": THERMO_COLS, "ENV_COLS": ENV_COLS, "STEER_DROP": 0.0}
|
| 30 |
-
STEER_DROP = 0.0 # bare-name fallback referenced by TrackFormerV17.forward (never actually
|
| 31 |
-
# called for v21/v23 -- TrackFormerCoT overrides forward entirely -- kept
|
| 32 |
-
# only so the class body is valid to define)
|
| 33 |
-
USE_FLOW = 1
|
| 34 |
-
USE_HIST = 1
|
| 35 |
-
KM6H = 6 * 3600 / 1000.0
|
| 36 |
-
|
| 37 |
-
_i, _j = torch.meshgrid(torch.arange(17) - 8, torch.arange(17) - 8, indexing="ij")
|
| 38 |
-
_r = torch.hypot(_i.float(), _j.float()) * 2.5
|
| 39 |
-
ANN = ((_r >= 3.0) & (_r <= 8.0)).float() # 3-8 deg annulus mask matching the training target
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
def sinusoidal(n, d):
|
| 43 |
-
p = torch.arange(n).unsqueeze(1).float()
|
| 44 |
-
dv = torch.exp(torch.arange(0, d, 2).float() * (-math.log(10000.0) / d))
|
| 45 |
-
e = torch.zeros(n, d); e[:, 0::2] = torch.sin(p * dv); e[:, 1::2] = torch.cos(p * dv)
|
| 46 |
-
return e
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
def enc(d, h, ffn, dr, depth):
|
| 50 |
-
return nn.TransformerEncoder(nn.TransformerEncoderLayer(d, h, ffn, dr, batch_first=True,
|
| 51 |
-
norm_first=True, activation="gelu"), depth)
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
def dec(d, h, ffn, dr, depth):
|
| 55 |
-
return nn.TransformerDecoder(nn.TransformerDecoderLayer(d, h, ffn, dr, batch_first=True,
|
| 56 |
-
norm_first=True, activation="gelu"), depth)
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
class TrackFormerV17(nn.Module):
|
| 60 |
-
"""Base architecture: track/thermo/env history encoders + steering-CNN + cross-attention
|
| 61 |
-
decoders. v21/v23 build on this but override forward() -- it is never called directly for v23,
|
| 62 |
-
kept here only because TrackFormerCoT inherits __init__ from it."""
|
| 63 |
-
|
| 64 |
-
def __init__(self, d=256, h=8, ffn=1024, dr=0.15, hist=9, leads=20):
|
| 65 |
-
super().__init__()
|
| 66 |
-
self.leads = leads
|
| 67 |
-
self.kin_proj = nn.Linear(KIN_DIM, d); self.thermo_proj = nn.Linear(THERMO_DIM, d)
|
| 68 |
-
self.env_proj = nn.Linear(ENV_DIM, d)
|
| 69 |
-
self.register_buffer("kin_time", sinusoidal(hist, d).unsqueeze(0))
|
| 70 |
-
self.register_buffer("thermo_time", sinusoidal(hist, d).unsqueeze(0))
|
| 71 |
-
self.register_buffer("env_time", sinusoidal(hist, d).unsqueeze(0))
|
| 72 |
-
self.kin_enc = enc(d, h, ffn, dr, 3); self.thermo_enc = enc(d, h, ffn, dr, 3)
|
| 73 |
-
self.env_enc = enc(d, h, ffn, dr, 2)
|
| 74 |
-
self.track_dec = dec(d, h, ffn, dr, 3); self.int_dec = dec(d, h, ffn, dr, 3)
|
| 75 |
-
self.track_q = nn.Parameter(torch.randn(1, leads, d) * 0.02)
|
| 76 |
-
self.int_q = nn.Parameter(torch.randn(1, leads, d) * 0.02)
|
| 77 |
-
self.register_buffer("qpos", sinusoidal(leads, d))
|
| 78 |
-
self.adapter = nn.Sequential(nn.Linear(d, d), nn.GELU(), nn.Linear(d, d))
|
| 79 |
-
nn.init.zeros_(self.adapter[-1].weight); nn.init.zeros_(self.adapter[-1].bias)
|
| 80 |
-
self.alpha = nn.Parameter(torch.zeros(leads)); self.rho = nn.Parameter(torch.ones(leads))
|
| 81 |
-
self.gturn = nn.Parameter(torch.zeros(leads))
|
| 82 |
-
self.steer_cnn = nn.Sequential(
|
| 83 |
-
nn.Conv2d(4, 24, 3, padding=1), nn.GELU(), nn.Dropout2d(0.10),
|
| 84 |
-
nn.Conv2d(24, 48, 3, stride=2, padding=1), nn.GELU(), nn.Dropout2d(0.10),
|
| 85 |
-
nn.Conv2d(48, d, 3, stride=2, padding=1), nn.GELU())
|
| 86 |
-
self.steer_pos = nn.Parameter(torch.zeros(1, 25, d))
|
| 87 |
-
self.track_res = nn.Linear(d, 2)
|
| 88 |
-
nn.init.zeros_(self.track_res.weight); nn.init.zeros_(self.track_res.bias)
|
| 89 |
-
self.int_state = nn.Linear(d, 15); self.int_logscale = nn.Linear(d, 15)
|
| 90 |
-
|
| 91 |
-
def forward(self, track, vpair, slp):
|
| 92 |
-
b = track.shape[0]
|
| 93 |
-
kin = self.kin_enc(self.kin_proj(track[:, :, KIN_COLS]) + self.kin_time)
|
| 94 |
-
thermo = self.thermo_enc(self.thermo_proj(track[:, :, THERMO_COLS]) + self.thermo_time)
|
| 95 |
-
env = self.env_enc(self.env_proj(track[:, :, ENV_COLS]) + self.env_time)
|
| 96 |
-
if self.training and STEER_DROP > 0:
|
| 97 |
-
keep = (torch.rand(b, 1, 1, 1, device=slp.device) >= STEER_DROP).float()
|
| 98 |
-
slp = slp * keep
|
| 99 |
-
st = self.steer_cnn(slp).flatten(2).transpose(1, 2) + self.steer_pos
|
| 100 |
-
tq = (self.track_q + self.qpos.unsqueeze(0)).expand(b, -1, -1)
|
| 101 |
-
h_track = self.track_dec(tq, torch.cat([kin, env, st], dim=1))
|
| 102 |
-
h_track = h_track + self.alpha.view(1, self.leads, 1) * self.adapter(thermo.mean(1).detach()).unsqueeze(1)
|
| 103 |
-
v0, vp = vpair[:, :2], vpair[:, 2:]
|
| 104 |
-
s0 = v0.norm(dim=1, keepdim=True).clamp(min=1e-3)
|
| 105 |
-
phi0 = torch.atan2(v0[:, 1], v0[:, 0])
|
| 106 |
-
dphi = phi0 - torch.atan2(vp[:, 1], vp[:, 0])
|
| 107 |
-
omega = torch.atan2(torch.sin(dphi), torch.cos(dphi))
|
| 108 |
-
phil = phi0.unsqueeze(1) + self.gturn.view(1, self.leads) * omega.unsqueeze(1)
|
| 109 |
-
speed = self.rho.view(1, self.leads) * s0
|
| 110 |
-
base = torch.stack([speed * torch.cos(phil), speed * torch.sin(phil)], dim=-1) / 100.0
|
| 111 |
-
motion = base + self.track_res(h_track)
|
| 112 |
-
iq = (self.int_q + self.qpos.unsqueeze(0)).expand(b, -1, -1)
|
| 113 |
-
h_int = self.int_dec(iq, torch.cat([thermo, env, kin.detach(), st.detach()], dim=1))
|
| 114 |
-
istate = self.int_state(h_int); ilog = self.int_logscale(h_int)
|
| 115 |
-
return torch.cat([motion, istate], -1), torch.cat([torch.zeros_like(motion), ilog], -1)
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
class TrackFormerCoT(TrackFormerV17):
|
| 119 |
-
"""v20's network, with the track derived from a predicted steering flow (v21)."""
|
| 120 |
-
|
| 121 |
-
def __init__(self, **kw):
|
| 122 |
-
super().__init__(**kw)
|
| 123 |
-
d = self.track_q.shape[-1]
|
| 124 |
-
self.flow_delta = nn.Linear(d, 2)
|
| 125 |
-
nn.init.zeros_(self.flow_delta.weight); nn.init.zeros_(self.flow_delta.bias)
|
| 126 |
-
self.A = nn.Parameter(torch.tensor([0.76, 0.91]))
|
| 127 |
-
|
| 128 |
-
def forward(self, track, vpair, slp):
|
| 129 |
-
b = track.shape[0]
|
| 130 |
-
KIN_COLS, THERMO_COLS, ENV_COLS = G["KIN_COLS"], G["THERMO_COLS"], G["ENV_COLS"]
|
| 131 |
-
STEER_DROP = G["STEER_DROP"]
|
| 132 |
-
kin = self.kin_enc(self.kin_proj(track[:, :, KIN_COLS]) + self.kin_time)
|
| 133 |
-
thermo = self.thermo_enc(self.thermo_proj(track[:, :, THERMO_COLS]) + self.thermo_time)
|
| 134 |
-
env = self.env_enc(self.env_proj(track[:, :, ENV_COLS]) + self.env_time)
|
| 135 |
-
if self.training and STEER_DROP > 0:
|
| 136 |
-
keep = (torch.rand(b, 1, 1, 1, device=slp.device) >= STEER_DROP).float()
|
| 137 |
-
slp = slp * keep
|
| 138 |
-
st = self.steer_cnn(slp).flatten(2).transpose(1, 2) + self.steer_pos
|
| 139 |
-
tq = (self.track_q + self.qpos.unsqueeze(0)).expand(b, -1, -1)
|
| 140 |
-
h_track = self.track_dec(tq, torch.cat([kin, env, st], dim=1))
|
| 141 |
-
h_track = h_track + self.alpha.view(1, self.leads, 1) * self.adapter(thermo.mean(1).detach()).unsqueeze(1)
|
| 142 |
-
|
| 143 |
-
w = ANN / ANN.sum()
|
| 144 |
-
sc = torch.as_tensor(DSC, device=slp.device, dtype=slp.dtype)
|
| 145 |
-
flow_now = (slp[:, 2:4] * w).sum((-2, -1)) * sc
|
| 146 |
-
fd = self.flow_delta(h_track)
|
| 147 |
-
flow_pred = flow_now.unsqueeze(1) + fd
|
| 148 |
-
|
| 149 |
-
v0, vp = vpair[:, :2], vpair[:, 2:]
|
| 150 |
-
s0 = v0.norm(dim=1, keepdim=True).clamp(min=1e-3)
|
| 151 |
-
phi0 = torch.atan2(v0[:, 1], v0[:, 0])
|
| 152 |
-
dphi = phi0 - torch.atan2(vp[:, 1], vp[:, 0])
|
| 153 |
-
omega = torch.atan2(torch.sin(dphi), torch.cos(dphi))
|
| 154 |
-
phil = phi0.unsqueeze(1) + self.gturn.view(1, self.leads) * omega.unsqueeze(1)
|
| 155 |
-
speed = self.rho.view(1, self.leads) * s0
|
| 156 |
-
base = torch.stack([speed * torch.cos(phil), speed * torch.sin(phil)], dim=-1) / 100.0
|
| 157 |
-
motion = base + self.track_res(h_track)
|
| 158 |
-
if USE_FLOW:
|
| 159 |
-
motion = motion + (self.A.view(1, 1, 2) * fd) * KM6H / 100.0
|
| 160 |
-
iq = (self.int_q + self.qpos.unsqueeze(0)).expand(b, -1, -1)
|
| 161 |
-
h_int = self.int_dec(iq, torch.cat([thermo, env, kin.detach(), st.detach()], dim=1))
|
| 162 |
-
istate = self.int_state(h_int); ilog = self.int_logscale(h_int)
|
| 163 |
-
return (torch.cat([motion, istate], -1),
|
| 164 |
-
torch.cat([torch.zeros_like(motion), ilog], -1), flow_pred)
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
class HistStem(nn.Module):
|
| 168 |
-
"""v17's steering stem, plus a zero-initialised residual carrying t-12h and t-24h (v23)."""
|
| 169 |
-
|
| 170 |
-
def __init__(self, base, ch):
|
| 171 |
-
super().__init__()
|
| 172 |
-
self.base = base
|
| 173 |
-
self.stem = nn.Sequential(
|
| 174 |
-
nn.Conv2d(10, 24, 3, padding=1), nn.GELU(), nn.Dropout2d(0.10),
|
| 175 |
-
nn.Conv2d(24, 48, 3, stride=2, padding=1), nn.GELU(), nn.Dropout2d(0.10),
|
| 176 |
-
nn.Conv2d(48, ch, 3, stride=2, padding=1), nn.GELU())
|
| 177 |
-
self.out = nn.Conv2d(ch, ch, 1)
|
| 178 |
-
nn.init.zeros_(self.out.weight); nn.init.zeros_(self.out.bias)
|
| 179 |
-
self.ctx = None
|
| 180 |
-
|
| 181 |
-
def forward(self, slp):
|
| 182 |
-
st = self.base(slp)
|
| 183 |
-
if USE_HIST and self.ctx is not None:
|
| 184 |
-
hist, have = self.ctx
|
| 185 |
-
hv = have.view(-1, 2, 1, 1).expand(-1, 2, hist.shape[-2], hist.shape[-1])
|
| 186 |
-
st = st + self.out(self.stem(torch.cat([hist, hv], 1)))
|
| 187 |
-
return st
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
class TrackFormerHist(TrackFormerCoT):
|
| 191 |
-
"""v23: v21 + a temporal history of the steering representation (t-12h, t-24h). This is the
|
| 192 |
-
class the released v23 checkpoints instantiate."""
|
| 193 |
-
|
| 194 |
-
def __init__(self, **kw):
|
| 195 |
-
super().__init__(**kw)
|
| 196 |
-
self.steer_cnn = HistStem(self.steer_cnn, self.steer_pos.shape[-1])
|
| 197 |
-
|
| 198 |
-
def forward(self, tr, vp, slp, hist=None, have=None):
|
| 199 |
-
sd = G["STEER_DROP"]
|
| 200 |
-
drop = self.training and sd > 0 and hist is not None
|
| 201 |
-
if drop:
|
| 202 |
-
keep = (torch.rand(tr.shape[0], 1, 1, 1, device=slp.device) >= sd).float()
|
| 203 |
-
slp = slp * keep
|
| 204 |
-
hist = hist * keep
|
| 205 |
-
have = have * keep.view(-1, 1)
|
| 206 |
-
G["STEER_DROP"] = 0.0
|
| 207 |
-
self.steer_cnn.ctx = (hist, have) if hist is not None else None
|
| 208 |
-
try:
|
| 209 |
-
return super().forward(tr, vp, slp)
|
| 210 |
-
finally:
|
| 211 |
-
self.steer_cnn.ctx = None
|
| 212 |
-
G["STEER_DROP"] = sd
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
# ---- loaded at import time from the small companion norm-stats file --------------------------
|
| 216 |
-
import os as _os
|
| 217 |
-
import numpy as _np
|
| 218 |
-
|
| 219 |
-
_stats = _np.load(_os.path.join(_os.path.dirname(__file__), "v23_norm_stats.npz"))
|
| 220 |
-
TMEAN = _stats["tmean"] # (54,) float32 -- per-column track/thermo/env feature mean
|
| 221 |
-
TSTD = _stats["tstd"] # (54,) float32 -- per-column std
|
| 222 |
-
DSC = _stats["dsc"] # (2,) float32 -- deep-layer-mean steering u/v de-normalization scale
|
| 223 |
-
TARGET_SCALE = torch.from_numpy(_stats["target_scale"]) # (17,) -- motion(2)+intensity(15) scale
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
def build_v23():
|
| 227 |
-
"""Returns an uninitialized TrackFormerHist -- load_state_dict a v23_seed*.pt checkpoint,
|
| 228 |
-
call .eval()."""
|
| 229 |
-
return TrackFormerHist()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
v23_seed0.pt
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:7d849efd9c16f14f1a13f4eea9ba2395d5f3d880f30a77a3e6a12b512a35f4d4
|
| 3 |
-
size 52625227
|
|
|
|
|
|
|
|
|
|
|
|
v23_seed3.pt
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:bf109a94ca734e18b442ce34ea4858921607d768e63f0b7eaa6508163c38fdd5
|
| 3 |
-
size 52625227
|
|
|
|
|
|
|
|
|
|
|
|
v23_seed4.pt
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:e170d8368e8f7e712015efc728b42f2202bd5d902aa3397acd9bc0f66c8dcb5f
|
| 3 |
-
size 52625227
|
|
|
|
|
|
|
|
|
|
|
|
v23_seed5.pt
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:3f834873a59350285b19ca197a2cbcea02db1e3f851fe614b9d0fcd0f5bb9018
|
| 3 |
-
size 52625227
|
|
|
|
|
|
|
|
|
|
|
|
v23_seed6.pt
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:3beecc4f8ecd08e3973c5ac2ca5019982b7fc3a99f877acb4c5be6a8d93fc7b8
|
| 3 |
-
size 52625227
|
|
|
|
|
|
|
|
|
|
|
|
v23_seed7.pt
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:cb4fca855e66ec026d3281d1c445d4570348eb18d52a51535383226294fecfc0
|
| 3 |
-
size 52625227
|
|
|
|
|
|
|
|
|
|
|
|
v23_seed8.pt
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:d7b25707064415dde3286aee34a87d3930374009a2da076b2d9ad914d4c0e08e
|
| 3 |
-
size 52625227
|
|
|
|
|
|
|
|
|
|
|
|
v23_seed9.pt
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:90ed26ece67621226779dfc606b9ddfb602200637d292142900f8ab1fa0ee140
|
| 3 |
-
size 52625227
|
|
|
|
|
|
|
|
|
|
|
|