THEMIS Mars hot springs

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.pyglobal_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.