# ------------------- the break / defect-injection table ---------------------
breaks = []
def brk(name, target, before, after, note=""):
moved = not (np.isclose(before, after, rtol=1e-12, atol=1e-15))
breaks.append((name, target, before, after, "MOVES" if moved else "-- no ---", note))
# 1. the text layer's exponent in eq. (37)
b_bad = c.N0_e15 / (25.2e16 * c.b_prime_cu_mm_per_m2 / 1e4 / 1e15)
brk("eq. (37) coefficient 25.2e15 -> 25.2e16 (the text layer's reading)",
"N0_max_rel_pct", RES["N0_max_rel_pct"], float(((b_bad - 1).abs() * 100).max()))
brk("same, propagated to the bound", "beta_max", RES["beta_max"],
RES["beta_max"] * RES["eq37_textlayer_factor"],
f"{int(RES['beta_n_above_one_textlayer'])} of {int(RES['beta_entries'])} entries would exceed "
f"unity and the paper's conclusion would reverse")
# 2. Avogadro mistyped
brk("N 6.06e23 -> 6.60e23 (digit transposition)", "chain_max_rel_pct",
RES["chain_max_rel_pct"], abs(6.60e23 * CUMM / K37 - 1) * 100)
# 3. the gas constant in the wrong units
brk("R 83.2e6 -> 8.314e7 (SI-flavoured erg value)", "chain_max_rel_pct",
RES["chain_max_rel_pct"], abs(43.75e-6 * np.sqrt(2 * np.pi * 8.314e7) - 1) * 100)
# 4. mica area
brk("mica surface 5750 -> 5075 sq.cm (transposition)", "s_inlier_max_dev_pct",
RES["s_inlier_max_dev_pct"],
float((inl.b_cu_mm / inl.b_prime_cu_mm_per_m2 * 1e4 /
inl.adsorbent.map({"mica": 5075.0, "glass": 1966.0}) - 1).abs().max() * 100))
# 5. a monolayer count swapped between gases
bad_mono = dict(MONO); bad_mono["CH4"], bad_mono["CO2"] = MONO["CO2"], MONO["CH4"]
gb = g.copy(); gb["beta_bad"] = gb.N0_e15 / gb.gas.map(bad_mono)
brk("methane/CO2 monolayer counts swapped", "beta_max", RES["beta_max"], gb.beta_bad.max())
# 6. sqrt(MT) -> MT in eq. (38)
brk("eq. (38) sqrt(MT) -> MT", "sigma_max_abs_rel_pct", RES["sigma_max_abs_rel_pct"],
float(((sg.sigma_s / (K38 * sg.a_summary * (sg.gas.map(MW) * sg.T_K)) - 1) * 100).abs().max()))
# 7. Table XXI life inverted
lv = liv.copy(); lv["sigma_liq_90K_s"] = 1.0 / lv["sigma_liq_90K_s"]
p0_bad = np.sqrt(28 * 90) / (K3 * lv.loc["N2", "sigma_liq_90K_s"])
brk("Table XXI sigma_liq inverted (a plausible misreading of 'relative life')",
"x_max_anywhere", RES["x_max_anywhere"], 34.0 / p0_bad)
# 8. the discrimination, with Freundlich given a free third parameter
def q_freu3(p, Afr, n):
return Afr * p ** n
rows_b = []
for (t, gname), grp in A.groupby(["table", "gas"]):
if len(grp) < 4:
continue
p, q = grp.p_bars.values, grp.q_obs_cu_mm.values
aL, bL, rL = fit_langmuir(p, q)
rF = rms(q_freundlich(p, 8.4, 0.417) - q) # the paper's own Freundlich, not refitted
rows_b.append(rF / rL)
brk("Freundlich NOT refitted (the paper's own 8.4 p^0.417 everywhere)",
"disc_median_ratio", RES["disc_median_ratio"], float(np.median(rows_b)),
"the contest gets EASIER for Langmuir, which is why the page refits")
# 9. Langmuir fitted on the p/q linearisation instead of on q
lin = []
for (t, gname), grp in A.groupby(["table", "gas"]):
if len(grp) < 4:
continue
p, q = grp.p_bars.values, grp.q_obs_cu_mm.values
sl, ic = np.polyfit(p, p / q, 1)
bl, al = 1.0 / sl, 1.0 / (ic / sl)
aF, nF, rF = fit_freundlich(p, q)
lin.append(rF / rms(q_eq31(p, al, bl) - q))
brk("Langmuir fitted by Langmuir's linearisation instead of on q",
"disc_median_ratio", RES["disc_median_ratio"], float(np.median(lin)),
"the transform reweights towards low pressure; this is why section 5 uses newton")
# 10. eq. (30)'s b coefficient mistyped
b30_bad = s2 * (4 * s2 - 3 * s3 + s1)
lang_bad = sp.simplify((s1 / (1 / mu + a30 + b30_bad * mu)).subs({s3: s2, s4: s2})
.subs({mu: xx / s2, s1: cc * s2}))
brk("eq. (30): sign of sigma_1 in b flipped", "sym_caseVI_minus_BET",
RES["sym_caseVI_minus_BET"],
abs(complex(sp.N(sp.simplify(lang_bad - bet).subs({cc: sp.Rational(37, 10),
xx: sp.Rational(3, 17)}))).real))
# 11. eq. (30)'s c coefficient mistyped
c30_bad = 2 * s2 * (6 * s2 * s3 - 2 * s3 * s4 + s1 * s2 - s1 * s3 - 3 * s2 ** 2)
brk("eq. (30): the 4 sigma_2^2 in c changed to 3 sigma_2^2", "sym_collapse_c_residual",
RES["sym_collapse_c_residual"],
abs(complex(sp.N(sp.simplify(c30_bad.subs({s2: s1, s3: s1, s4: s1}))
.subs({s1: sp.Rational(7, 3)}))).real))
# 12. sampling instead of root-finding the 1 % separation
brk("1 % separation sampled on a 40-point log grid, not root-found",
"x_1pct_rootfind", RES["x_1pct_rootfind"], RES["x_1pct_sampled"])
# 13. eq. (15) as printed rather than corrected
brk("eq. (15) taken as printed", "t_half_eq15_corrected",
RES["t_half_eq15_corrected"], RES["t_half_eq15_printed"],
"this IS the defect of section 6.7")
# 14. the implicit march at one-eighth the steps
t_c, th_c = marches[200]
ok_c = th_c < theta1 * (1 - 1e-9)
brk("eq. (14) marched with 200 steps instead of 1600", "eq14_vs_eq15corrected_max_abs",
RES["eq14_vs_eq15corrected_max_abs"],
float(np.abs(t_c[ok_c][1:] - t_eq15_corrected(th_c[ok_c][1:], theta1, k_rate)).max()))
# 14b. the half-coverage time read off the MARCH, at one-eighth the steps
brk("half-coverage root-found on a 200-step march instead of 1600",
"t_half_march_vs_eq16_rel", RES["t_half_march_vs_eq16_rel"],
abs(t_half_of_march(*marches[200]) / t_half_eq16 - 1),
"the metric IS a discretisation error, so coarsening dt must scale it -- and it scales by 8")
# 15. Table IV's printed b used where the two-point solution belongs
brk("Table IV: printed b = 58.3 used instead of the two-point solution",
"t4_two_route_b_rel_pct", RES["t4_two_route_b_rel_pct"],
abs(58.3 / RES["t4_b_from_bprime"] - 1) * 100)
# 16. a digit transposition in Table VII's q_obs
q7b = q7.copy(); q7b[0] = 30.0 # 33.0 -> 30.0
_, _, rL7b = fit_langmuir(p7, q7b)
_, _, rF7b = fit_freundlich(p7, q7b)
brk("Table VII top row q_obs 33.0 -> 30.0", "t7_F_over_L", RES["t7_F_over_L"], rF7b / rL7b)
# 17. one isotherm row dropped
_A17 = A.drop(A[A.table == "VIII"].index[:1])
_chk17 = pd.concat([stated, _A17.groupby(["table", "gas"]).size().rename("transcribed")], axis=1)
brk("one Bulb A row dropped from Table VIII", "n_obs_mismatches",
RES["n_obs_mismatches"], int((_chk17.printed_n_obs != _chk17.transcribed).sum()),
"the 'Number of obs.' identity of section 4, recounted")
# 18. the ordering claim under the printed beta
brk("Table XX beta 0.36 accepted rather than recomputed", "order_breaks_recomputed_beta",
RES["order_breaks_recomputed_beta"], RES["order_breaks_printed_beta"])
# 19. platinum area taken as one face
brk("platinum area 312 -> 156 sq.cm (one face)", "pt_N0_max_abs_dev",
RES["pt_N0_max_abs_dev"],
float((ptn.N0_e15 - (K37 * ptn.q_cu_mm / 156.0 / 1e15).round(2)).abs().max()))
# 20. Table XXII with the arithmetic mean where the geometric reproduces
brk("Table XXII combined with the arithmetic mean", "t22_multi_geom_max_pct",
RES["t22_multi_geom_max_pct"], RES["t22_multi_arith_max_pct"])
# 21. the molecular-volume chain with a wrong cube root
brk("monolayer chain: cube root replaced by square root", "mono_n_rel_pct",
RES["mono_n_rel_pct"],
float((1.0 / (Vm_N2 / NAV) ** (2 / 2) / float(clm.loc["monolayer_N2", "value"]) - 1) * 100))
# 22. the departure evaluated at the mean pressure instead of the maximum
dep_mean, dep_mean_90 = [], []
for _, r in rp.iterrows():
grp = A[(A.table == r.table) & (A.gas == r.gas)]
xm = grp.p_bars.mean() / r.p0_bars
dep_mean.append(((1 + r.c * xm) / ((1 - xm) * (1 + (r.c - 1) * xm)) - 1) * 100)
if r.T_K == 90:
dep_mean_90.append(dep_mean[-1])
brk("separation read at the mean pressure instead of the maximum",
"departure_max_pct", RES["departure_max_pct"], float(np.max(dep_mean)),
"monotonicity is what licenses the maximum; without it this is a sampled max")
# 23. eq. (26) assembled with theta_0 omitted
inv22_bad = sum(theta[k] for k in range(1, 6))
brk("eq. (22): the bare fraction theta_0 omitted from the inventory",
"sym_eq26_residual", RES["sym_eq26_residual"],
abs(complex(sp.N(sp.simplify(tot23 / inv22_bad - eq26_rhs)
.subs({s1: sp.Rational(7, 3), s2: sp.Rational(5, 4),
s3: sp.Rational(9, 8), s4: sp.Rational(11, 10),
s5: sp.Rational(13, 12), mu: sp.Rational(1, 7),
th1: sp.Rational(1, 5)}))).real))
# 24. the c -> infinity closed form used where finite c belongs
brk("1 % separation from the c -> infinity closed form 1/101",
"x_1pct_rootfind", RES["x_1pct_rootfind"], RES["x_1pct_closed_form"])
# 25. STRUCTURAL: rescaling every b by a common factor
gs = g.copy(); gs["beta_scaled"] = (gs.N0_e15 * 1.37) / gs.gas.map(MONO)
brk("every b' scaled by 1.37", "scatter_over_sep_min",
RES["scatter_over_sep_min"], RES["scatter_over_sep_min"],
"STRUCTURAL: the ratio is scale-free in q, so a common factor cannot move it")
# 26. STRUCTURAL: the symbolic identities under a change of dummy symbol
brk("sigma symbols renamed in the eq. (30) check", "sym_eq30_c_residual",
RES["sym_eq30_c_residual"], 0.0,
"STRUCTURAL: symbolic identity is invariant under renaming, and says so")
# 27. a digit transposition in a printed q_cal cell
d8 = A[A.table == "VIII"].copy()
qc8 = d8.q_cal_cu_mm.values.copy(); qc8[0] = 48.8 # 84.8 -> 48.8
f8 = model_for("VIII", "CH4")["eq31"]
brk("Table VIII q_cal 84.8 -> 48.8 (digit transposition)", "qcal_max_abs_cu_mm",
RES["qcal_max_abs_cu_mm"], float(np.abs(f8(d8.p_bars.values) - qc8).max()))
# 28. the b' outlier repaired instead of reported
c_rep = c.copy()
c_rep.loc[(c_rep.table == "IV"), "b_prime_cu_mm_per_m2"] = 101.4
brk("Table IV b' repaired to 101.4 instead of reported", "s_worst_dev_pct",
RES["s_worst_dev_pct"],
float((c_rep.b_cu_mm / c_rep.b_prime_cu_mm_per_m2 * 1e4 / c_rep.s_printed - 1).max() * 100))
# 29. the Table XX beta cell repaired instead of reported
bet_rep = betdf.copy(); bet_rep.loc[bet_rep.beta == 0.36, "beta"] = 0.46
brk("Table XX beta repaired to 0.46 instead of reported", "beta_max_abs_dev",
RES["beta_max_abs_dev"],
float((bet_rep.beta - bet_rep.beta_from_N0.round(2)).abs().max()))
# 30-32. Table XXI inverted, on the three places it reaches
liv_b = liv.copy()
for cc_ in ("sigma_liq_90K_s", "sigma_liq_155K_s"):
liv_b[cc_] = 1.0 / liv_b[cc_]
t22_bad = []
for name, (ads, T, lc, rc) in sets.items():
for gg in MONO:
pr = liv.loc[gg, rc]
if not np.isfinite(pr):
continue
ss = c[(c.adsorbent == ads) & (c.T_K == T) & (c.gas == gg)].sigma_s.dropna()
ss = ss[ss > 2e4] if len(ss) > 1 else ss
if len(ss) == 1:
t22_bad.append(abs(pr / (float(ss.mean()) / liv_b.loc[gg, lc]) - 1) * 100)
brk("Table XXI sigma_liq inverted", "t22_single_max_pct", RES["t22_single_max_pct"],
float(np.max(t22_bad)))
sl_b = 1.0 / liv.loc["N2", "sigma_liq_90K_s"]
p0_b = np.sqrt(28 * 90) / (K3 * sl_b)
brk("Table XXI sigma_liq inverted", "p0_N2_90K_atm", RES["p0_N2_90K_atm"], p0_b / ATM)
r7 = rp[rp.table == "VII"].iloc[0]
x_b = 34.0 / p0_b
c_b = k7.a_header * p0_b
dep_b = (1 + c_b * x_b) / ((1 - x_b) * (1 + (c_b - 1) * x_b)) - 1
brk("Table XXI sigma_liq inverted", "scatter_over_sep_t7", RES["scatter_over_sep_t7"],
float(RES["t7_rms_printed"] / abs(dep_b * q_eq31(34.0, k7.a_header, k7.b_cu_mm))))
brk("Table XXI sigma_liq inverted", "scatter_over_sep_min", RES["scatter_over_sep_min"],
float(RES["t7_rms_printed"] / abs(dep_b * q_eq31(34.0, k7.a_header, k7.b_cu_mm))),
"the STRUCTURAL row above cannot move this metric; this one can")
# 33. Freundlich's printed exponent
brk("printed q_F column checked against 8.4 p^0.42 instead of p^0.417",
"qF_column_max_dev_pct", RES["qF_column_max_dev_pct"],
float((np.abs(q_freundlich(p7, 8.4, 0.42) - d7.q_F_cu_mm.values)
/ d7.q_F_cu_mm.values).max() * 100))
# 34-35. the two nulls, under the same transposed digit
_, _, rL7c = fit_langmuir(p7, q7b)
brk("Table VII top row q_obs 33.0 -> 30.0", "t7_null_mean", RES["t7_null_mean"],
rms(q7b - q7b.mean()) / rL7c)
brk("Table VII top row q_obs 33.0 -> 30.0", "t7_null_kp", RES["t7_null_kp"],
rms((np.dot(p7, q7b) / np.dot(p7, p7)) * p7 - q7b) / rL7c)
# 36. what the graphical fit cost, if the optimum were taken on the linearisation
sl7, ic7 = np.polyfit(p7, p7 / q7, 1)
brk("Table VII optimum taken on the p/q linearisation", "t7_graphical_penalty",
RES["t7_graphical_penalty"], rms_printed / rms(q_eq31(p7, sl7 / ic7, 1 / sl7) - q7))
# 37-41. each coefficient of eq. (30), and each collapse claim, mistyped in turn
sub = {s1: sp.Rational(7, 3), s2: sp.Rational(5, 4), s3: sp.Rational(9, 8),
s4: sp.Rational(11, 10), s5: sp.Rational(13, 12), mu: sp.Rational(1, 7)}
val = lambda e: abs(complex(sp.N(sp.simplify(e).subs(sub))).real)
brk("eq. (30): a = sigma_1 - 2 sigma_2 -> sigma_1 - sigma_2", "sym_eq30_a_residual",
RES["sym_eq30_a_residual"], val(got[1] - (s1 - s2)))
brk("eq. (30): b's 4 sigma_2 -> 3 sigma_2", "sym_eq30_b_residual",
RES["sym_eq30_b_residual"], val(got[2] - s2 * (3 * s2 - 3 * s3 - s1)))
brk("eq. (30): c's 6 sigma_2 sigma_3 -> 5 sigma_2 sigma_3", "sym_eq30_c_residual",
RES["sym_eq30_c_residual"],
val(got[3] - 2 * s2 * (5 * s2 * s3 - 2 * s3 * s4 + s1 * s2 - s1 * s3 - 4 * s2 ** 2)))
brk("collapse claim 1 evaluated on b with the 3 sigma_3 dropped", "sym_collapse_b_residual",
RES["sym_collapse_b_residual"],
val(sp.simplify((s2 * (4 * s2 - s1)).subs({s2: s1, s3: s1, s4: s1}))))
brk("collapse claim 2 evaluated with sigma_4 left free", "sym_collapse2_c_residual",
RES["sym_collapse2_c_residual"], val(sp.simplify(c30.subs({s3: s2}))))
# 41b. the infinite ladder without its distinguished first layer: theta_0 = theta_1/x
# instead of theta_1/(c x), i.e. forgetting that layer 1's life is sigma_1 not sigma_2.
brk("infinite ladder built with no distinguished first layer",
"sym_ladder_infinite_minus_BET", RES["sym_ladder_infinite_minus_BET"],
abs(complex(sp.N(sp.simplify(tot_inf / (1 / xx + (inv_inf - 1 / (cc * xx))) - bet)
.subs({cc: sp.Rational(37, 10), xx: sp.Rational(3, 17)}))).real))
# 42-43. the numerical ladder
def caseVI_series_bad(x, cpar, n_layers):
th = [1.0 / (cpar * x)] + [np.prod([1.0] + [x] * (k - 1)) for k in range(1, n_layers + 1)]
return sum(k * th[k] for k in range(1, n_layers + 1)) / sum(th[1:]) # theta_0 dropped
brk("ladder: the bare fraction dropped from the inventory", "caseVI_series_vs_closed_form",
1e-13,
abs(caseVI_series_bad(xt, ct, 4000) / float(bet.subs({cc: ct, xx: xt})) - 1),
"'before' is the bound the gap is held to; the gap itself is round-off")
brk("ladder truncated at 5 layers instead of 20", "caseVI_ladder_20layer_rel",
RES["caseVI_ladder_20layer_rel"],
abs(caseVI_series(xf, cf, 5) / float(bet.subs({cc: cf, xx: xf})) - 1))
# 44. a second-order march
def march_mid(theta1_, k_, t_end, n):
dt = t_end / n; th = 0.0
for _ in range(n):
th = th + dt * k_ * (theta1_ - (th + 0.5 * dt * k_ * (theta1_ - th)))
return th
errs2 = [abs(march_mid(theta1, k_rate, 6.0 / k_rate, n)
- theta1 * (1 - np.exp(-6.0))) for n in (200, 400, 800, 1600)]
brk("eq. (14) marched with a midpoint rule instead of implicit Euler", "eq14_time_order",
RES["eq14_time_order"], -np.polyfit(np.log([200, 400, 800, 1600]), np.log(errs2), 1)[0],
"the observed order is a property of the scheme and must move when the scheme does")
# 45-47. the log-log slope, under a mistyped a
sl_bad = lambda pv: 1.0 / (1.0 + 0.15 * pv)
brk("Table VII a 0.156 -> 0.15", "slope_at_3bars", RES["slope_at_3bars"], sl_bad(p_lo))
brk("Table VII a 0.156 -> 0.15", "slope_100_factor", RES["slope_100_factor"], s_hi / sl_bad(p_hi))
brk("Table VII a 0.156 -> 0.15", "p_for_slope_020", RES["p_for_slope_020"],
brentq(lambda pv: sl_bad(pv) - s_hi, 1e-3, 500.0))
# 48-50. the 1 % point
c_half = RES["c_t7"] / 2
x_h = brentq(lambda z: (1 + c_half * z) / ((1 - z) * (1 + (c_half - 1) * z)) - 1.01,
1e-12, 0.5, xtol=1e-17, rtol=8.9e-16)
brk("c halved (sigma_1/sigma_2 from a different table)", "x_1pct_two_route_rel",
RES["x_1pct_two_route_rel"], abs(x_h / (1 / 101) - 1))
grid2 = np.logspace(-6, np.log10(0.4), 400)
dep2 = (1 + cpar7 * grid2) / ((1 - grid2) * (1 + (cpar7 - 1) * grid2)) - 1
brk("the sampling grid refined from 40 to 400 points", "x_1pct_sampling_error_pct",
RES["x_1pct_sampling_error_pct"],
abs(grid2[np.argmin(np.abs(dep2 - 0.01))] / x1 - 1) * 100)
brk("1 % separation sampled on a 40-point log grid, not root-found", "p_1pct_over_p_max",
RES["p_1pct_over_p_max"], RES["x_1pct_sampled"] * RES["p0_N2_90K_bars"] / 34.0)
# 50a-c. the sensitivity metrics
brk("Table XXI sigma_liq inverted", "x_max_90K_only", RES["x_max_90K_only"],
float(RES["x_max_90K_only"] * (K3 * liv.loc["N2", "sigma_liq_90K_s"]) ** 2))
brk("separation read at the mean pressure instead of the maximum", "departure_max_90K_only",
RES["departure_max_90K_only"], float(np.max(dep_mean_90)))
brk("every 155 K row kept rather than dropped", "scatter_over_sep_min_90K_only",
RES["scatter_over_sep_min_90K_only"], RES["scatter_over_sep_min"],
"this is the sensitivity itself: the metric IS the value with those rows dropped")
brk("Table XXI sigma_liq inverted", "x_max_155K_permanent_gases",
RES["x_max_155K_permanent_gases"],
float(RES["x_max_155K_permanent_gases"] * (K3 * liv.loc["N2", "sigma_liq_155K_s"]) ** 2))
# 50d. the free two-parameter contest, on data that ARE multilayer
brk("Table VII's q replaced by Case VI's own curve reaching p/p0 = 0.3",
"t7_BET_free_over_L", RES["t7_BET_free_over_L"], RES["t7_BET_free_over_L_multilayer"],
"the power check: the free contest CAN see multilayer adsorption when it is present")
# 50e. the p0-error margin, computed on the 90 K rows only
_t90 = rp[rp.T_K == 90].loc[rp[rp.T_K == 90].scatter_over_separation.idxmin()]
_g90 = A[(A.table == _t90.table) & (A.gas == _t90.gas)]
_k90 = con[(con.table == _t90.table) & (con.gas == _t90.gas) & (con.model == "eq31")].iloc[0]
brk("p0 margin computed on the 90 K entries only", "p0_shrink_factor_for_parity",
RES["p0_shrink_factor_for_parity"],
1.0 / brentq(lambda z: sos_at_p0_factor(z, _g90, _k90, _t90.p0_bars) - 1.0,
1e-9, 1.0, xtol=1e-17, rtol=8.9e-16))
# 51. Table IV's printed b
brk("Table IV b 58.3 -> 57.5", "t4_b_printed_dev_pct", RES["t4_b_printed_dev_pct"],
(57.5 / RES["t4_b_exact"] - 1) * 100)
# 52. the two-route fit agreement, at a loosened newton tolerance
TWO_ROUTE_SAVE = list(TWO_ROUTE); TWO_ROUTE.clear()
_amp = lambda tt: float(np.dot(q7, LANG_BASIS(p7, tt)) / np.dot(LANG_BASIS(p7, tt), LANG_BASIS(p7, tt)))
_S = lambda tt: float(np.sum((_amp(tt) * LANG_BASIS(p7, tt) - q7) ** 2))
_dS = lambda tt: (_S(tt + 1e-6 * max(abs(tt), 1)) - _S(tt - 1e-6 * max(abs(tt), 1))) / (2e-6 * max(abs(tt), 1))
_j = NumJac((1, 1))
_z = newton(lambda z: _j(lambda zz: np.array([[_dS(float(zz[0, 0]))]]), z),
np.array([[0.05]]), tol=1e-3, backward_tol=1e-3, maxfev=120)
_tn = float(np.asarray(getattr(_z, "x", _z), float).reshape(1)[0])
TWO_ROUTE.extend(TWO_ROUTE_SAVE)
brk("newton tol and backward_tol loosened to 1e-3 on one fit", "fit_two_route_max_rel",
FIT_TWO_ROUTE_MAX, abs(_tn / brentq(_dS, 1e-6, 50.0) - 1),
"'before' is the bound the gap is held to; the gap itself is round-off")
# 53-54. the bound's headroom and the count above unity
brk("methane/CO2 monolayer counts swapped", "beta_headroom", RES["beta_headroom"],
1.0 / gb.beta_bad.max())
brk("eq. (37) coefficient 25.2e15 -> 25.2e16 (the text layer's reading)",
"beta_n_above_one", RES["beta_n_above_one"], RES["beta_n_above_one_textlayer"])
bt = pd.DataFrame(breaks, columns=["injected defect", "metric", "before", "after",
"effect", "note"])
pd.set_option("display.max_colwidth", 78)
print(bt.to_string(index=False, float_format=lambda v: f"{v:.6g}"))
keep("break_rows", len(bt))
keep("break_rows_moving", int((bt.effect == "MOVES").sum()))
n_still = int(RES["break_rows"]) - int(RES["break_rows_moving"])
print(f"\n{int(RES['break_rows_moving'])} of {int(RES['break_rows'])} injected defects move "
f"their metric. The {n_still} that do not are labelled STRUCTURAL above, are the ONLY "
f"rows so labelled, and are cited as evidence for nothing: a scale-free ratio cannot "
f"notice a common factor, and a symbolic identity cannot notice a renaming. Both metrics "
f"they name are separately covered by rows that DO move them.")