5D Library › Phase Transitions, Critical Slowing Down and Resilience in Complex Dynamical Systems · 中文
Chapter 8 Practice and a Pre-registered Trial
Note
The checklist in this chapter is a thinking tool, not an engineering standard, a medical protocol or an investment method. The example is a fictional small-town water system, and every name and number in it is invented. Any risk assessment of a real reservoir, power grid, hospital or company must be carried out by qualified professionals in the field according to local standards. This chapter gives no medical advice and no investment or business advice.
Part 1 A four-step resilience and early-warning checklist
The models and evidence of the first seven chapters can be condensed into four questions. They help a team see how far its system is from a threshold, how failures would spread and what a flip would cost, rather than forecasting a date.
- Monitoring recovery time and rolling autocorrelation. Do key indicators recover ever more slowly after disturbances? Compute the lag-1 autocorrelation in a rolling window and convert it into a recovery time (Chapter 2), look at the variance as well, and check whether outside disturbances themselves have grown. A rise is only a prompt that “the cause is worth finding”, not an alarm; the decision rule is written down in advance and its false-alarm rate estimated on periods without a transition (Chapter 3).
- Auditing the spare capacity . By how much does each key component’s capacity exceed its everyday load ()? More importantly, if any one component fails, can the others absorb its load (an “N−1” check, Chapter 5)? The most important redundancy looks most like waste in ordinary times (Chapter 4).
- Compartmentalising tight coupling and single points of failure. Is there a component whose failure cuts off a large part of the system? Which parts are tightly coupled, without slack or delay (Chapter 6)? Can bypasses be added and modules separated, while checking that new switching devices do not create new complex interactions?
- Assessing irreversibility and the cost of hysteresis. If the system flipped, how low would the pressure have to go for recovery, and how long would it take (Chapter 1)? How much margin is left before the threshold? Prevention usually costs a little everyday output or efficiency; recovery can cost far more, or be impossible.
Calculation
The checklist applied to the fictional “Clearbrook” water system: a reservoir, three treatment plants (P1–P3), a hub M and five districts. Step 1 computes the quarterly lag-1 autocorrelation and recovery time of two years of synthetic daily chlorophyll anomalies (with the recovery rate falling from 0.40 to 0.15 per day in the second year); step 2 runs an N−1 check on the three plants (when one plant stops, its load moves to the others in proportion to their spare capacity, and an overloaded plant stops in turn); step 3 looks for single points of failure in the pipe network; step 4 uses the lake model of Chapter 1 () to estimate the reservoir’s safety margin and what recovery would require after a flip.
Calculation
# The four-step checklist on a FICTITIOUS small-town water system ("Clearbrook"): a reservoir,
# three treatment plants (P1-P3), a hub M and five districts. All names and numbers invented.
import math, random
rng = random.Random(15)
# Step 1. Rolling AC(1) of the reservoir's daily chlorophyll anomaly (synthetic: the recovery
# rate falls from 0.40 to 0.15 per day during year 2), and recovery time -1/ln AC(1).
x, days = 0.0, []
for d in range(730):
lam = 0.40 if d < 365 else 0.40 - 0.25 * (d - 365) / 365
x += -lam * x + 0.5 * rng.gauss(0, 1); days.append(x)
def ac1(w):
m = sum(w) / len(w); v = sum((u - m) ** 2 for u in w)
return sum((w[i] - m) * (w[i + 1] - m) for i in range(len(w) - 1)) / v
q_ac = [ac1(days[q * 91 - 90:q * 91]) for q in range(1, 9)]
print("Step 1 quarter " + " ".join(f" Q{q} " for q in range(1, 9)))
print(" AC(1), 90 days " + " ".join(f"{a:.2f}" for a in q_ac))
print(" recovery (days) " + " ".join(f"{-1 / math.log(a):4.1f}" for a in q_ac))
s = sum((q_ac[j] > q_ac[i]) - (q_ac[j] < q_ac[i]) for i in range(8) for j in range(i + 1, 8))
ratio = -1 / math.log(q_ac[7]) / (sum(-1 / math.log(a) for a in q_ac[:4]) / 4)
print(f" Kendall tau over 8 quarters {s / 28:+.2f}; recovery time Q8 / mean(Q1-Q4)"
f" = {ratio:.1f}\n -> REVIEW (look for a cause; not an alarm)")
# Step 2. N-1 check of spare capacity alpha = C/L - 1: a failed plant's load moves to the
# others in proportion to their spare capacity; a plant pushed above capacity fails in turn.
def n_minus_1(load, cap):
out = {}
for first in load:
down = {first}
while True:
up = [v for v in load if v not in down]
moved = sum(load[v] for v in down); spare = sum(cap[v] - load[v] for v in up)
over = [v for v in up if spare <= 0 or moved * (cap[v] - load[v]) / spare
> cap[v] - load[v] + 1e-9]
down |= set(over)
if not over or len(over) == len(up): break
out[first] = len(down)
return out
load, cap = {"P1": 60, "P2": 40, "P3": 30}, {"P1": 70, "P2": 70, "P3": 45}
print("Step 2 alpha: " + ", ".join(f"{v} {cap[v] / load[v] - 1:.2f}" for v in load)
+ f"; total spare {sum(cap.values()) - sum(load.values())}, largest load 60")
print(" plants down if one fails: " + str(n_minus_1(load, cap)))
cap["P2"] += 20; cap["P3"] += 15
print(" after P2 +20, P3 +15: " + str(n_minus_1(load, cap)))
# Step 3. Single points of failure: which one node, if lost, cuts a district off all plants?
links = [("P1", "M"), ("P2", "M"), ("P2", "E"), ("P3", "W"), ("M", "N"), ("M", "E"),
("M", "S"), ("M", "W"), ("S", "T"), ("W", "T")]
def cut_off(links, lost):
nb = {}
for a, b in links:
if lost not in (a, b): nb.setdefault(a, set()).add(b); nb.setdefault(b, set()).add(a)
seen = {p for p in ("P1", "P2", "P3") if p != lost}; st = list(seen)
while st:
for w in nb.get(st.pop(), ()):
if w not in seen: seen.add(w); st.append(w)
return [d for d in "NESWT" if d != lost and d not in seen]
nodes = sorted({v for l in links for v in l})
print("Step 3 single points of failure: " + str({v: cut_off(links, v) for v in nodes
if cut_off(links, v)}))
links.append(("E", "N"))
print(" after adding link E-N: " + str({v: cut_off(links, v) for v in nodes
if cut_off(links, v)}))
# Step 4. Hysteresis cost of the reservoir (lake model of Chapter 1, loss rate b = 0.8).
a_now, a_on, a_off = 0.42, 0.510, 0.144
print(f"Step 4 loading {a_now} vs tipping point {a_on}: safety margin {1 - a_now / a_on:.0%}")
print(f" once turbid, loading must fall to {a_off} ({1 - a_off / a_now:.0%} below today)")
# Step 1 quarter Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8
# AC(1), 90 days 0.40 0.52 0.63 0.60 0.71 0.63 0.84 0.73
# recovery (days) 1.1 1.5 2.2 2.0 2.9 2.1 5.9 3.1
# Kendall tau over 8 quarters +0.71; recovery time Q8 / mean(Q1-Q4) = 1.8
# -> REVIEW (look for a cause; not an alarm)
# Step 2 alpha: P1 0.17, P2 0.75, P3 0.50; total spare 55, largest load 60
# plants down if one fails: {'P1': 3, 'P2': 3, 'P3': 1}
# after P2 +20, P3 +15: {'P1': 1, 'P2': 1, 'P3': 1}
# Step 3 single points of failure: {'M': ['N']}
# after adding link E-N: {}
# Step 4 loading 0.42 vs tipping point 0.51: safety margin 18%
# once turbid, loading must fall to 0.144 (66% below today)Step 1: over eight quarters the lag-1 autocorrelation rises from 0.40 to 0.73 (0.84 in the seventh quarter), Kendall’s , and the latest quarter’s recovery time is 1.8 times the first year’s average. The conclusion is “review”: look for the cause (has nutrient input increased, has the monitoring method changed?) rather than announce that the reservoir is about to flip; with only eight quarters of data, the simulations of Chapter 3 suggest that such a trend is not rare even in a system where nothing has changed. Step 2: the plants’ are 0.17, 0.75 and 0.50 and total capacity exceeds total load by 42 %, but the total spare capacity of 55 is less than the largest plant’s load of 60: if P1 or P2 stops, all three stop. After adding 20 units of capacity to P2 and 15 to P3, the loss of any one plant affects only itself. Step 3: the hub M is a single point of failure; if it fails, district N is cut off from every plant; one added link E–N removes all single points of failure. Step 4: current loading of 0.42 leaves an 18 % margin before the tipping point of 0.51; but once the reservoir flips, loading must fall to 0.144 for recovery, 66 % below today’s. This asymmetry is the cost of hysteresis. The numbers are invented and serve only to show the calculations.
Part 2 A pre-registered randomised controlled trial protocol
Question
In a resource-management task in which managers do not know the structure of the system and a fold is hidden within it, does adding an early-warning dashboard of “rolling recovery time and variance” to the current stock and current profit reduce irreversible collapses of the resource? And how much everyday profit does it cost?
Earlier experiments suggest that the question is worth asking. Moxnes had 83 participants, mostly from the Norwegian fisheries sector, each manage a simulated fish stock alone: with sole property rights, the “tragedy of the commons” was ruled out, yet they consistently over-invested (building about 60 % more fishing capacity than needed) and drove the stock about 15 % below its optimal level; he attributed this to misperception of feedback in dynamic systems (Moxnes 1998). In experiments on climate negotiations, Barrett and Dannenberg found that when the location of a catastrophic threshold was known, collective action almost always avoided crossing it, while uncertainty about its location made cooperation collapse (Barrett and Dannenberg 2012). The question is whether the indicators of Chapter 2 can partly substitute for “knowing where the threshold is”.
Design
| Element | Specification |
|---|---|
| Design | Two-arm parallel trial randomised by individual, stratified by site, 1:1 allocation |
| Participants | Adults aged 18 or over, recruited through public calls at universities and research institutions and paid in line with experimental-economics practice, partly in proportion to their earnings in the task; not recruited from the author’s community, students or customers |
| Exclusions | People with specialist training in critical transitions or early warning; participants in the pilot study |
| Task | An 80-round computer resource-management task. The stock follows a hidden model (participants are not told the model or its fold): ; participants set the effort every 4 rounds; profit per round = catch . The rules state that once the stock falls below 2.0 the fishery is closed for good |
| Common interface | Current stock, current profit and cumulative profit; identical task instructions |
| Arm A (dashboard) | In addition, a dashboard showing as line charts the rolling recovery time (converted from the lag-1 autocorrelation) and rolling variance of the last 20 rounds’ stock, with a two-minute neutral explanation (“slower recovery and larger fluctuations may mean the resource is becoming more sensitive to disturbance”); no hint of the threshold’s location or of a recommended effort |
| Arm B (control) | The common interface only |
| Blinding | Participants know their own interface but not the hypothesis or the other arm’s interface; the primary outcome is computed automatically; analysts do not know the arm labels until the analysis plan is locked |
| Primary outcome | Irreversible collapse: whether the stock falls below 2.0 (and so, by the rules, the fishery closes for good) within 80 rounds; one binary outcome per participant |
| Secondary outcomes | Cumulative profit; round of collapse; mean effort over the last 20 rounds; adjustments of effort when the dashboard rises; understanding of the system after the task (coded by raters blind to arm) |
| Analysis | Logistic regression of collapse on arm and site stratum, reported as a risk difference with 95 % confidence interval; two-sided |
There is a single primary outcome. Profit is reported as the most important secondary outcome so that the everyday cost of the warnings can be seen.
Sample size
Calculation
# Chapter 8 trial. Part 1: the simulated resource game, played by two simple rule-following
# bots (a MODEL of the hypothesis, not data about people). Part 2: sample size.
# Hidden stock dynamics per round (participants are not told the model or its fold):
# x <- x + 0.5 [x (1 - x/10) - E x^2/(x^2 + 1)] + 0.1 N(0,1); fold at E = 2.604.
# Profit per round = catch - 0.2 E. Collapse (primary outcome): x < 2.0 in any round; by the
# game's rules the fishery is then closed for good. 80 rounds; effort E reviewed every 4 rounds.
import math, random
from statistics import NormalDist
rng = random.Random(16)
def play(dashboard, rounds=80, heed=0.3):
"""A bot that nudges effort up or down by its own step, reversing when the block's profit
falls, and stops exploring once profit per round reaches its own aspiration. The dashboard
bot also looks at lag-1 autocorrelation of the last 20 stock values (with probability heed
at each review) and cuts effort by 0.3 if it exceeds 0.6."""
x, E, step = 8.9, rng.uniform(0.5, 2.0), rng.uniform(0.05, 0.3)
asp, stopped, last, hold = rng.uniform(0.6, 0.9), False, None, 0
xs, block, total = [], 0.0, 0.0
for t in range(1, rounds + 1):
catch = 0.5 * E * x * x / (x * x + 1)
x += 0.5 * (x * (1 - x / 10)) - catch + 0.1 * rng.gauss(0, 1)
xs.append(x); block += catch - 0.2 * E; total += catch - 0.2 * E
if x < 2.0: return True, total
if t % 4: continue
if not stopped and block / 4 >= asp: stopped = True
if last is not None and block < last: step = -step # profit fell: reverse
last, block = block, 0.0
if dashboard and len(xs) >= 20 and rng.random() < heed:
w = xs[-20:]; m = sum(w) / 20; v = sum((u - m) ** 2 for u in w)
a = sum((w[i] - m) * (w[i + 1] - m) for i in range(19)) / v
if a > 0.6: E, step, hold = max(0.0, E - 0.3), -abs(step), 3
if hold: hold -= 1; continue
if not stopped: E = max(0.0, E + step)
return False, total
for dash, lab in ((False, "stock and profit only"), (True, "plus recovery dashboard")):
res = [play(dash) for _ in range(2000)]
print(f"bots, {lab:24s}: collapse in {sum(c for c, _ in res) / 2000:.0%} of games;"
f" mean profit {sum(p for _, p in res) / 2000:.1f}")
def n_per_arm(p1, p2, alpha=0.05, power=0.90):
z = NormalDist().inv_cdf; pb = (p1 + p2) / 2
num = (z(1 - alpha / 2) * math.sqrt(2 * pb * (1 - pb))
+ z(power) * math.sqrt(p1 * (1 - p1) + p2 * (1 - p2)))
return math.ceil(num ** 2 / (p1 - p2) ** 2)
print("assumed collapse share: control -> dashboard analysable per arm (90 % power)")
for p1, p2 in ((0.40, 0.25), (0.40, 0.28), (0.50, 0.35), (0.30, 0.18)):
print(f" {p1:.2f} -> {p2:.2f} {n_per_arm(p1, p2)}")
n = n_per_arm(0.40, 0.25); rec = math.ceil(n / 0.9)
print(f"chosen 0.40 -> 0.25: {n} per arm; allowing 10 % incomplete sessions,")
print(f" recruit {rec} per arm, {2 * rec} in all")
hits = 0
for _ in range(4000):
a = sum(rng.random() < 0.40 for _ in range(n)); b = sum(rng.random() < 0.25 for _ in range(n))
pa, pb = a / n, b / n; pp = (a + b) / (2 * n)
hits += abs(pa - pb) / math.sqrt(pp * (1 - pp) * 2 / n) > 1.96
print(f"simulation check of power with {n} per arm: {hits / 4000:.2f}")
# bots, stock and profit only : collapse in 40% of games; mean profit 42.3
# bots, plus recovery dashboard : collapse in 7% of games; mean profit 35.3
# assumed collapse share: control -> dashboard analysable per arm (90 % power)
# 0.40 -> 0.25 203
# 0.40 -> 0.28 326
# 0.50 -> 0.35 227
# 0.30 -> 0.18 265
# chosen 0.40 -> 0.25: 203 per arm; allowing 10 % incomplete sessions,
# recruit 226 per arm, 452 in all
# simulation check of power with 203 per arm: 0.90The first part of the program is not data but a model of the hypothesis: two kinds of rule-following “bot” players. Bots that see only stock and profit collapse the resource in 40 % of games; the same bots, consulting the dashboard with probability 30 % at each review (and cutting effort by 0.3 whenever the autocorrelation exceeds 0.6), collapse it in 7 %, while their cumulative profit falls from 42.3 to 35.3. This gap is an upper bound: the bots’ response to the dashboard was designed, and the hidden model really does contain a fold. Real people may misread, ignore or over-react.
The sample size is therefore based on far more cautious assumptions: 40 % collapses in the control arm and 25 % in the dashboard arm, two-sided , 90 % power. Each arm needs 203 analysable participants; allowing for about 10 % incomplete sessions, each arm recruits 226, 452 in all; a simulation check gives a power of 0.90. If the true gap were only 12 percentage points (40 % against 28 %), each arm would need 326. These parameters are assumptions and should be checked against pilot data before the trial is run.
Decision rule
Calculation
# Decision rule for the Chapter 8 trial, fixed before any data are collected.
# diff = collapse share in the control arm (stock and profit only) minus the dashboard arm
# (plus rolling recovery time and variance); POSITIVE diff = fewer collapses with the dashboard.
# 95 % CI from the pre-specified model (logistic regression, arm + site stratum, reported as a
# risk difference). Smallest difference of interest: 0.10 (10 percentage points).
SESOI = 0.10
def verdict(diff, lo, hi, shown, profit_change):
if shown < 0.90:
return (f"diff {diff:+.2f}: NOT INTERPRETABLE - dashboard shown as specified"
f" in only {shown:.0%} of sessions")
if lo >= SESOI: v = "dashboard cuts collapses by >= 10 points"
elif lo > 0: v = "dashboard cuts collapses; >= 10 points not shown"
elif hi < 0: v = "dashboard INCREASES collapses"
elif lo > -SESOI and hi < SESOI: v = "no difference worth acting on"
else: v = "unclear: the interval is too wide to decide"
return f"diff {diff:+.2f} ({lo:+.2f} to {hi:+.2f}): {v}; profit {profit_change:+.0%}"
for case in ((0.19, 0.11, 0.27, 0.96, -0.08), (0.08, 0.01, 0.15, 0.95, -0.03),
(0.02, -0.05, 0.09, 0.94, 0.00), (-0.09, -0.17, -0.01, 0.95, 0.05),
(0.21, 0.13, 0.29, 0.80, -0.10), (0.05, -0.08, 0.18, 0.93, -0.02)):
print(verdict(*case))
print("profit and time to collapse are secondary: reported with intervals, never used to")
print(" override the primary verdict")
# diff +0.19 (+0.11 to +0.27): dashboard cuts collapses by >= 10 points; profit -8%
# diff +0.08 (+0.01 to +0.15): dashboard cuts collapses; >= 10 points not shown; profit -3%
# diff +0.02 (-0.05 to +0.09): no difference worth acting on; profit +0%
# diff -0.09 (-0.17 to -0.01): dashboard INCREASES collapses; profit +5%
# diff +0.21: NOT INTERPRETABLE - dashboard shown as specified in only 80% of sessions
# diff +0.05 (-0.08 to +0.18): unclear: the interval is too wide to decide; profit -2%
# profit and time to collapse are secondary: reported with intervals, never used to
# override the primary verdictThe example results are made up. If more than 10 % of sessions do not display the dashboard as specified, the report says “not interpretable” whatever the result. Only if the lower limit of the 95 % confidence interval of the difference in collapse rates is at least 10 percentage points does the report say “the dashboard reduced collapses by at least the smallest difference of interest”. Profit and round of collapse are reported only as estimates with intervals and are never used to override the primary verdict.
What this trial cannot show
First, this is an 80-round computer task; real resource management unfolds over years and decades and involves many stakeholders, politics and institutions; the results cannot be extrapolated directly to real fisheries, lakes or any company. Second, the hidden model does contain a fold and the noise is constant, so the dashboard’s signal is valid here; Chapter 3 showed that in real systems the same indicators often give false or missed alarms. A positive result shows only whether people can use the signal when it is valid. A natural follow-up would add a hidden model without a fold, to test whether the dashboard there misleads people into giving up profit. Third, “closed for good” is irreversibility created by the rules of the game; in the model itself, the resource can recover once the pressure falls below the lower fold. Fourth, arm A receives both extra information and an explanation, so a positive result cannot tell whether the content of the dashboard worked or simply “being reminded to watch the risk”. Fifth, most participants are not professional resource managers. Sixth, only one set of model parameters is used.
Ethics and conflicts of interest
This is a low-risk study: the task is a simulated computer game, involves no health intervention and collects no health information, and the foreseeable discomforts are frustration when the resource “collapses” and a lower payment. Even so, the trial must be reviewed and approved by an independent ethics committee; every participant gives informed consent; and the protocol and analysis plan are registered before the first participant is enrolled (Nosek et al. 2018). The instructions tell participants truthfully that this is a simplified simulation whose rules include permanent closure; the details of the hidden model are withheld, which is withholding information rather than deception, and all participants are told the purpose of the study and the model after the task. The payment rules are announced in advance; any participant may withdraw at any time without giving a reason and is paid for the part completed. Data are stored anonymously. The results will be published whether positive or negative.
Disclosure
The author uses the works of 5D Library, including this book, as the methodological basis of his community courses, the 5D Ascension Cards (Dimensional Shift Card Decks) and the Yafu AI knowledge base, and therefore has a commercial interest in the subject of this book. The book promotes no course, card deck or service; the Chapter 8 trial is designed to be run and analysed by researchers independent of the author’s business and does not recruit from the author’s community, students or customers; all the code and the study protocol are published openly for anyone to check. To limit the influence of this interest: the task software and analysis code are published before enrolment; whatever the result, the results, data and code will be published.
References
Fan, Y. (2026). Phase Transitions, Critical Slowing Down and Resilience in Complex Dynamical Systems: From Saddle-Node Bifurcations and Early-Warning Signals to Cascading Failures, Read Beside Classical Chinese Thought on Incipient Change [复杂系统相变、临界慢化与生态韧性的非线性动力学] (Version 1.0.0). 5D Library. https://doi.org/10.5281/zenodo.23770082 · Licensed under Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0).