"The Price of Caution near a Collapsing Threshold" Romain Fillon
What the paper does. It prices the risk of crossing a climate tipping threshold. Inside a window the paper delimits, more climate noise lowers that price rather than raising it, inverting the standard reading of uncertainty. And the price diverges or stays bounded according to how fast the safety margin closes — as the square root of the remaining carbon budget, or in proportion to it — so two elements at identical proximity can carry prices that differ without limit.
One command, from this directory:
bash run_all.shIt recomputes every number, table and figure the manuscript displays, writes them where
manuscript/paper.tex reads them, verifies that each exhibit the paper displays was
produced by that run, and compiles manuscript/paper.pdf.
The run produces:
- the figures into
02_output/figures/, copied tomanuscript/figures/; - the table file,
manuscript/tables/crra_hjb_slopes.tex; manuscript/values.tex, every inline number the paper quotes, each carrying the script and the inputs it came from;02_output/values/VALUES_DIFF.md, a per-macro comparison of the regenerated values against the manuscript's previous state;manuscript/paper.pdf.
The last thing it prints is one line per exhibit. The run exits non-zero if any step
fails or if any exhibit the paper displays was not produced, and names what is missing.
The authority for that list is paper.tex itself, harvested at run time — not a list kept
by hand in this file.
What that check does and does not establish. Step 4/5 asserts that every file
paper.tex includes or inputs exists and is newer than the start of the run — so a
committed leftover whose generator has been removed is reported, not passed. It does not
compare figure contents between runs: every PDF carries a creation timestamp in its
metadata, so two runs of the same seeded script differ byte for byte. What stands behind
the figures instead is that they are built from the same seeded runs as the numbers, and
that no script anywhere in 01_code/ prints a date into a figure.
| Python | 3.14.3 (developed and tested; 3.11+ expected to work) |
| Packages | numpy==2.4.4, scipy==1.17.1, matplotlib==3.10.8, pandas==3.0.1 |
| Install | python -m pip install -r 01_code/requirements.txt. On Windows, run under Git Bash; if python resolves to the Microsoft Store alias the run stops and tells you to pass PYTHON=/c/path/to/python.exe, that alias not being an interpreter. On Linux and macOS bash run_all.sh needs nothing said about it. |
| LaTeX | latexmk with a full TeX distribution, for step 5/5 only |
| Network | Required once, for one series (§3) |
| Disk | About 10 MB for the package; under 50 MB after a run |
| Runtime | Half an hour to an hour on a current laptop, dominated by a few Monte-Carlo and BVP sweeps. Read that as an order of magnitude, not a budget: across cold runs of the same machine the wall clock has varied by more than half while the values came back byte-identical — one run was faster on every single script — so the spread is machine load and says nothing about the work. The per-script RUNTIME headers give an observed range for the same reason. |
All Monte Carlo is seeded, so reruns are deterministic: calibration_ci.py,
eps_inf_ci.py, ews_calibration.py and fig_forest_tc.py use np.random.seed(42). The
solvers draw no random numbers at all. Two cold runs separated in time return every computed
macro identical to the last digit printed and the CRRA slope table byte for byte, which
is what makes 02_output/values/VALUES_DIFF.md worth reading: a rerun that disagrees with
manuscript/values.tex is reporting a real difference in the environment, not sampling noise.
Every input is either one public time series, fetched at run time, or a literature constant cited in the manuscript. The package bundles no data files.
| Input | Provider / citation | Status | In this package | Used by |
|---|---|---|---|---|
| AMOC SST fingerprint (subpolar-gyre index, HadISST, 1871–2016) | Caesar et al. (2018), Nature 556, 191–196 — PIK | Host page publishes no licence, copyright notice or conditions of use | No — fetched at run time | ews_calibration.py |
| HadISST (the underlying SST record) | Met Office Hadley Centre | Distributed by the Met Office under its own terms | No — reached only through the index above | — |
| Tipping thresholds and timescales | Armstrong McKay et al. (2022), Science 377, eabn7950 | Published constants | Yes — 01_code/inputs_literature.py, with citation |
calibration |
| Present warming, TCRE | IPCC AR6 | Published constants | Yes — 01_code/inputs_literature.py, with citation |
calibration |
Three NOAA PSL series (AMO, Niño 3.4, Tropical Southern Atlantic) shipped with the package
until 2026-09 as regional placebos for a third-moment diagnostic. That argument is no longer
in the manuscript; the diagnostic and the three files are in 99_archive/, with their
provenance and checksums, and nothing in the package reads them.
ews_calibration.py downloads, at every run, from
https://www.pik-potsdam.de/~caesar/AMOC_slowdown/sg_index_hadisst.txt
a plain-text file of 1 638 bytes, 146 annual values covering 1871–2016, carrying a one-line header with the years and the unit: the subpolar-gyre sea surface temperature index of Caesar et al. (2018), derived by those authors from the HadISST record. The package reads it into memory and does not write a copy to disk.
The host page publishes no licence, no copyright notice and no conditions of use, and this package does not redistribute the file. A replicator therefore needs network access once, for this one series. This is the package's only external dependency at run time and its only availability risk.
This is also the only network call the package makes, and it is made in one place. A second script used to fetch the same series independently, from outside the guard described below; it is archived, and with it that second call.
If the URL stops resolving. The index is a documented transformation of a public
record: it can be reconstructed from HadISST using the subpolar-gyre region definition
given in Caesar et al. (2018). That reconstruction has not been carried out here, and
nothing in this package performs it — a replicator taking that route would be rebuilding
the input rather than rerunning it, and should verify the rebuilt series against the
summary statistics ews_calibration.py prints (146 values, detrended variance 0.0683 K²,
AR(1) restoring rate 0.4933 yr⁻¹).
What does not reproduce without it. The fetch is the first link in a chain: it estimates
the restoring rate λ̂ and the detrended variance, and three later scripts —
reduced_form_scc.py, crosspartial_contrast.py and noise_reversal_frontier.py — read
those two numbers back out of its captured output.
35 of the 60 computed macros in manuscript/values.tex carry
[requires network], alongside 11 sourced literature constants
(71 macros in the file).
Those macros are unobtainable without the series — not only λ̂ and Var themselves, but
everything downstream of them: the reduced-form price level, the cross-partial contrast
ratios, the noise-reversal frontier, the additivity errors, the dynamic scale ε_dyn and the
discount-range price ratios. run_all.sh does not run those three dependent scripts at all
when the fetch fails, rather than let any of them continue on a recorded constant, and it
names the step that failed.
What does reproduce without it. Every figure the paper shows; the CRRA slope table; the
Monte-Carlo budgets and deadlines; and every computed macro not marked [requires network],
together with the sourced literature constants — the counts above give both. The contrast
and frontier solvers are not in that list, although their mathematics needs no data:
both read λ̂ out of the fetch and exit without it, so every macro they emit is marked, on
the availability the marker states rather than on what the equations consume. The manuscript
will not compile to its published numbers, because the missing macros include ones the body
quotes.
Everything between the markers below is written by make_inventory.py at every run, from
what the run actually produced; a hand edit there is overwritten by the next run.
The paper's other tables are written inline in paper.tex; their cells are macros from
values.tex or analytic constants, so they carry no separate file.
| Exhibit in the paper | Produced by | Data consumed | Status |
|---|---|---|---|
fig_bifurcation_intro.pdf |
01_code/03_figures/fig_bifurcation_intro.py |
none (self-contained) | up-to-date |
fig_cross_partial.pdf |
01_code/02_solvers/paper3_cross_partial_figures.py |
none (self-contained) | up-to-date |
fig_forest_tc.pdf |
01_code/03_figures/fig_forest_tc.py |
none (self-contained) | up-to-date |
crra_hjb_slopes.tex |
01_code/04_tables_values/make_tables_values.py (from crra_hjb_scc.py) |
none (self-contained) | up-to-date |
Every number the paper quotes inline is a macro in manuscript/values.tex, and every
macro carries, as a comment on its own line, the script and the inputs it came from.
Grouped here by producing script. The Macros column counts the macros marked
[requires network], and those counts add up down the column to the figure §3 gives. The
Data column answers a different question — what the script needs in order to run.
Here the two agree: every script that reads the fetched series
emits macros marked by it, because none of them runs without it.
| Produced by | Macros | Data |
|---|---|---|
01_code/01_calibration/calibration_ci.py |
6 (0 network) | none (self-contained) |
01_code/01_calibration/eps_inf_ci.py |
3 (0 network) | none (self-contained) |
01_code/01_calibration/ews_calibration.py |
3 (3 network) | PIK SST record (network) |
01_code/01_calibration/reduced_form_scc.py |
8 (8 network) | PIK SST record, via the EWS estimates |
01_code/02_solvers/crosspartial_contrast.py |
9 (9 network) | PIK SST record, via the EWS estimates |
01_code/02_solvers/noise_reversal_frontier.py |
3 (3 network) | PIK SST record, via the EWS estimates |
01_code/02_solvers/paper3_cross_partial_figures.py |
2 (0 network) | none (self-contained) |
01_code/03_figures/fig_forest_tc.py |
6 (0 network) | none (self-contained) |
01_code/04_tables_values/collect_computed.py |
20 (12 network) | closed forms and sourced constants |
01_code/inputs_literature.py |
11 (0 network) | literature constants |
These ship although the paper displays nothing they make and they emit no macro. Each is the numerical evidence behind one sentence, which is a third category next to "produces a figure" and "produces a macro" -- and the one no import graph reveals.
| Script | The sentence it supports | In the default run? |
|---|---|---|
01_code/02_solvers/paper2_all_bifurcations.py |
the class column of the universality table, stated analytically and checked numerically here | yes -- it is also imported by paper3_cross_partial_figures.py |
01_code/02_solvers/paper1_ez_amplitude.py |
*"monotonicity is numerical, over four decades bracketing the calibrated values and for | βL |
01_code/02_solvers/paper1_crra_reversal.py |
"the price falls strictly with the noise at every γ ∈ {0.5,…,6}" (CRRA appendix) | no -- run python 01_code/02_solvers/paper1_crra_reversal.py, about 13 min |
| Path | Contents |
|---|---|
run_all.sh |
The single entry point. Runs everything, checks coverage, compiles. |
01_code/01_calibration/ |
Calibration: the EWS estimates, the Monte-Carlo budgets, deadlines and the reduced-form level. |
01_code/02_solvers/ |
The first-passage, amplitude, cross-partial and noise-reversal numerics. |
01_code/03_figures/ |
Two scripts written around a figure: fig_bifurcation_intro.py produces only that, fig_forest_tc.py also emits six macros. |
01_code/04_tables_values/ |
Collects the printed results, writes values.tex and the table file, and regenerates §4 above. |
01_code/check_exhibits.py |
The coverage control: every exhibit paper.tex displays was produced by this run. |
01_code/inputs_literature.py |
The sourced constants, each with its citation. |
02_output/ |
Everything the run writes: figures, tables, computed.json, captured script output. |
manuscript/ |
paper.tex (authoritative), the generated values.tex, the table and figure files it reads. |
99_archive/ |
Withdrawn scripts and their outputs, each under its original path, with a dry inventory: what, from where, why withdrawn, and the commit to restore it from. Nothing here is needed to reproduce the paper. The authoring tooling and the record of how the package reached its present shape are kept in the project repository, outside this package. |
The code in 01_code/ and run_all.sh is released under the MIT licence; see
LICENSE. Use it, adapt it, cite the paper if it helps you.
The manuscript (manuscript/paper.tex, its figures and its text) is not covered by that
licence and remains the author's, as does the paper itself.
The data are covered by neither, because the package redistributes none: the one series it uses is fetched from its publisher at run time and is discussed in §3.