diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 6941004dc..7357082c9 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,67 @@ +## 2026-08-12 — E-THE-CONTROL-SCORED-THE-HEADLINE-1 + +**Status:** FINDING `[G]` — `comet_tail_f16.py` / `.json`, bars committed +BEFORE the run (`05f09005`); report §5.12. EXPLORATORY, not an EV. + +**The arc's leading rescue was measured and it failed — and the anti-vacuity +control accidentally calibrated the instrument that had been judging it.** + +CT-F16 swapped ONE variable: the dipole's motion reference, from 6h surface +displacement to the 500/600/700 hPa steering flow, on CT-F14's OWN 19 storms +(paired; stored centres reused, so selection and disk geometry cannot move). +Report §9.2 had named this *"the single most promising fix"*. Measured: +**sign consistency 0.579 against a 0.70 bar** (worse than surface's 0.684), +**residual sd 68.29° → 87.71°, 28.4 % WIDER** where a ≥10 % tightening was +predicted, and a level sweep improving **monotonically toward the SURFACE** — +best at 850 hPa, outside the 400–650 hPa band the height ladder predicted, +converging exactly on the surface-displacement figure. There is no +mid-tropospheric optimum on this sample. + +**The control is the larger finding.** F16c scored two deliberately WRONG +references through the identical pipeline. The **90°-rotated** steering +reference returned **13/19 = 0.684, p=0.0835** — *numerically identical to +CT-F14's headline*, the number this arc has carried as "suggestive" since +§5.11 (a different set of 13 storms, so the count coincides, not the +identity). At n=19 the one-sided ladder is 11→p=0.324, 13→p=0.0835, +**14→p=0.0318**. So **CT-F14 was never one storm short of significance; it was +one storm short of distinguishability from an answer built to be wrong.** + +**Rule: an anti-vacuity control does not only guard the test it is attached to +— it measures the RESOLVING POWER of the instrument.** This one was written to +protect CT-F16 and instead retro-calibrated CT-F14. Attach a +deliberately-wrong reference to any claim whose headline is a rate, and read +the control's score as the floor that headline must clear. Had F16c existed at +§5.11, "0.684, suggestive" would have been reported as "0.684, indistinguishable +from a rotated control". + +**Second finding, independent of the first: the sign test conflates a +SYSTEMATIC ROTATION with a correct prediction.** Stratified by steering +strength — weak flow (<10 m/s, n=6) **sign 0.833 / median |err| 103°**; strong +flow (≥10 m/s, n=13) **sign 0.462 / median |err| 55°**; `corr(speed,|err|) = +−0.407`. The prediction gets **more accurate in magnitude** as steering +strengthens, exactly as the physics expects, while sign consistency moves the +**opposite** way. High sign consistency in the weak subset is errors clustered +near −103°: a systematic rotation, which a sign test reports as success. **A +one-sided sign test on a distribution not centred at zero measures which SIDE +the bias falls on, not whether the prediction holds** — and this arc has used +it as the primary instrument since §4. The apparatus story told since §5.9 +(*slow storms have noisy bearings → filter on displacement*) is not what these +data show: the well-steered storms are the ones whose signs split at chance +while their magnitudes are best. + +**What is NOT falsified.** The height ladder (§5.2/5.8) decomposed the FIELD at +each level about that level's own centre; CT-F16 keeps the SURFACE dipole and +swaps the FLOW reference. Different quantities — the ladder stands as a +measurement (n=2, unreplicated), and what died is the operational reading §9.2 +built on it. The structural claim (§9.1: ring profile, wn-1 dominance, the +12-byte carrier) is untouched; nothing in CT-F16 touches it. + +**Cross-ref:** `E-ZERO-FOR-ELEVEN-THE-AUTHOR-CANNOT-AUDIT-HIS-OWN-FALSIFIERS-1` +(the author is the wrong person to find a spec's vacuous pass routes — here a +control found a *live* one, in a number already published); +`E-THE-HEADLINE-NUMBER-MEASURED-A-MODEL-NOBODY-CLAIMED-1` (the other time this +arc's headline described something other than what was claimed). + ## 2026-08-11 — E-THE-BYTE-WAS-ONLY-THE-SELECTOR-THE-PAIR-IS-THE-CARRIER-1 **Status:** FINDING `[H]` — operator correction + `l4_rail_probe.py` (commit diff --git a/probes/weather-p1/COMET_TAIL_REPORT.md b/probes/weather-p1/COMET_TAIL_REPORT.md index a8a181d93..cf5e3acc2 100644 --- a/probes/weather-p1/COMET_TAIL_REPORT.md +++ b/probes/weather-p1/COMET_TAIL_REPORT.md @@ -770,6 +770,140 @@ this quantity. No stronger attribution is claimed at n = 2. --- +### 5.12 CT-F16 — the steering-level moderator, measured: **it makes the directional claim WORSE** + +`comet_tail_f16.py` / `.json`. Bars pre-registered and **committed before the +run** (`05f09005`). §9.2 named this "the single most promising fix" for the +directional claim. It has now been tested and it **fails on both bars.** + +**Design.** A *paired* re-scoring of CT-F14's OWN 19 qualifying storms with +**only the motion reference changed** — same storms, same stored centres, same +decomposition, byte-identical `err_deg`. Surface 6h displacement → disk-mean +500/600/700 hPa steering flow. Reusing the stored centres removes storm +selection and disk geometry as variables. **This is a mechanistic test, NOT a +verdict** — a fresh steering-scored sample is CT-F17 and is not run. + +| bar | prediction | measured | verdict | +|---|---|---|---| +| **F16a** sign consistency vs steering flow | ≥ 0.70 | **0.579** (11/19), p=0.324 | **FAIL** — and *worse* than surface's 0.684 | +| **F16b** paired residual tightening | sd drops ≥ 10 % | **68.29° → 87.71°, +28.4 % WIDER** | **FAIL** — opposite direction | +| **F16c** anti-vacuity | permuted & rotated both < 0.70 | 0.421 / 0.684 | PASS — but see the caution below | +| **F16d** level sweep (descriptive) | optimum inside 400–650 hPa | **monotone toward the SURFACE; best 850 hPa** | outside the predicted band | + +The level sweep is the clearest signal, and it points the wrong way: + +| level | 400 | 500 | 600 | 700 | 850 | +|---|---|---|---|---|---| +| sign frac | 0.579 | 0.579 | 0.632 | 0.632 | **0.684** | +| sd (deg) | 89.5 | 87.8 | 82.1 | 80.3 | **77.0** | + +Both columns improve **monotonically as the reference level approaches the +surface**, converging at 850 hPa on exactly the surface-displacement figure. +There is no mid-tropospheric optimum. On this sample the surface displacement +was already the best available motion reference, and mid-level flow is a +*worse* one. + +**⚠ Does this falsify the height ladder (§5.2/5.8)? No — and the distinction +matters.** The ladder decomposed the **field at each level about that level's +own centre**; CT-F16 keeps the **surface dipole** and swaps only the **flow +reference**. Those are different quantities, so the ladder is untouched as a +measurement. What CT-F16 falsifies is the **application** §9.2 proposed on top +of it — "score the dipole against steering-level motion". That inference is now +measured and dead. The ladder (n=2) remains an unreplicated observation whose +operational reading has failed its first test. + +**⚠ The anti-vacuity arm accidentally calibrated the noise floor — and CT-F14's +headline sits exactly on it.** The deliberately **90°-rotated** steering +reference scored **13/19 = 0.684, p=0.0835** — numerically identical to +CT-F14's own headline figure (on a *different* set of 13 storms, so the count +coincides rather than the identity). **A reference constructed to be wrong +produces this arc's "suggestive" number on this sample.** F16c's bar was +`< 0.70` and 0.684 clears it, so the gate passes as written — but the margin is +the finding. At n=19 the one-sided ladder is 11/19 → p=0.324, 13/19 → p=0.0835, +**14/19 → p=0.0318**: the test only separates from chance at 14. CT-F14's +0.684 was never one storm short of significance; it was one storm short of +*distinguishability from a deliberately wrong answer*. + +**⚠ And the sign test is not measuring what the arc assumed.** Stratifying by +steering-flow strength inverts the two statistics against each other: + +| subset | n | sign frac | median \|error\| | +|---|---:|---:|---:| +| weak flow (< 10 m/s) | 6 | **0.833** | **103°** | +| strong flow (≥ 10 m/s) | 13 | **0.462** | **55°** | + +`corr(steering speed, |error|) = −0.407` — the prediction gets **more accurate +in magnitude** as the flow strengthens, which is physically sensible. But sign +consistency moves the *opposite* way. High sign consistency in the weak-flow +subset is not the prediction working: those errors cluster near **−103°**, a +systematic rotation, and a sign test on a distribution centred far from zero +reports the *side* of the offset, not its correctness. The apparatus story this +arc has told since §5.9 — *slow storms have noisy bearings, so filter on +displacement* — is not what these data show; the well-steered storms are the +ones whose signs split at chance while their magnitudes are best. + +**Consequence for the arc.** The directional claim does not merely remain +unproven; its **leading mechanistic rescue is now measured and failed**, and +the instrument used to judge it is shown to conflate a systematic rotation with +a correct prediction. §9.2's ranking of the dry moderators is superseded to +that extent: steering level is no longer "the single most promising fix". The +**structural** claim (§9.1) is untouched — nothing here involves the ring +profile, the wn-1 dominance, or the 12-byte carrier. + +### 5.13 The instrument was the collapse: circular resultant vs sign test, same 19 storms + +`comet_tail_resultant_instrument.py` / `.json`. **Post-hoc re-analysis of +stored rows — explicitly NOT a verdict.** Operator framing: *"die irrationale +Aufsummierung hilft, dass der Dipol nicht auf 0.68 kollabiert."* Measured: + +| referent | sign < 0 | R̄ | μ | μ 95 % CI | Rayleigh p | +|---|---:|---:|---:|---:|---:| +| **surface (CT-F14)** | 0.684 | **0.516** | **−30.2°** | ±36.5° | **0.0050** | +| steering (CT-F16) | 0.579 | 0.343 | −40.5° | ±64.3° | 0.107 | +| CONTROL rot+90° | **0.684** | 0.343 | **−130.5°** | ±64.7° | 0.107 | +| CONTROL permuted | 0.421 | 0.142 | — | ±152° | 0.689 | + +(uniform-expectation R̄ at n=19 ≈ 0.203) + +Three things, in order of importance: + +1. **The 0.684 plateau was a property of the STATISTIC, not the data.** The + sign test collapses each storm's error vector to one bit; 19 bits saturate + below the 14/19 distinguishability floor (§5.12). The circular resultant — + the *Aufsummierung*: sum the unit error vectors, read length and direction + — resolves the identical rows at **p = 0.0050**, because concentration (R̄) + and offset (μ) come out as two numbers instead of eating each other. The + systematic ≈−30° offset the arc has chased since §5.1 is now *estimated* + (−30.2° ± 36.5°) instead of *penalizing the score*. +2. **The wrong referent is now visible.** F16c's rotated control scored + 0.684 = indistinguishable under the sign test. Under the resultant it has + the same R̄ (rotation preserves concentration, by construction) but μ + shifted **100.3°** — separated by well over both CIs. The instrument + hierarchy is clean: real referent (0.516) > structured-but-wrong (0.343, + wrong μ) > permuted (0.142, below the uniform floor). +3. **NOT a promotion.** Same sample, post-hoc — the p=0.0050 demonstrates the + instrument, it does not establish the directional claim. CT-W6 is the + pre-registered use of circular statistics on these rows (with the + two-component Faltung decomposition); a fresh-sample verdict is CT-F17. + +**The Faltung reading (operator, same exchange):** the resultant IS the first +circular Fourier coefficient — a Faltung of the empirical error distribution +with `e^{iθ}`. The generalization is the full harmonic/kernel readout (von +Mises smoothing = circular Faltung; n=19 supports the first 2–3 harmonics), +and the W6 decomposition is a DEconvolution: the measured dipole distribution += (referent component mix) ⊛ (apparatus noise, ±3–7° measured in CT-F4). +Components add linearly in the transform domain, which is exactly what makes +the multi-referent separation solvable — and on the palette ring `Z_256` the +circular convolution is native to the substrate (`DistanceLut::circular()`'s +own domain, FFT-able at 256 points). + +**Consequence for every prior sign-consistency number in this document:** +§4's 2/2, §5.9's 6/10, §5.10's 8/10, §5.11's 13/19 were all read through an +instrument that (a) cannot estimate the offset it penalizes and (b) cannot +distinguish a rotated referent at these n. They stand as recorded, but their +evidential weight is bounded by this section, in both directions — the sign +test neither established the claim nor could it have. + ## 6. Product / encoding consequence `[S]` > **⚠ Read with §5.9–5.11 AND the compression correction in §1.** The figures @@ -1119,7 +1253,7 @@ already more explicit structure than a learned model exposes. | moderator | measured evidence | wiring | |---|---|---| -| **Steering level** (baroclinic tilt) | the 92–102° monotone height ladder, zero-crossing 400–650 hPa (§5.2/5.8) | score the dipole against the *steering-level* motion (500–700 hPa flow) instead of the 6h surface displacement — the single most promising fix, **CT-F16** | +| ~~**Steering level** (baroclinic tilt)~~ **— TESTED, FAILED (§5.12)** | the 92–102° monotone height ladder, zero-crossing 400–650 hPa (§5.2/5.8), n=2 | ~~score the dipole against steering-level motion — the single most promising fix, **CT-F16**~~ **RUN: 0.579 vs a 0.70 bar, residual 28.4 % WIDER, level sweep monotone toward the SURFACE (best 850 hPa, outside the predicted band). The ladder as a measurement stands; this operational reading of it is dead.** | | **Displacement magnitude** (label noise) | 6/7 pooled at ≥250 km vs 14/20 unfiltered; CT-F14 0.684 | model the motion-bearing *uncertainty* explicitly instead of a hard cutoff | | **Surface type / friction** | +14° ocean vs +34° land inflow, paired within one disk (§5.7) | a wind-level correction; second-order on the pressure dipole | | **Latitude / f, regime** | the low-wn1 July cases; the 75°N outlier | intake covariates, already computed per storm | diff --git a/probes/weather-p1/comet_tail_f16.json b/probes/weather-p1/comet_tail_f16.json new file mode 100644 index 000000000..a84b42866 --- /dev/null +++ b/probes/weather-p1/comet_tail_f16.json @@ -0,0 +1,417 @@ +{ + "store": "https://storage.googleapis.com/weatherbench2/datasets/era5/1959-2022-6h-1440x721.zarr", + "source": "comet_tail_f14.json qualifying subset", + "steer_levels_hPa": [ + 500, + 600, + 700 + ], + "n": 19, + "rows": [ + { + "date": "1996-03-16T12:00:00", + "t0": 54358, + "center_lat": 37.7606455990929, + "center_lon": 158.35550138007275, + "displacement_km": 394.9627752490917, + "err_surface_deg": -68.29092173098762, + "err_steering_deg": -55.4964456402418, + "steer_speed_ms": 20.893884867023488, + "steer_bearing_rad": -0.013620107639295798, + "low_pole_rad": 0.5885805195793288, + "err_by_level_deg": { + "400": -59.838329834513104, + "500": -57.49978406139752, + "600": -54.938639108651955, + "700": -53.22854650717882, + "850": -57.484084991766565 + } + }, + { + "date": "1997-01-15T12:00:00", + "t0": 55578, + "center_lat": 49.30630498741868, + "center_lon": 329.96462355455805, + "displacement_km": 276.131907815995, + "err_surface_deg": -37.85248283720318, + "err_steering_deg": -41.88760036753982, + "steer_speed_ms": 11.192600318905614, + "steer_bearing_rad": 0.820961163679052, + "low_pole_rad": 1.660680948300792, + "err_by_level_deg": { + "400": -51.67577943655738, + "500": -45.44834257952496, + "600": -41.112301832278064, + "700": -37.77928195663105, + "850": -30.331307443751598 + } + }, + { + "date": "1997-03-17T12:00:00", + "t0": 55822, + "center_lat": 47.12116315432716, + "center_lon": 218.75905030400767, + "displacement_km": 328.84589939421153, + "err_surface_deg": 9.241906494550676, + "err_steering_deg": 19.06883133778618, + "steer_speed_ms": 15.044165553272386, + "steer_bearing_rad": 0.9234407431273538, + "low_pole_rad": 2.827050961274094, + "err_by_level_deg": { + "400": 22.148791083297112, + "500": 20.298922020143806, + "600": 19.17046608541341, + "700": 17.093253022613055, + "850": 10.061778026058306 + } + }, + { + "date": "1999-11-18T12:00:00", + "t0": 59726, + "center_lat": 56.26681556574107, + "center_lon": 178.97969837924182, + "displacement_km": 296.0538640285731, + "err_surface_deg": -32.31823290971391, + "err_steering_deg": -33.380163227723756, + "steer_speed_ms": 9.67421077131886, + "steer_bearing_rad": -0.08184762628111478, + "low_pole_rad": 0.9063549473368648, + "err_by_level_deg": { + "400": -46.26534227865537, + "500": -37.40866178865471, + "600": -32.93849579300499, + "700": -29.360379287813146, + "850": -28.56830161219824 + } + }, + { + "date": "2001-09-19T12:00:00", + "t0": 62410, + "center_lat": 45.102734243947886, + "center_lon": 307.4108806847002, + "displacement_km": 271.6480406924297, + "err_surface_deg": -135.8866370078875, + "err_steering_deg": -124.0206169115745, + "steer_speed_ms": 11.200927732930534, + "steer_bearing_rad": 0.4017789347713794, + "low_pole_rad": 6.091192463284326, + "err_by_level_deg": { + "400": -130.7464895092748, + "500": -126.65603177349436, + "600": -123.87382735163453, + "700": -118.87266160071198, + "850": -113.08014650051786 + } + }, + { + "date": "2002-01-19T12:00:00", + "t0": 62898, + "center_lat": 52.23181291184118, + "center_lon": 328.6795165909927, + "displacement_km": 322.77297720503896, + "err_surface_deg": -9.43809335671719, + "err_steering_deg": -15.690933942651071, + "steer_speed_ms": 17.519419964503772, + "steer_bearing_rad": 0.3769628197504792, + "low_pole_rad": 1.6739006865331794, + "err_by_level_deg": { + "400": -17.81015575023497, + "500": -16.28210608881895, + "600": -15.990736839584656, + "700": -14.559292663684744, + "850": -9.623656106435277 + } + }, + { + "date": "2002-03-21T12:00:00", + "t0": 63142, + "center_lat": 38.751726980533526, + "center_lon": 323.6873562719305, + "displacement_km": 274.6911720596344, + "err_surface_deg": -98.33221272712603, + "err_steering_deg": -74.58270648240982, + "steer_speed_ms": 13.145852615473832, + "steer_bearing_rad": 0.7290192723249141, + "low_pole_rad": 0.9981018059532412, + "err_by_level_deg": { + "400": -76.22661238593426, + "500": -76.85073879438357, + "600": -75.67082544119826, + "700": -68.75360529208191, + "850": -52.26477799004101 + } + }, + { + "date": "2002-07-21T12:00:00", + "t0": 63630, + "center_lat": 28.845082353475743, + "center_lon": 67.81185755361025, + "displacement_km": 457.5499062282751, + "err_surface_deg": 49.13517569045763, + "err_steering_deg": -115.81070042696979, + "steer_speed_ms": 5.595079917438299, + "steer_bearing_rad": -0.8869450228208542, + "low_pole_rad": 4.945758579662202, + "err_by_level_deg": { + "400": -132.17326331006063, + "500": -115.07432656360731, + "600": -107.4572465601405, + "700": -125.95165040211839, + "850": -163.03941412603047 + } + }, + { + "date": "2003-07-22T12:00:00", + "t0": 65094, + "center_lat": 40.20503910539359, + "center_lon": 79.34242857084944, + "displacement_km": 591.8334818738821, + "err_surface_deg": -87.62762374253634, + "err_steering_deg": -89.20263010464555, + "steer_speed_ms": 7.257228643381492, + "steer_bearing_rad": -0.10536566977492949, + "low_pole_rad": 6.191736367434874, + "err_by_level_deg": { + "400": -87.3072468758283, + "500": -89.40694788266802, + "600": -91.43351254962721, + "700": -85.53375362454096, + "850": -65.80408906300465 + } + }, + { + "date": "2004-07-22T12:00:00", + "t0": 66558, + "center_lat": 54.20799369844886, + "center_lon": 130.25559414294094, + "displacement_km": 429.6171401779512, + "err_surface_deg": 126.4843089002764, + "err_steering_deg": 117.50853289638326, + "steer_speed_ms": 10.119190692307956, + "steer_bearing_rad": -0.02762976240892321, + "low_pole_rad": 3.5940773626159297, + "err_by_level_deg": { + "400": 111.82758288577708, + "500": 115.52685640828088, + "600": 117.07038531969937, + "700": 121.64124193134762, + "850": 122.00984677857912 + } + }, + { + "date": "2004-11-21T12:00:00", + "t0": 67046, + "center_lat": 54.75, + "center_lon": 188.15043347333307, + "displacement_km": 306.60785355366534, + "err_surface_deg": -13.583816972829794, + "err_steering_deg": 18.377084048600693, + "steer_speed_ms": 16.251132252644478, + "steer_bearing_rad": 0.9397407008191558, + "low_pole_rad": 2.8312776511778646, + "err_by_level_deg": { + "400": 20.73660070719555, + "500": 18.99933069673773, + "600": 18.21456129585468, + "700": 17.717903526921532, + "850": 5.988347332778545 + } + }, + { + "date": "2005-05-23T12:00:00", + "t0": 67778, + "center_lat": 49.66383829643146, + "center_lon": 338.7627794330812, + "displacement_km": 307.28577630573585, + "err_surface_deg": -0.6118558893321051, + "err_steering_deg": 14.233197222440708, + "steer_speed_ms": 10.90237942206143, + "steer_bearing_rad": 0.24556012017853057, + "low_pole_rad": 2.0647726015907293, + "err_by_level_deg": { + "400": 14.031168703738501, + "500": 13.091975117165447, + "600": 15.948834027319549, + "700": 13.938485092428152, + "850": 6.901574005296283 + } + }, + { + "date": "2006-01-22T12:00:00", + "t0": 68754, + "center_lat": 41.63454690695439, + "center_lon": 158.0240357377511, + "displacement_km": 291.43078891941735, + "err_surface_deg": 46.520425963822305, + "err_steering_deg": 87.97929041259249, + "steer_speed_ms": 18.888531375646444, + "steer_bearing_rad": 0.7827201144252919, + "low_pole_rad": 3.889044732488208, + "err_by_level_deg": { + "400": 93.33934084003317, + "500": 89.4431851363114, + "600": 89.01523573680828, + "700": 83.97358398331141, + "850": 49.431471370709176 + } + }, + { + "date": "2006-03-24T12:00:00", + "t0": 68998, + "center_lat": 40.081768429120515, + "center_lon": 316.97489409322253, + "displacement_km": 282.49422464834026, + "err_surface_deg": -58.900962386334385, + "err_steering_deg": -54.82071331935245, + "steer_speed_ms": 13.663558939602074, + "steer_bearing_rad": 0.09416265739708433, + "low_pole_rad": 0.708157038477371, + "err_by_level_deg": { + "400": -61.540008229027066, + "500": -58.20688097688398, + "600": -54.72627767299666, + "700": -49.70249285208149, + "850": -33.27679812600337 + } + }, + { + "date": "2007-01-23T12:00:00", + "t0": 70218, + "center_lat": 39.33113364502934, + "center_lon": 174.7526838816152, + "displacement_km": 317.67915242399596, + "err_surface_deg": 108.08865494211045, + "err_steering_deg": 110.83868457970243, + "steer_speed_ms": 22.460354498807664, + "steer_bearing_rad": 0.27944717000506025, + "low_pole_rad": 3.784743481295231, + "err_by_level_deg": { + "400": 112.57962589311364, + "500": 113.095942073213, + "600": 111.60153107752132, + "700": 105.90251809828118, + "850": 82.57016302173577 + } + }, + { + "date": "2009-05-26T12:00:00", + "t0": 73634, + "center_lat": 32.081890067957204, + "center_lon": 85.29824266895127, + "displacement_km": 585.0246566127836, + "err_surface_deg": -53.08536899757155, + "err_steering_deg": 177.88228339735582, + "steer_speed_ms": 5.360588233148435, + "steer_bearing_rad": -0.1406915616760268, + "low_pole_rad": 4.5347362913683735, + "err_by_level_deg": { + "400": 146.88706422182213, + "500": 164.80969521013878, + "600": -174.62541647760298, + "700": -172.08372255443373, + "850": -161.97648447470897 + } + }, + { + "date": "2009-07-26T12:00:00", + "t0": 73878, + "center_lat": 34.14437390359832, + "center_lon": 71.81849475304371, + "displacement_km": 363.2025023438297, + "err_surface_deg": -93.58088940292669, + "err_steering_deg": -148.69260686646555, + "steer_speed_ms": 3.4780222783006693, + "steer_bearing_rad": -0.8830706971097777, + "low_pole_rad": 4.375735373671352, + "err_by_level_deg": { + "400": -176.60909933579978, + "500": -158.19430225088678, + "600": -149.47506431516194, + "700": -134.93946522107103, + "850": -152.45008516792728 + } + }, + { + "date": "2009-09-25T12:00:00", + "t0": 74122, + "center_lat": 69.01585282912403, + "center_lon": 353.4343585345505, + "displacement_km": 562.8503596905939, + "err_surface_deg": 0.13717943106883013, + "err_steering_deg": 1.4961231807097874, + "steer_speed_ms": 14.058687512366305, + "steer_bearing_rad": 0.4892440056722573, + "low_pole_rad": 2.0861526079859494, + "err_by_level_deg": { + "400": 3.4097472141104106, + "500": 1.7990182929773084, + "600": 2.0847834302397814, + "700": 0.40412512958917546, + "850": -6.870111260628079 + } + }, + { + "date": "2009-11-25T12:00:00", + "t0": 74366, + "center_lat": 59.52619677699913, + "center_lon": 352.03934730709716, + "displacement_km": 270.81659311746546, + "err_surface_deg": -30.594845686320213, + "err_steering_deg": -41.24289549429341, + "steer_speed_ms": 6.876359597323519, + "steer_bearing_rad": 0.381631449382264, + "low_pole_rad": 1.2326034567458064, + "err_by_level_deg": { + "400": -58.25206976979618, + "500": -49.115244831619066, + "600": -39.77289855449334, + "700": -32.383346410420245, + "850": -22.24999021670004 + } + } + ], + "scope": "PAIRED re-scoring of CT-F14's own storms with ONLY the motion reference changed. MECHANISTIC test, NOT a verdict \u2014 a fresh-sample verdict is CT-F17, not run.", + "verdicts": { + "CT_F16c_antivacuity": { + "permuted_frac": 0.42105263157894735, + "rotated90_frac": 0.6842105263157895, + "discriminates": true + }, + "CT_F16a": { + "surface_frac": 0.6842105263157895, + "steering_frac": 0.5789473684210527, + "one_sided_p": 0.3238029479980469, + "n": 19, + "pass": false + }, + "CT_F16b": { + "sd_surface_deg": 68.28621577501147, + "sd_steering_deg": 87.71263184119131, + "sd_reduction_frac": -0.284485175312478, + "pass": false + }, + "CT_F16d_by_level": { + "400": { + "sign_neg_frac": 0.5789473684210527, + "sd_deg": 89.54592193903673 + }, + "500": { + "sign_neg_frac": 0.5789473684210527, + "sd_deg": 87.8040436831529 + }, + "600": { + "sign_neg_frac": 0.631578947368421, + "sd_deg": 82.06685657920383 + }, + "700": { + "sign_neg_frac": 0.631578947368421, + "sd_deg": 80.25137388442171 + }, + "850": { + "sign_neg_frac": 0.6842105263157895, + "sd_deg": 77.02385632711038 + } + }, + "best_level_hPa": 850 + } +} \ No newline at end of file diff --git a/probes/weather-p1/comet_tail_f16.py b/probes/weather-p1/comet_tail_f16.py new file mode 100644 index 000000000..ffcaee2bc --- /dev/null +++ b/probes/weather-p1/comet_tail_f16.py @@ -0,0 +1,283 @@ +"""EXPLORATORY — CT-F16: score the dipole against the STEERING-LEVEL flow +instead of the 6h surface displacement. NOT an EV; bars mine, unaudited. + +WHY THIS TEST, AND WHAT IT IS NOT. + +CT-F14 -- the properly-powered, pre-registered, displacement-filtered sample -- +scored the dipole's low-pole bearing against the **6h surface-centre +displacement** and returned 13/19 = 0.684, p=0.0835, NO-VERDICT. The report +names ONE moderator that this chain actually MEASURED and never wired: the +steering level. The height ladder (report SS5.2/5.8) runs monotonically 92-102 +deg with a zero-crossing at 400-650 hPa on both storms it was measured on. The +physics SS2 lays out is a vortex embedded in a STEERING flow -- and 10m surface +displacement is a noisy proxy for that flow, not the flow itself. + +So CT-F16 swaps ONLY the motion reference. Same storms, same centres, same +decomposition, same `err_deg` convention -- one variable changed. + +**THIS IS A RE-SCORING, AND IT IS NOT A VERDICT.** The arc's own rule (SS5.10) +is explicit: *"the correct way to chase a post-hoc lead is a FRESH +pre-registered sample with the filter applied a priori -- not a re-scoring of +the sample that already failed."* That rule was written about re-FILTERING +(choosing which storms count after seeing them), and this is a change of +PREDICTOR on a fixed storm set -- a different operation, and the one the report +pre-named. It is still the same 19 storms whose surface-displacement scoring +already came back NO-VERDICT, so: + + * CT-F16 is the MECHANISTIC test -- does the physically-motivated predictor + improve on IDENTICAL data? That question is answerable here and is worth + answering before spending a fresh sample. + * A VERDICT on the directional claim needs a fresh, pre-registered, + steering-scored sample. That is **CT-F17**, named here, NOT run. + +Reusing CT-F14's stored centres also removes centre-finding as a variable: +nothing about the storm selection or the disk geometry can move between arms. + +STEERING FLOW, defined before the run: the disk-mean (u, v) over the SAME +1200 km disk, averaged over the **500/600/700 hPa** layer -- the textbook +extratropical steering layer, and the band containing the ladder's measured +400-650 hPa zero-crossing. Bearing = `arctan2(v_mean, u_mean)`, the identical +CCW-from-east convention `mth = arctan2(disp[1], disp[0])` uses in CT-F14, so +`err_deg` is untouched. + +PRE-REGISTERED (all four stated before any fetch): + +CT-F16a PRIMARY. Sign consistency (fraction with error < 0, the same + left-of-motion direction as every prior probe) scored against the + steering flow, on CT-F14's 19 qualifying storms, >= 0.70. + CT-F14's surface-displacement figure on these SAME storms is 0.684. + +CT-F16b THE PAIRED TEST, and the real content. If the steering level is the + moderator, swapping to it should not merely nudge the sign count -- + it should TIGHTEN the residual. Bar: the standard deviation of the + signed offset must DROP by >= 10 % versus the surface-displacement + scoring on the same storms. **This can fail in both directions**: the + spread can widen (steering flow is the wrong reference) or move < 10 % + (it is not the moderator at this magnitude). + +CT-F16c ANTI-VACUITY / CAN-IT-STAY-SILENT. Two deliberately WRONG references + are scored through the identical pipeline: + (i) the steering bearings DETERMINISTICALLY permuted across storms + (storm i gets storm (i+7) mod 19's steering flow), and + (ii) the true steering flow rotated by +90 deg. + Neither may reach 0.70. If a scrambled or rotated reference scores as + well as the true one, this probe measures nothing and F16a/F16b are + void regardless of what they say. Reported FIRST in the verdicts. + +CT-F16d DESCRIPTIVE, not a bar. Sign consistency scored at EACH level + (400/500/600/700/850 hPa separately). The ladder predicts an optimum + inside 400-650 hPa; a flat sweep, or an optimum at 850/1000, would + say the level structure is not what is driving any improvement. + +NOT tested here: SH, tropical cyclones, or any claim beyond the extratropical +steering-flow framing. A fresh-sample verdict is CT-F17 and is not run. +""" +import json +import pathlib +import urllib.request +from math import comb + +import numcodecs +import numpy as np + +B = ("https://storage.googleapis.com/weatherbench2/datasets/era5/" + "1959-2022-6h-1440x721.zarr") +R_E, R_DISK, RING = 6371.0, 1200.0, 100.0 +STEER_LEVELS = (500, 600, 700) # pre-registered steering layer +SWEEP_LEVELS = (400, 500, 600, 700, 850) + +op = urllib.request.build_opener(urllib.request.ProxyHandler({})) +meta = json.loads(op.open(B + "/.zmetadata", timeout=90).read())["metadata"] + + +def fetch(var, key): + """Fetch and decode one zarr chunk from the WB2 store.""" + za = meta[f"{var}/.zarray"] + raw = op.open(f"{B}/{var}/{key}", timeout=900).read() + dec = numcodecs.get_codec(za["compressor"]).decode(raw) + return np.frombuffer(dec, dtype=np.dtype(za["dtype"])).reshape(za["chunks"]) + + +def wrap_deg(d): + """Wrap degrees into [-180, 180) — the identical convention CT-F14 uses.""" + return (d + 180.0) % 360.0 - 180.0 + + +def err_deg(low_pole_rad, motion_rad): + """Signed alignment error vs the left-of-motion prediction. VERBATIM from + comet_tail_f14.py:205 — the paired comparison is only valid if the scoring + function is byte-identical and ONLY the motion reference changes.""" + return float(wrap_deg(np.rad2deg(low_pole_rad - (motion_rad + np.pi / 2)))) + + +def binom_sf_ge(k, n, p=0.5): + """Exact one-sided binomial tail P(X >= k) for n trials at probability p.""" + return sum(comb(n, i) * (p ** i) * ((1 - p) ** (n - i)) + for i in range(k, n + 1)) + + +lat = fetch("latitude", "0").astype(np.float64).ravel() +levels = fetch("level", "0").astype(int).ravel() +NY, NX = lat.size, 1440 +phi = np.deg2rad(lat) +lon_deg = np.arange(NX) * 0.25 +LEV_IDX = {int(v): i for i, v in enumerate(levels)} +print(f"levels: {list(levels)}") +print(f"steering layer (pre-registered): {STEER_LEVELS} hPa", flush=True) + + +def geom_ll(latc, lonc): + """dx, dy, r (km), azimuth (rad CCW from east) about a continuous centre.""" + phic = np.deg2rad(latc) + dlon = np.deg2rad((lon_deg[None, :] - lonc + 180) % 360 - 180) + dx = R_E * np.cos(phic) * dlon * np.ones((NY, 1)) + dy = R_E * (phi[:, None] - phic) * np.ones((1, NX)) + return dx, dy, np.hypot(dx, dy), np.arctan2(dy, dx) + + +def low_pole_bearing(field, latc, lonc): + """Ring-profile + per-ring wn-1 fit; returns the amplitude-weighted low-pole + bearing. VERBATIM shape from comet_tail_f14.py's decompose_ll.""" + _, _, r, th = geom_ll(latc, lonc) + disk = r <= R_DISK + vals, rr, tt = field[disk], r[disk], th[disk] + nb = int(R_DISK / RING) + rings = np.clip((rr / RING).astype(int), 0, nb - 1) + prof = np.zeros(nb) + a1 = np.zeros(nb) + b1 = np.zeros(nb) + for b in range(nb): + m = rings == b + if not m.any(): + continue + v, t = vals[m], tt[m] + prof[b] = v.mean() + a1[b] = 2 * ((v - prof[b]) * np.cos(t)).mean() + b1[b] = 2 * ((v - prof[b]) * np.sin(t)).mean() + amp = np.hypot(a1, b1) + w = amp * np.arange(nb) + ph = np.arctan2(np.sum(b1 * w), np.sum(a1 * w)) + return float((ph + np.pi) % (2 * np.pi)) + + +def disk_mean_uv(u3, v3, latc, lonc, lev_list): + """Disk-mean (u, v) over the 1200 km disk, averaged across `lev_list`.""" + _, _, r, _ = geom_ll(latc, lonc) + disk = r <= R_DISK + us, vs = [], [] + for lev in lev_list: + li = LEV_IDX[lev] + us.append(u3[li][disk].mean()) + vs.append(v3[li][disk].mean()) + return float(np.mean(us)), float(np.mean(vs)) + + +# ---- CT-F14's OWN qualifying storms (paired: nothing about selection moves) -- +src = json.loads( + pathlib.Path(__file__).with_name("comet_tail_f14.json").read_text()) +storms = [r for r in src["rows"] + if r.get("status") == "OK" and r["displacement_km"] >= 250.0] +assert len(storms) == 19, f"expected CT-F14's 19 qualifying storms, got {len(storms)}" +print(f"paired sample: {len(storms)} storms (CT-F14's qualifying subset)\n", + flush=True) + +rows = [] +for i, s in enumerate(storms): + t0, la, lo = s["t0"], s["center_lat"], s["center_lon"] + p = fetch("mean_sea_level_pressure", f"{t0}.0.0")[0].astype(np.float64) + u3 = fetch("u_component_of_wind", f"{t0}.0.0.0")[0].astype(np.float64) + v3 = fetch("v_component_of_wind", f"{t0}.0.0.0")[0].astype(np.float64) + + lp = low_pole_bearing(p, la, lo) + um, vm = disk_mean_uv(u3, v3, la, lo, STEER_LEVELS) + steer_rad = float(np.arctan2(vm, um)) + e_steer = err_deg(lp, steer_rad) + + per_level = {} + for lev in SWEEP_LEVELS: + ul, vl = disk_mean_uv(u3, v3, la, lo, (lev,)) + per_level[str(lev)] = err_deg(lp, float(np.arctan2(vl, ul))) + + rows.append({ + "date": s["date"], "t0": t0, "center_lat": la, "center_lon": lo, + "displacement_km": s["displacement_km"], + "err_surface_deg": s["error_deg"], # CT-F14's own number + "err_steering_deg": e_steer, + "steer_speed_ms": float(np.hypot(um, vm)), + "steer_bearing_rad": steer_rad, + "low_pole_rad": lp, + "err_by_level_deg": per_level}) + print(f"[{i+1}/{len(storms)}] {s['date'][:10]} " + f"surface {s['error_deg']:+7.1f} steering {e_steer:+7.1f} " + f"|steer| {np.hypot(um, vm):5.1f} m/s", flush=True) + +out = {"store": B, "source": "comet_tail_f14.json qualifying subset", + "steer_levels_hPa": list(STEER_LEVELS), "n": len(rows), "rows": rows, + "scope": ("PAIRED re-scoring of CT-F14's own storms with ONLY the motion " + "reference changed. MECHANISTIC test, NOT a verdict — a " + "fresh-sample verdict is CT-F17, not run.")} + +e_surf = np.array([r["err_surface_deg"] for r in rows]) +e_steer = np.array([r["err_steering_deg"] for r in rows]) +n = len(rows) + + +def frac_neg(e): + """Fraction with error < 0 — the left-of-motion direction.""" + return float((e < 0).mean()) + + +# ---- CT-F16c FIRST: if the wrong references also pass, nothing else counts --- +perm = np.array([rows[(i + 7) % n]["steer_bearing_rad"] for i in range(n)]) +e_perm = np.array([err_deg(rows[i]["low_pole_rad"], perm[i]) for i in range(n)]) +e_rot = np.array([err_deg(r["low_pole_rad"], r["steer_bearing_rad"] + np.pi / 2) + for r in rows]) +f_perm, f_rot = frac_neg(e_perm), frac_neg(e_rot) +c_ok = (f_perm < 0.70) and (f_rot < 0.70) + +print("\n=== CT-F16c ANTI-VACUITY (reported first — gates everything else) ===") +print(f" permuted steering bearings : {f_perm:.3f} (must be < 0.70)") +print(f" steering rotated +90 deg : {f_rot:.3f} (must be < 0.70)") +print(f" -> {'PASS — the test discriminates' if c_ok else 'FAIL — VOID: a wrong reference scores as well'}") + +f_surf, f_steer = frac_neg(e_surf), frac_neg(e_steer) +p_steer = binom_sf_ge(int((e_steer < 0).sum()), n) +sd_surf, sd_steer = float(e_surf.std(ddof=1)), float(e_steer.std(ddof=1)) +drop = (sd_surf - sd_steer) / sd_surf + +print("\n=== CT-F16a PRIMARY — sign consistency vs the steering flow ===") +print(f" surface displacement (CT-F14) : {f_surf:.3f} ({int((e_surf<0).sum())}/{n})") +print(f" steering flow 500/600/700 hPa : {f_steer:.3f} ({int((e_steer<0).sum())}/{n}), " + f"one-sided p={p_steer:.4f}") +print(f" -> {'PASS' if f_steer >= 0.70 else 'FAIL'} (bar >= 0.70)") + +print("\n=== CT-F16b PAIRED — does the residual TIGHTEN? ===") +print(f" sd(signed offset) surface : {sd_surf:7.2f} deg") +print(f" sd(signed offset) steering : {sd_steer:7.2f} deg ({100*drop:+.1f} %)") +print(f" -> {'PASS' if drop >= 0.10 else 'FAIL'} (bar: >= 10 % reduction)") + +print("\n=== CT-F16d DESCRIPTIVE — sign consistency by level ===") +lev_frac = {} +for lev in SWEEP_LEVELS: + e = np.array([r["err_by_level_deg"][str(lev)] for r in rows]) + lev_frac[str(lev)] = {"sign_neg_frac": frac_neg(e), + "sd_deg": float(e.std(ddof=1))} + print(f" {lev:4d} hPa : frac {frac_neg(e):.3f} sd {e.std(ddof=1):6.1f} deg") +best = max(lev_frac, key=lambda k: lev_frac[k]["sign_neg_frac"]) +print(f" best level: {best} hPa " + f"({'inside' if 400 <= int(best) <= 650 else 'OUTSIDE'} the ladder's " + "400-650 hPa zero-crossing band)") + +out["verdicts"] = { + "CT_F16c_antivacuity": {"permuted_frac": f_perm, "rotated90_frac": f_rot, + "discriminates": bool(c_ok)}, + "CT_F16a": {"surface_frac": f_surf, "steering_frac": f_steer, + "one_sided_p": p_steer, "n": n, + "pass": bool(f_steer >= 0.70)}, + "CT_F16b": {"sd_surface_deg": sd_surf, "sd_steering_deg": sd_steer, + "sd_reduction_frac": drop, "pass": bool(drop >= 0.10)}, + "CT_F16d_by_level": lev_frac, "best_level_hPa": int(best)} + +with open(pathlib.Path(__file__).with_name("comet_tail_f16.json"), "w") as fh: + json.dump(out, fh, indent=2) +print("\nwrote comet_tail_f16.json") diff --git a/probes/weather-p1/comet_tail_resultant_instrument.json b/probes/weather-p1/comet_tail_resultant_instrument.json new file mode 100644 index 000000000..4b0bb8ac4 --- /dev/null +++ b/probes/weather-p1/comet_tail_resultant_instrument.json @@ -0,0 +1,38 @@ +{ + "n": 19, + "source": "comet_tail_f16.json rows (paired, stored)", + "scope": "POST-HOC instrument comparison on the same storms whose sign test returned NO-VERDICT. Not a verdict; CT-W6 is the pre-registered use, CT-F17 the fresh-sample one.", + "referents": { + "surface (CT-F14)": { + "sign_neg_frac": 0.6842105263157895, + "R_bar": 0.5164480996096197, + "mu_deg": -30.193114037358924, + "mu_ci95_deg": 36.52016531393819, + "rayleigh_p": 0.004967482150890209 + }, + "steering (CT-F16)": { + "sign_neg_frac": 0.5789473684210527, + "R_bar": 0.342632911535701, + "mu_deg": -40.52194970164434, + "mu_ci95_deg": 64.29776042051351, + "rayleigh_p": 0.10672421673666334 + }, + "CONTROL rot+90": { + "sign_neg_frac": 0.6842105263157895, + "R_bar": 0.34263291153570086, + "mu_deg": -130.52194970164436, + "mu_ci95_deg": 64.69069385224262, + "rayleigh_p": 0.10672421673666359 + }, + "CONTROL permuted": { + "sign_neg_frac": 0.42105263157894735, + "R_bar": 0.14168656351290035, + "mu_deg": -28.449526845634523, + "mu_ci95_deg": 152.15618068129842, + "rayleigh_p": 0.688473350954606 + } + }, + "discrimination": { + "mu_separation_deg": 100.3288356642854 + } +} \ No newline at end of file diff --git a/probes/weather-p1/comet_tail_resultant_instrument.py b/probes/weather-p1/comet_tail_resultant_instrument.py new file mode 100644 index 000000000..d0e986dc2 --- /dev/null +++ b/probes/weather-p1/comet_tail_resultant_instrument.py @@ -0,0 +1,138 @@ +"""EXPLORATORY — the INSTRUMENT comparison: sign test vs circular resultant, +on the SAME 19 stored storms. Post-hoc re-analysis, explicitly NOT a verdict. + +Operator (2026-08-12): "die irrationale Aufsummierung hilft, dass [der] Dipol +nicht auf 0.68 kollabiert." This probe measures that claim's core mechanism. + +THE ANATOMY OF THE 0.684 COLLAPSE. CT-F14/F16 scored the dipole with a SIGN +test: each storm's signed angular error collapses to one bit (error < 0?), and +19 bits saturate — F16c measured that a 90-deg-ROTATED reference also scores +0.684, i.e. the statistic cannot distinguish the real referent from a wrong +one at this n. Two distinct losses happen at the binarization: + + (1) MAGNITUDE is discarded — a tight cluster at -103 deg and a loose cloud + straddling zero can produce the same bit count (measured in F16's + weak/strong stratification). + (2) The MEAN DIRECTION is discarded — a systematic offset (the arc's -40 + deg) EATS the sign margin instead of being estimated. + +THE FIX IS AN AUFSUMMIERUNG: keep the error VECTORS and sum them. The +circular resultant R_bar (mean resultant length) + mean direction mu is the +standard directional statistic (Rayleigh test). It preserves exactly what the +sign test destroys: concentration (R_bar) and offset (mu) come out as two +separate numbers. A rotated control then shows the SAME R_bar with mu shifted +by 90 deg — visibly wrong — where the sign test scored it identically. + +Where the IRRATIONALITY enters (the operator's framing, demarcated honestly): +the vector summation itself is what prevents the collapse; the irrational +(golden/low-discrepancy) structure is what keeps the summation UNBIASED — +no resonance between sampling geometry and signal harmonics (within-storm, +load-bearing once the dipole is estimated from SPARSE spiral samples instead +of the dense grid), and natural phase diversity across storms (which is what +makes the CT-W6 multi-component fit identifiable at all). + +SCOPE: same 19 storms whose sign test returned NO-VERDICT. This is an +instrument demonstration on stored data — the verdict-grade use of the +resultant is CT-W6 on the same rows with pre-registered bars, and any FRESH +claim needs a fresh sample (CT-F17). Nothing here promotes the directional +claim; it measures what the previous instrument could not see. +""" +import json +import pathlib + +import numpy as np + +N_BOOT = 20_000 +RNG_SEED = 20260812 # fixed before running; bootstrap is deterministic + + +def wrap_deg(d): + """Wrap degrees into [-180, 180) — the identical convention CT-F14/F16 use.""" + return (d + 180.0) % 360.0 - 180.0 + + +def circular(errs_deg, n): + """Resultant length R_bar, mean direction mu (deg), Rayleigh p. + + R_bar in [0,1] measures CONCENTRATION (1 = all vectors identical, 0 = + uniform); mu is WHERE the cluster sits — the offset the sign test could + only penalize. Rayleigh Z = n*R_bar^2 with the standard small-n corrected + p-approximation (Zar/Mardia). Under uniformity E[R_bar] ~ sqrt(pi)/(2*sqrt(n)). + """ + th = np.deg2rad(np.asarray(errs_deg)) + c, s = np.cos(th).mean(), np.sin(th).mean() + r = float(np.hypot(c, s)) + mu = float(np.rad2deg(np.arctan2(s, c))) + z = n * r * r + p = float(np.exp(-z) * (1 + (2 * z - z * z) / (4 * n) + - (24 * z - 132 * z**2 + 76 * z**3 - 9 * z**4) + / (288 * n * n))) + return r, mu, max(min(p, 1.0), 0.0) + + +def boot_mu_ci(errs_deg, n, rng): + """Bootstrap 95% CI half-width (deg) for the mean direction mu.""" + th = np.deg2rad(np.asarray(errs_deg)) + idx = rng.integers(0, n, size=(N_BOOT, n)) + c = np.cos(th)[idx].mean(axis=1) + s = np.sin(th)[idx].mean(axis=1) + mus = np.rad2deg(np.arctan2(s, c)) + mu0 = np.rad2deg(np.arctan2(np.sin(th).mean(), np.cos(th).mean())) + dev = wrap_deg(mus - mu0) + lo, hi = np.percentile(dev, [2.5, 97.5]) + return float(max(abs(lo), abs(hi))) + + +rows = json.loads(pathlib.Path(__file__).with_name( + "comet_tail_f16.json").read_text())["rows"] +n = len(rows) +assert n == 19, f"expected CT-F14's 19 qualifying storms, got {n}" +rng = np.random.default_rng(RNG_SEED) + +e_surf = np.array([r["err_surface_deg"] for r in rows]) +e_steer = np.array([r["err_steering_deg"] for r in rows]) +lp = np.array([r["low_pole_rad"] for r in rows]) +sb = np.array([r["steer_bearing_rad"] for r in rows]) + +# the two F16c controls, recomputed per-row with the identical err convention +e_rot = wrap_deg(np.rad2deg(lp - (sb + np.pi / 2 + np.pi / 2))) +perm = sb[(np.arange(n) + 7) % n] +e_perm = wrap_deg(np.rad2deg(lp - (perm + np.pi / 2))) + +out = {"n": n, "source": "comet_tail_f16.json rows (paired, stored)", + "scope": ("POST-HOC instrument comparison on the same storms whose " + "sign test returned NO-VERDICT. Not a verdict; CT-W6 is the " + "pre-registered use, CT-F17 the fresh-sample one."), + "referents": {}} + +print(f"{'referent':<22} {'sign<0':>7} {'R_bar':>7} {'mu':>9} " + f"{'mu 95% CI':>10} {'Rayleigh p':>11}") +for name, e in (("surface (CT-F14)", e_surf), + ("steering (CT-F16)", e_steer), + ("CONTROL rot+90", e_rot), + ("CONTROL permuted", e_perm)): + r, mu, p = circular(e, n) + ci = boot_mu_ci(e, n, rng) + frac = float((np.asarray(e) < 0).mean()) + out["referents"][name] = {"sign_neg_frac": frac, "R_bar": r, + "mu_deg": mu, "mu_ci95_deg": ci, + "rayleigh_p": p} + print(f"{name:<22} {frac:>7.3f} {r:>7.3f} {mu:>+8.1f}° " + f"±{ci:>7.1f}° {p:>11.4f}") + +su, ro = out["referents"]["surface (CT-F14)"], out["referents"]["CONTROL rot+90"] +sep = abs(wrap_deg(su["mu_deg"] - ro["mu_deg"])) +print(f"\nuniform-expectation R_bar at n={n}: " + f"{np.sqrt(np.pi) / (2 * np.sqrt(n)):.3f}") +print(f"\nTHE DISCRIMINATION THE SIGN TEST LACKED:") +print(f" sign test : surface {su['sign_neg_frac']:.3f} vs rotated control " + f"{ro['sign_neg_frac']:.3f} -> conflated at n={n}") +print(f" resultant : mu separated by {sep:.1f}° " + f"(CIs ±{su['mu_ci95_deg']:.0f}°/±{ro['mu_ci95_deg']:.0f}°) at " + f"near-identical R_bar -> the wrong referent is VISIBLE") +out["discrimination"] = {"mu_separation_deg": sep} + +with open(pathlib.Path(__file__).with_name( + "comet_tail_resultant_instrument.json"), "w") as fh: + json.dump(out, fh, indent=2) +print("\nwrote comet_tail_resultant_instrument.json")