PhiltheBuilder commited on
Commit
b2db41b
·
verified ·
1 Parent(s): 8c25920

Persistence reference forecast

Browse files
Files changed (3) hide show
  1. README.md +51 -0
  2. earthboi.yaml +22 -0
  3. predict.py +84 -0
README.md ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ tags:
4
+ - weather
5
+ - forecasting
6
+ - baseline
7
+ ---
8
+
9
+ # Persistence baseline
10
+
11
+ A reference forecast for 2 metre temperature: the analysis at T+0, carried
12
+ forward unchanged to every lead hour.
13
+
14
+ Persistence is the standard trivial reference in weather forecasting. It has no
15
+ parameters and no training data. Its purpose is comparison — a forecast system
16
+ is only demonstrating skill once it beats persistence, and the margin by which
17
+ it does so at each lead time is a more meaningful number than an error score
18
+ quoted on its own.
19
+
20
+ ## Behaviour
21
+
22
+ Skill decays in a predictable way, which is what makes it useful as a control:
23
+
24
+ | Lead | Typical behaviour |
25
+ |---|---|
26
+ | +6 h | Hard to beat by much — the atmosphere is strongly autocorrelated at this range |
27
+ | +24 h | Clearly beaten by any competent forecast |
28
+ | +120 h | Error approaches that of climatology |
29
+
30
+ Error that does **not** grow with lead time indicates a problem in the
31
+ evaluation, not in the forecast.
32
+
33
+ ## Inputs
34
+
35
+ Reads `2m_temperature` from a gridded analysis in netCDF, with dimensions
36
+ `(time, latitude, longitude)`, latitude descending from +90 to −90, and
37
+ longitude ascending on a 0–360 convention. It uses the most recent timestep.
38
+
39
+ ## Outputs
40
+
41
+ `temperature_2m` with dimensions `(lead_hour, latitude, longitude)` on the input
42
+ grid, in Kelvin (the unit of the source field).
43
+
44
+ ## Files
45
+
46
+ - `predict.py` — the model
47
+ - `earthboi.yaml` — declared variables, lead hours and grid
48
+
49
+ ## Licence
50
+
51
+ MIT.
earthboi.yaml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # What this model forecasts. The platform verifies every field against what the
2
+ # model actually writes, and displays this on the leaderboard beside the score.
3
+ entrypoint: predict.py
4
+
5
+ variables:
6
+ - temperature_2m
7
+
8
+ # 6-hourly to +168h (7 days).
9
+ #
10
+ # Matched to AIFS's own 6-hourly instantaneous steps so the two are scored at
11
+ # the same instants against the same observations. A model may declare any lead
12
+ # set it likes -- the platform pools whatever exists rather than forcing a
13
+ # package -- but a baseline is only useful if it lines up with what it is a
14
+ # baseline FOR.
15
+ lead_hours: [6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120, 126, 132, 138, 144, 150, 156, 162, 168]
16
+
17
+ grid:
18
+ resolution: 0.25
19
+ layout: global
20
+
21
+ display_name: Persistence baseline
22
+ license: mit
predict.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Persistence: tomorrow is the same as today.
2
+
3
+ The simplest forecast there is. It copies the T+0 initial condition forward,
4
+ unchanged, to every declared lead hour.
5
+
6
+ This is not a joke entry. Persistence is the standard reference forecast in
7
+ meteorology, and on this platform it does two jobs:
8
+
9
+ 1. **A skill floor.** Any model worth ranking must beat it beyond the first step
10
+ or two. A submission that cannot is not forecasting, whatever it costs to
11
+ run.
12
+
13
+ 2. **A pipeline oracle.** Its error curve is known in advance -- small at +6h,
14
+ climbing steadily, bad by +120h. That makes it the one model whose score can
15
+ be checked rather than merely believed, which is why it is what the platform
16
+ is proven with. Weather forecasts fail plausibly: a latitude flip, a
17
+ Kelvin/Celsius slip, or an off-by-one in lead hours all produce output that
18
+ still looks like weather. Against persistence those show up immediately:
19
+
20
+ flat error across leads -> lead hours are not being applied
21
+ error around 273 -> a unit conversion was missed
22
+ large error at +6h -> grid sampling is wrong (lat or lon)
23
+ beats AIFS at long leads -> scoring or window alignment is broken
24
+
25
+ Keeping it permanently on the leaderboard means any future regression of that
26
+ kind is visible on the board itself rather than needing a diagnostic run.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import numpy as np
32
+
33
+ # The unit the arrays below are in.
34
+ #
35
+ # The harness reads this to convert once, at the boundary. Persistence returns a
36
+ # slice of the IC untouched, and the IC stores 2m temperature in Kelvin, so that
37
+ # is what this says. Getting it wrong is the "confident RMSE of 273" failure --
38
+ # which is precisely the failure this model exists to make visible, so it would
39
+ # be poor form to commit it here.
40
+ OUTPUT_UNITS = {"temperature_2m": "K"}
41
+
42
+ # Index of T+0 within the IC's time axis.
43
+ #
44
+ # The IC carries two timesteps, T-6h and T+0, in that order (`INPUT_STEP_HOURS =
45
+ # (-6, 0)` in the spec). Persistence forecasts from the most recent one, so it
46
+ # is the last, not the first. Using index 0 would forecast from six hours before
47
+ # the init and quietly lose that much skill at every lead.
48
+ T_ZERO = -1
49
+
50
+
51
+ def run(inputs, declaration, init_time):
52
+ """Copy the analysis forward to every lead hour.
53
+
54
+ Args:
55
+ inputs: IC fields as numpy arrays, keyed by ERA5 name. Surface fields
56
+ are (time, lat, lon).
57
+ declaration: the parsed earthboi.yaml -- lead_hours, grid_shape, etc.
58
+ init_time: the init as an aware datetime (unused; persistence has no
59
+ time dependence beyond the analysis it starts from).
60
+
61
+ Returns:
62
+ {"temperature_2m": (lead, lat, lon) float32}
63
+ """
64
+ analysis = np.asarray(inputs["2m_temperature"])[T_ZERO]
65
+
66
+ rows, cols = declaration["grid_shape"]
67
+ if analysis.shape != (rows, cols):
68
+ # The IC is 0.25 degrees; a 1 degree declaration would need subsampling.
69
+ # Persistence does not implement that -- a wrong-shaped array would be
70
+ # caught by the harness anyway, but failing here names the actual reason.
71
+ raise ValueError(
72
+ f"IC grid {analysis.shape} does not match the declared grid "
73
+ f"{(rows, cols)}. This baseline only runs at the IC's native resolution."
74
+ )
75
+
76
+ leads = len(declaration["lead_hours"])
77
+ # `broadcast_to` returns a read-only view over one grid rather than N copies;
78
+ # `np.array` then makes the one contiguous array the harness writes. At 0.25
79
+ # degrees and 28 leads that is ~116 MB, which is worth not doubling.
80
+ return {
81
+ "temperature_2m": np.array(
82
+ np.broadcast_to(analysis, (leads, rows, cols)), dtype="float32"
83
+ )
84
+ }