Error check
On 2026-09-07 the whole analysis was re-examined with fresh eyes: code, numbers, figures and notes. This is the complete log. It is also available as Markdown.
Fresh-eyes audit of the analysis code, the numbers in REPORT.md / literature_review.md / the PDF, all 22 figures, and notes/reconstruction_notes.md. Four independent audit passes (physics scripts; statistics and counts; candidate/day-night chain; figures, captions and unsupported claims) followed by targeted reruns and repairs.
Severity: critical = a stated conclusion was unsupported; major = a reported number, method description or figure was wrong but the conclusion survives; minor = small numeric drift or a missing qualifier; cosmetic = wording / plotting.
Published deliverables are preserved untouched: THEMIS_hot_springs_report_v1_2026-07-06.pdf
(original), THEMIS_hot_springs_report_v2_2026-09-07_precheck.pdf and
REPORT_v2_2026-09-07_precheck.md (as reconstructed, before this check),
literature_review_v1_2026-06-18.md. Corrected versions carry the base names; corrected
figures carry a _v2 suffix beside the originals in data/analysis/ and figures/.
Headline: did any conclusion change?
The top-line result stands. No compact, localized, hot feature consistent with an active hot spring exists in the nighttime archive; the Bayesian limit (< ~2 vigorous vents, 95%) is unchanged; the depth-to-liquid-water distribution is unchanged and, if anything, conservative.
One supporting claim was unsupported as published and has been redone. The per-candidate day/night and thermal-model results of §10–§11 ("every one of 21,385 persistent candidates individually resolved as rock", "95th-percentile endogenic warming < 3 K") were computed from samples taken a median of 3 pixels away from the detected warm pixels, i.e. from near-candidate background. See finding C1 for the corrected analysis and its numbers.
Two other analysis bugs were fixed and rerun: the regional (§4) cluster positions were flipped north–south (C2), and the global mosaic's TES albedo layer was misregistered by 180° of longitude (C3). Neither changes the null result.
Bookkeeping was wrong in several places: 194 of the 828 "pixel-analysed" warmest-pixel outliers were empty byte-fill products (B1); five different image counts were used interchangeably (B4); the global noise figure was stale (B5).
A. Physics scripts (detection_limit, detection_depth, depth_to_water, depth_atlas, thermal_model)
| id | severity | finding | fix | conclusion changed? |
|---|---|---|---|---|
| A1 | cosmetic | REPORT §3 table, 1200 K row, 5 K column said 9 m²; script gives 6.5 m² (8.7 is the 1000 K value) | table corrected | no |
| A2 | minor | §6 "~10⁵ GW globally"; script gives 3.0×10⁵ GW | text → ~3×10⁵ GW | no |
| A3 | minor | §6/§8b cooling time "40–400 yr", "decades": 2.1 W m⁻² is the ceiling of invisible flux (so a lower bound on lifetime) and latent heat of freezing (3.3×10⁸ J m⁻³) exceeds the sensible heat, lengthening it ~2–3× | text reworded ("at least", "centuries") | no |
| A4 | cosmetic | "×80 / ×100 / ×50–100" background multipliers used different heat-flow bases (25 vs 21 vs 20 mW m⁻²) | harmonized to ~×100 / ~40–80× with "~20–25 mW m⁻²" | no |
| A5 | minor | depth_to_water.py docstring claims a ~213 K equatorial anchor; the code yields 220.4 K (equilibrium T of the mean flux exceeds mean T by ~5–8 K, Jensen bias). Rerun with −7 K: pure-water median 7.2 → 8.0 km, marginalized 5.4 → 6.2 km, P(marg < 1 m) 2.6e-2 → 1.3e-2, P(pure < 1 km) stays 0. Bias makes the published depths shallower and the tail fatter, i.e. conservative for the report's claims |
docstring corrected; caveat noted in §8b | no |
| A6 | minor | §9 prose "heat flow high over Tharsis": the atlas has Tharsis at the mean (19.9 vs 20.0 mW m⁻²); highs are Acidalia 22.9 / Arabia 21.1, lows Hellas 17.9 / Elysium 18.8 | text corrected | no |
| A7 | cosmetic | depth_atlas.py K heat-production constant 16% low (2.92e-9 vs 3.48e-9 W/kg); absorbed by the 20 mW m⁻² calibration (0.1 mW m⁻² effect) |
documented only | no |
| A8 | major (model limitation) | thermal_model.py has no atmospheric back-radiation and no CO₂-frost floor: equatorial nights 10–40 K too cold at low inertia (128 K at I=50), amplitudes 25–50% too large. §11 differences each candidate against its own in-image background, which cancels the absolute bias; sensitivity rerun with F_down = 15 W m⁻² changes the §11 percentiles by < 0.1 K (95th 3.00 → 3.00 K, 99th 5.19 → 5.14 K) |
optional F_down argument added (default 0 preserves reproducibility); limitation stated in §11 |
no |
| A9 | cosmetic | build_tref.py final diagnostic print crashed (nstep undefined, LT index 48 out of range) after the lookup was already saved |
fixed | no |
| A10 | cosmetic | detection_depth.py printed "~0 GW" for the 10×10 km case and "1e+14" for 1.45e14 m² (format widths) |
fixed | no |
Verified correct: Planck band integration and mixed-pixel inversion (independent recomputation agrees to all digits); G = 4σT³ values; the invisible-flux and buried-water tables; the 210 K conduction sink vs 190/230 K radiative stiffness (different quantities, defensible); every §8b and §9 number against fresh seeded runs (byte-identical outputs); the eccentricity-weighted annual insolation; area weighting; GRS heat-production formula; MOLA lapse sign; the thermal model's orbit, declination, energy-balance signs, skin depth, I→k conversion, and explicit-scheme stability (κΔt/Δz² = 0.25).
B. Statistics, bounds and counts
| id | severity | finding | fix | conclusion changed? |
|---|---|---|---|---|
| B1 | major | Of the 828 warmest-pixel outliers that were "fully pixel-analysed", 194 are uncalibrated byte-fill products (SCALING_FACTOR 1, OFFSET 0, every DN identical; index MAX_BT = 255.0 or 250.0 is a fill value) and 4 more carry 32767 sentinel geometry. btrlib nulled only DN = 0, the screen only rejected MAX_BT outside 100–330 K, and reprocess_outliers.py filed them as "physical scenes, no cluster". Corrected split: 833 rows → 832 distinct → 828 with a BTR product → 630 genuine: 36 calibration artifacts (median > 260 K), 594 physical scenes, 282 with a cluster (106 compact, 92 diffuse, 84 linear). 106 compact ≤ 256.5 K unchanged |
btrlib._reject_fill (constant-DN / unit-scaling products → all-NaN); sentinel + fill rows dropped in global_maxbt_screen.py; new global_figure_v2.py → global_quadrant_v2.png; §7 rewritten |
no |
| B2 | minor | The same fill products are counted among the 136,564 "analysed" global images: 908 stat rows have σ = 0 (constant DN), 50 more have < 5,000 valid px. Images with real pixel variance ≈ 135,600 (99.3%) | footnoted in §7 | no |
| B3 | minor | "829" double-counted one observation (I02021001 appears twice in the index); global_figure.py hard-coded "829 images flagged from 137,952" |
v2 figure computes its counts from the data | no |
| B4 | major (bookkeeping) | Five image counts were used interchangeably: 137,952 (index rows with valid MAX_BT), 137,907 (worklist rows), 137,505 (hard-coded atlas title = done-list lines incl. 941 double-processed images), 136,567 (distinct), 136,564 (analysed). The headline value 136,564 appeared in no section body; §7 still said "we are streaming … growing to 100%" | §7 rewritten with the definitive accounting; atlas title → 136,564 (global_anomaly_atlas_v2.png); coverage title → distinct count and 99.8% (coverage_map_v2.png) |
no |
| B5 | minor | Global "median NEΔT 1.3–1.4 K" came from small early test shards; the full sample (136,514 images) gives median σ = 1.58 K (1.32 K at 0–30°N, 2–4 K at the poles). σ is a residual MAD after background removal and includes scene texture, so it is an upper bound on NEΔT | §7 corrected | no |
| B6 | minor | 1,340 duplicate worklist rows (two calibration versions per observation) were rasterized in coverage_map.py and processed twice by btr_stream.py (941 obsids): ≥5-look fraction 94.9 → 94.7%, ≥10 67.1 → 66.4%, mean looks 11.1 → 10.9; median 10 and 99.8% unchanged; ~7.84 M distinct clusters of the 7.88 M rows |
noted in §7/§8 | no |
| B7 | minor | Injection-recovery: below 1.5 pixels of vent area only the MAX_BT screen can detect (single pixels are rejected by MIN_CLUSTER_PIX = 2), so the "screen ∨ cluster" description is literally true but the cluster branch never applies to sub-pixel vents. Among the 312 genuine no-cluster scenes the warmest pixel is 251.6 K, so nothing hot hides there |
documented in §8 | no |
| B8 | cosmetic | Mars area 1.449e8 km² is 4πR² for a sphere; the oblate value is 1.4437e8 (0.4%, cancels in N₉₅). Marginalization over T_v by averaging A_eff over {273, 300, 350} K is the correct expectation for independently drawn vent temperatures; a uniform-in-T interpolation gives N < 1.94 instead of 1.98 | documented | no |
| B9 | cosmetic | "~1,366 non-recurring" → 1,363 (six detections form the three recurring cells); "tails to ~25 K" → 99th percentile 19 K, max 38 K; "37 uniform artifacts (no localized source)" — 8 of the 36 carry a cluster | text softened | no |
Verified correct: Poisson zero-count limits (Jeffreys Γ(½,1) 95% = 1.921; uniform 2.996 / 4.605); A_eff redundancy logic; every §8 number from a fresh run (N < 1.92 / 3.00 / 4.61; marginalized 1.98; 1.36e-8 km⁻²; 4.42 at 794 m²; 86.5 at 79 m²); coverage rerun byte-identical (99.8%, median 10, 2.77e6 cell-looks); injection ceiling 238.98 K and A50 ≈ 3,000–8,000 m²; §4 cascade 33,365 → 6,248 → 5,344 → 2,606 → 1,369 → 3 exact (positions aside, see C2); morphology 53/29/19%; regional σ median 1.29 K; 7,880,590 cluster rows; 21,385 persistent cells.
C. Candidate / day–night chain
| id | severity | finding | fix | conclusion changed? |
|---|---|---|---|---|
| C1 | critical | §10–§11 sampled the wrong pixels. Each persistent candidate is a 0.05° cell whose catalogue position is the mean of its detections; sample_night_bt.py and daytime_discriminate.py took a 3×3 mean at the pixel nearest that mean (on a 4× downsampled grid). Detections within a cell are separated by a median 0.8 km, so the samplers landed a median 3 px (up to 12 px) from the warm pixels. Evidence: re-sampled night excess median −0.1 K vs catalogue 10 K (r = −0.06); on 38 cells with ≥ 15 K catalogue excess, the recorded pixel really is 10.4 K warm but the sampled position reads 2.1 K. Hence "84.8% cool by day", "r = +0.01", "ΔT_geo median 0.01 K / 95th 3 K", and "every candidate individually fit and found thermophysical" described background, not the anomalies |
Redone at the detection pixels with window sampling on the day side, control-field bias calibration and per-image Ls/LT in the thermal model (resample_candidates_v2.py, fit_residual_v2.py, outputs in data/analysis/v2/). Results: see §C1-results below |
§10–§11 statements replaced; overall null result unchanged (it never rested on §10–§11) |
| C2 | major | Regional SNU-cube georeferencing (§4) interpolated raw-image corner coordinates onto the map-projected array; raw line 1 is the south end, so latitude was reversed along the strip: position errors 22 km (289-row cube) to 545 km (10,692 rows). Global BTR work uses raw products and is unaffected | anomaly_search.py now uses the cube's own sinusoidal projection; regional search rerun → candidates_v2.csv, focused_candidates_v2.csv; see §C2-results |
§4 recurring-cell positions replaced; regional null unchanged |
| C3 | major | combine_grids.py looked up TES albedo at longitude L−180 for grid longitude L (grid 0–360, tif −180..180): Tharsis got Syrtis's albedo and vice versa. With the fix the §12 night-excess floor and temperate exceedances change: see §C3-results |
fixed in place; global_daynight_mosaic_v2.png, daynight_grids_v2.npz |
no (screen result unchanged) |
| C4 | major (description) | fit_residual.py seasonal binning was dead code: the candidate file has no Ls column, so every candidate was modelled at Ls = 250; day and night images were not season-matched |
v2 fit uses each image's own Ls and local time | subsumed by C1 |
| C5 | major (limitation) | Morphology gate: MIN_CLUSTER_PIX = 2 plus the elongation test send every ≤ 2-px-wide cluster to "linear/artifact"; the smallest "compact" cluster is 3×3 px, so the focused catalogue is blind below ~300 m. The §8 bound rests on the MAX_BT screen and is unaffected |
stated in §4/§8 | no |
| C6 | major (wording) | "Sunlit ceiling ~325–330 K" is the sub-solar noon perihelion equilibrium (326–335 K for A 0.15–0.05); THEMIS day passes are at ~15 h local time, where the project's own model tops out near 300–312 K, so the 306.8 K margin was not apples-to-apples | text corrected; the model fit (v2) is the actual test | no |
| C7 | minor | 4,214 daytime worklist rows have lt = ls = inc = 0 (bad metadata); grid_accumulate.py destriped them as midnight, biasing ~2% of day images (cells including one by a few K). Re-accumulating the day grid takes ~11 h and was not redone |
excluded in the v2 candidate sampling; caveat in §12 | no |
| C8 | minor | §12 coverage "95.8 / 97.1%" is cnt > 0; at the MINCNT = 20 actually used it is 95.7 / 97.1% | text | no |
| C9 | minor | "top-100 nearest-neighbour 3.8° ≈ 225 km" used flat degrees; great-circle 3.52° = 208 km, and the top-100 are slightly more clustered than random (4.9°) | superseded by C1 v2 analysis | no |
| C10 | minor | "temperate blob aspect 4–6": the eight largest are 2.7–11.9 | text ("~3–12") | no |
| C11 | cosmetic | sample_context.py row index ≤ 1 px off; unused helpers in combine_grids.py/anomaly_search.py; day local time measured then dropped in daytime_discriminate.py |
documented | no |
| C12 | minor | Literature review: the objectives "quote" splices the abstract and Section 1; "~2–5 a.m. orbit" is not in Christensen et al. 2004 (archive night local times are ~3–6 h); Eridania heat flow was cited to Tarnas et al. 2021 but is Ojha et al. 2021 and is a Noachian value | corrected in literature_review.md and REPORT §1 |
no |
| C13 | minor | "Cerberus Fossae, where Mars's marsquakes originate" overstated (dominant located source; S1222a and dichotomy-boundary events elsewhere) | text | no |
Verified correct: the anomaly detector core (background, MAD σ, thresholds, morphology PCA, edge handling; synthetic tests recover a 3×3 +12 K spot and produce zero false clusters on a gradient strip); corner-based georeferencing of raw BTR products (r = +0.99 against a projected cube); persistent-cell definition (21,385 rows, cell ids consistent); TES albedo/TI orientation in sample_context.py; §10/§11 numbers reproduce from the CSVs exactly as written (the CSVs themselves are what was wrong).
C1-results (day/night per-candidate redo)
resample_candidates_v2.py rebuilt the 21,385 cells from the shards (identical to the
catalogue), re-read each cell's strongest detecting night image from the cache and
recomputed the background at the detected pixel; the recomputed night excess matches the
catalogue to 0.1 K for 98.8% of cells (median 9.9 K). The detection coordinates were
georeferenced into 9,385 daytime images (exact footprint test; 4,214 bad-metadata images
excluded) and sampled in a 17×17-px window (cross-image georeferencing ≈ 1 km), with the
window-maximum bias calibrated against 20 random control sites per image. 21,223 cells have
night + day; 21,151 have a converged thermal fit. Runtime 12.5 min on 40 processes.
Two further flaws surfaced: daytime_match.py used a corner bounding-box test, so about
two thirds of the original day "samples" were edge pixels of images that do not contain the
point; and the original ΔT_geo differential implicitly assumed the warm pixel shares its
surroundings' albedo, so any dark rock registers as "endogenic".
| quantity | as published (§10–§11) | at the detected pixels |
|---|---|---|
| cool/neutral by day (< 2 K) | 84.8% (median day residual 0.13 K) | window-median: 88.3% (+0.05 K); centre pixel: 75.4% (+0.15 K); window-max (charitable, calibrated): 28.9% (+4.4 K, matched by −3.9 K window-min = terrain texture) |
| r(night excess, day excess) | +0.01 | −0.04 (window-max), −0.01 (window-median) |
| strongest 205 night features (> 20 K): median day excess | — | 3.3 K (window-max), 0.0 K (window-median): no proportional warm-both branch |
| hottest day pixel | 306.8 K (claimed below a 325–330 K noon ceiling) | 338 K, in a scene-wide 336 K calibration-outlier image (+1.1 K over its background); 4 cells > 310 K |
| ΔT_geo, original same-albedo definition: 50th / 95th / > 5 K | 0.01 K / 3.0 K / 1.1% | 8.4 K / 14.9 K / 93% — driven by sunlit-slope day pixels (r = 0.86 with day window-max, 0.01 with night excess) |
| ΔT_geo, albedo-free ceiling: 95th, F_down = 0 / 15 W m⁻² | — | 5.9 K / 1.0 K (window-max day value); 1.0 K (centre pixel); 0.0 K (window median) |
| cells inside the flat-surface manifold with zero internal heat | "100%" | 94–98% |
Consequence: §10–§11 rewritten. The supportable statements are that night and day excesses are uncorrelated with no co-located proportional day excess (the population is dark high-inertia rock), and that a 1-D flat-surface fit bounds the endogenic warming of ≥95% of candidates below ~1–6 K depending on model assumptions, with the tail dominated by sunlit slopes. The "< 3 K for 95%, median 0.01 K" claim, the "100% thermophysical" headline row and the abstract sentence are withdrawn and replaced. The §8 hot-spring bound never depended on §10–§11 and is unchanged.
C2-results (regional georeferencing rerun)
anomaly_search.py now georeferences from each cube's own sinusoidal projection (ISIS-3 v2
cubes via the rasterio affine + CRS; PDS-3 v1 cubes via the label projection, undoing the
per-line RECTIFY_LEFTEDGE shift and anchoring to the THMIDX corners because the v1 label
geometry is itself 1.4–8 km off, as shown by cross-correlation against overlapping v2 cubes).
Validation: north-up in 230/230 cubes (old path 0/229); footprint centres within 2 km of the
index centre for 219/230; overlapping-cube registration ≤ 4 px. Full evidence in
data/analysis/regional_v2_README.md.
| quantity | as published | corrected |
|---|---|---|
| cluster positions | flipped; median error 332 km, max 1,086 km | product frame, ~0.3–0.5 km |
| non-positional columns (33,365 rows), σ, morphology, cascade to 1,369 | — | byte-identical |
| recurring 0.05° cells across ≥2 overpasses | 3 (all "162°E") | 69 (Cerberus 43, Margaritifer 15, Elysium 10, Olympus 1; peak ≤ 14.6 K) |
| recurring cells within 0.1° of a global persistent cell | not checkable | 31/69 (45%) |
| focused detections within 0.1° of a global cell | 7.6% (chance 10–15%) | 29.1% |
| strongest one-offs | positions unusable | Ascraeus 11.60°N 254.67°E (+17.6 K; +26.3 K/150 px nearby, no global counterpart); Cerberus 3.69°N 159.96°E (+15.6 K; global cells 2–4 km away) |
The original "3 recurring locations, all at 162°E" statement and the §4 day/night
co-location check were artefacts of the flip and are withdrawn. Regional conclusion
(thermophysical, no vent) unchanged. Side effect: focused_candidates.csv was regenerated
with identical content during verification.
C3-results (mosaic albedo fix)
Rerun of combine_grids.py with the longitude fix (global_daynight_mosaic_v2.png,
daynight_grids_v2.npz):
| quantity | as published | corrected |
|---|---|---|
| night-excess 95th / 99th percentile | 16.9 / 27.3 K | 13.7 / 22.5 K |
| temperate cells > 15 K / > 20 K | 2.77% / 0.86% | 1.64% / 0.34% |
| coverage night / day (≥20 samples) | 95.8 / 97.1% | 95.7 / 97.1% |
| largest blobs overall | polar | polar (lat 74.8, 73.9, −82.7, 76.2) |
| ten largest temperate blobs, aspect | "4–6, never compact" | 2.1–46.7 (four are < 3) |
| ten largest temperate blobs, mean day anomaly | not checked | −6 to −14 K (all cool by day); night anomaly +13 to +32 K |
| temperate > 15 K cells with day anomaly < 0 | not checked | 94% |
The original "elongated, never compact" argument was wrong for four of the ten largest blobs; the day/night sense replaces it and is decisive (thermal inertia lifts night and lowers day; a geothermal offset lifts both). Conclusion unchanged.
D. Figures, captions, PDF, notes
| id | severity | finding | fix | conclusion changed? |
|---|---|---|---|---|
| D1 | major | global_quadrant.png drew the hot-spring boundary at 250 K and wrote "EMPTY" while six compact points at 250.5–256.5 K sat inside; the text acknowledged them, the figure and caption did not |
global_quadrant_v2.png (boundary at the 260 K bedrock ceiling, the six points labelled, fill products removed) |
no |
| D2 | major | daynight_discrimination.png caption said "none in the endogenic regime" while the shaded region held 15% of candidates; and the whole figure is superseded by C1 |
caption rewritten; v2 figure from the redo | see C1 |
| D3 | major | global_daynight_mosaic.png caption/§12 said "no coherent broad anomaly" while the middle panel showed province-scale ±5–10 K structure (manifold-fit residual over dust provinces); three thresholds (15/17/20 K) never reconciled; and the albedo layer was misregistered (C3) |
v2 figure and caption qualified "above the screen floor" | no |
| D4 | major | Atlas title hard-coded "137,505 images"; coverage title said "100%" (rounding of 99.82) | _v2 figures |
no |
| D5 | major | The two strongest one-off regional candidates (zoom_I01474006_7458_1102.png, zoom_I54939008_6465_1013.png; +15.6 K and +17.6 K, and a +26.3 K/150-px cluster in the same Ascraeus image) were never discussed; §4 dismissed all 1,363 non-recurring detections in one sentence |
discussed in §4 with corrected positions from C2 | no (absolute T 155–175 K, far below any hot-spring signature) |
| D6 | major | depth_to_water_hist.png: components each normalized to 1 under a "fraction of Mars surface" label, and a 2.5% spike at 0.1 m (depth-0 draws clipped to the log floor) under the title "note the empty shallow tail" |
depth_to_water_hist_v2.png (mixture-weighted components; depth-0 spike drawn and labelled as ambient-temperature eutectic brine) |
no |
| D7 | minor | Headline table said "84.8% of the 21,385" (denominator was 21,308); "4,043 day images cover all 21,385" (21,308 sampled); "≈60 m" vs table 66 m; 1.9 vs 1.92; "95% ≥ 5 looks" vs 94.85% | harmonized | no |
| D8 | minor | The daynight_I05456012_I44697047.png pair overlaps only in a ~0.3° corner; caption now says so |
caption | no |
| D9 | cosmetic | Clipped title in img_I05456012.png; unexplained "?" region bar in fig_by_region.png; colorbars capped (persistence 8, ΔT_geo 8); §12 figures land after the Figures/Reproduce subsections; PDF Title metadata "themis_report.html"; notes/reconstruction_notes.md said 46 scripts (41) |
notes corrected; others documented | no |
| D10 | minor | Unsupported literals checked: Yellowstone ~2 W m⁻² (OK, Morgan et al. 1977), Mars background 20–25 mW m⁻² (OK), Athabasca lava < 20 Ma (OK, conservative), TES albedo 7 km (OK, 8 ppd = 7.4 km) | — | no |
E. Reproducibility
Re-running detection_limit, detection_depth, coverage_map, global_maxbt_screen,
bayes_bound, vet_candidates, depth_to_water, depth_atlas and build_tref from the
on-disk inputs produced byte-identical outputs (md5) before any fix was applied. All fixes
are in new _v2 scripts/outputs or in-place code changes documented at the top of each
changed file.