A in the method (extrapolate='steps', default 'none' = published bit for bit): NOT accepted into the proposed rule by the declared stop rule - X2 fails. Proposed rule without -> with A: APS 2806 -> 2967 (X2 FAIL), CHA 3220 -> 2925 (X1 pass), flat fun3-6 1823 -> 1524 (X4 pass), guarantee intact, 0 violations in 39 family cells (X3 pass). The per-problem edge over Chandrupatla on APS is lost (87/24/42, p=9e-5 -> 42/62/49, p=0.53).
X5: dissimilar+slope with A reaches 2859+2545 = 5404 over both sets, the best total this line has measured (was 5511; Chandrupatla 5228) - but again by trading the canonical set (2726 -> 2859) for the wide brackets (2785 -> 2545). The one-step replay (APS no harm) did not carry over: its bracket-halving metric rewards overshooting the root, which on a simple root costs superlinear convergence in the full run - the declared one-step limitation, now measured. Among the nine functions A helps only the tent (6026 -> 5892) and slightly ratio_soft. Also: pin 'strictly fewer evaluations on every flat problem' was wrong for 6(x-2)^5 on [0,10] - both runs are budget-bound (90 of the guaranteed 91); corrected to the measured claim. verify_article 286/286, 836 tests pass twice. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,100 @@
|
|||||||
|
"""Verdicts X1-X5 of PREREG_extrapolate_in_method_2026-09-21.md from sweep_extrap.json.
|
||||||
|
|
||||||
|
python -u analyze_extrap.py
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
|
||||||
|
FLAT = ("fun3", "fun4", "fun5", "fun6")
|
||||||
|
FAMILY = ("project", "safe", "credit")
|
||||||
|
|
||||||
|
|
||||||
|
def load(p):
|
||||||
|
with open(p, encoding="utf8") as fh:
|
||||||
|
return json.load(fh)
|
||||||
|
|
||||||
|
|
||||||
|
def key(r):
|
||||||
|
s = r["spec"]
|
||||||
|
return (s[1], s[2], s[3], s[5], s[6], s[7])
|
||||||
|
|
||||||
|
|
||||||
|
def fam_sum(r, fams):
|
||||||
|
return sum((n if ok else 200) for pid, (n, ok) in r["CHA"]["per"].items()
|
||||||
|
if pid.split(".")[0] in fams)
|
||||||
|
|
||||||
|
|
||||||
|
def sign_p(w, l):
|
||||||
|
k, n = min(w, l), w + l
|
||||||
|
return min(1.0, 2.0 * sum(math.comb(n, i) for i in range(k + 1)) / 2.0 ** n) if n else 1.0
|
||||||
|
|
||||||
|
|
||||||
|
def h2h(a, b, s):
|
||||||
|
w = t = l = 0
|
||||||
|
for pid, (na, oka) in a[s]["per"].items():
|
||||||
|
nb, okb = b[s]["per"][pid]
|
||||||
|
if oka and okb:
|
||||||
|
w += na < nb
|
||||||
|
l += na > nb
|
||||||
|
t += na == nb
|
||||||
|
return w, t, l
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
rows = load("sweep_extrap.json")
|
||||||
|
full = {r["label"]: r for r in load("sweep_full.json")}
|
||||||
|
cha = full["chandrupatla"]
|
||||||
|
pairs = {}
|
||||||
|
for r in rows:
|
||||||
|
pairs.setdefault(key(r), {})[r["spec"][9]] = r
|
||||||
|
readings = {("none", "recent", "distance"): "plain", ("slope", "recent", "distance"): "slope",
|
||||||
|
("slope", "shape", "distance"): "shape+slope",
|
||||||
|
("slope", "recent", "dissimilar"): "dissim+slope",
|
||||||
|
("defect", "recent", "distance"): "defect"}
|
||||||
|
print("tent, three-point, pairs (without A -> with A): APS | CHA | both | flat fun3-6 sum")
|
||||||
|
for (sim, sc, gu, pre, pts, dirn), p in sorted(pairs.items()):
|
||||||
|
if sim != "ratio" or gu not in FAMILY + ("none",):
|
||||||
|
continue
|
||||||
|
a, b = p["none"], p["steps"]
|
||||||
|
rd = readings[(pre, pts, dirn)]
|
||||||
|
print(f" {rd:<13} {gu:<8} {a['APS']['total']:5d}->{b['APS']['total']:<5d} "
|
||||||
|
f"{a['CHA']['total']:5d}->{b['CHA']['total']:<5d} "
|
||||||
|
f"{a['APS']['total'] + a['CHA']['total']:5d}->"
|
||||||
|
f"{b['APS']['total'] + b['CHA']['total']:<5d} "
|
||||||
|
f"{fam_sum(a, FLAT):4d}->{fam_sum(b, FLAT):<4d} "
|
||||||
|
f"solved {b['APS']['solved'] + b['CHA']['solved']} over {b['APS']['over'] + b['CHA']['over']}")
|
||||||
|
prop = pairs[("ratio", "derived3", "project", "none", "recent", "distance")]
|
||||||
|
a, b = prop["none"], prop["steps"]
|
||||||
|
print("\nVERDICTS for the proposed rule (tent, 3-point, projection)")
|
||||||
|
print(f" X1 CHA < 3220: {b['CHA']['total']} -> {'PASS' if b['CHA']['total'] < 3220 else 'FAIL'}")
|
||||||
|
print(f" X2 APS <= 2806: {b['APS']['total']} -> {'PASS' if b['APS']['total'] <= 2806 else 'FAIL'}")
|
||||||
|
fam = [p["steps"] for k, p in pairs.items() if k[2] in FAMILY]
|
||||||
|
viol = sum(r["APS"]["over"] + r["CHA"]["over"] for r in fam)
|
||||||
|
low = [r["label"] for r in fam if r["APS"]["solved"] + r["CHA"]["solved"] < 193]
|
||||||
|
print(f" X3 guarantee family with A: {viol} violations over {len(fam)} cells; "
|
||||||
|
f"cells solving < 193: {len(low)} -> {'PASS' if viol == 0 and not low else 'FAIL'}")
|
||||||
|
fa, fb = fam_sum(a, FLAT), fam_sum(b, FLAT)
|
||||||
|
print(f" X4 flat fun3-6 sum: {fa} -> {fb} -> {'PASS' if fb < fa else 'FAIL'}")
|
||||||
|
best = min((p["steps"] for p in pairs.values()
|
||||||
|
if p["steps"]["spec"][3] in FAMILY
|
||||||
|
and p["steps"]["APS"]["solved"] + p["steps"]["CHA"]["solved"] >= 193),
|
||||||
|
key=lambda r: r["APS"]["total"] + r["CHA"]["total"])
|
||||||
|
print(f" X5 best pair with A over both sets: {best['label']} "
|
||||||
|
f"{best['APS']['total']}+{best['CHA']['total']}="
|
||||||
|
f"{best['APS']['total'] + best['CHA']['total']} (reference 5511, Chandrupatla 5228)")
|
||||||
|
for s in ("APS", "CHA"):
|
||||||
|
w, t, l = h2h(b, cha, s)
|
||||||
|
print(f" proposed + A vs Chandrupatla, {s}: {w}/{t}/{l} sign p={sign_p(w, l):.2g} "
|
||||||
|
f"totals {b[s]['total']} vs {cha[s]['total']}")
|
||||||
|
print("\nnine functions, three-point, projection, no readings (without A -> with A), both sets:")
|
||||||
|
for (sim, sc, gu, pre, pts, dirn), p in sorted(pairs.items()):
|
||||||
|
if gu == "project" and pre == "none" and pts == "recent" and dirn == "distance":
|
||||||
|
a2, b2 = p["none"], p["steps"]
|
||||||
|
print(f" {sim:<11} {a2['APS']['total'] + a2['CHA']['total']:6d} -> "
|
||||||
|
f"{b2['APS']['total'] + b2['CHA']['total']:6d}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
tent, three-point, pairs (without A -> with A): APS | CHA | both | flat fun3-6 sum
|
||||||
|
defect credit 3545->4134 2949->2926 6494->7060 1044->993 solved 193 over 0
|
||||||
|
plain credit 3568->4130 3020->2986 6588->7116 1044->988 solved 193 over 0
|
||||||
|
dissim+slope credit 3857->4223 3002->2947 6859->7170 1043->1002 solved 193 over 0
|
||||||
|
slope credit 3507->4121 2997->3007 6504->7128 1051->1007 solved 193 over 0
|
||||||
|
shape+slope credit 3755->4169 3021->3059 6776->7228 1064->1064 solved 193 over 0
|
||||||
|
defect none 2523->3180 4198->3508 6721->6688 2806->2081 solved 188 over 1
|
||||||
|
plain none 2806->2967 4104->3471 6910->6438 2707->2070 solved 188 over 0
|
||||||
|
dissim+slope none 2726->2859 2785->2545 5511->5404 1376->1157 solved 193 over 0
|
||||||
|
slope none 2848->2971 3075->2735 5923->5706 1710->1331 solved 193 over 0
|
||||||
|
shape+slope none 2675->2930 5443->5452 8118->8382 4000->4000 solved 173 over 0
|
||||||
|
defect project 2523->3180 3210->2954 5733->6134 1818->1527 solved 193 over 0
|
||||||
|
plain project 2806->2967 3220->2925 6026->5892 1823->1524 solved 193 over 0
|
||||||
|
dissim+slope project 2726->2859 2785->2545 5511->5404 1376->1157 solved 193 over 0
|
||||||
|
slope project 2848->2971 3074->2735 5922->5706 1709->1331 solved 193 over 0
|
||||||
|
shape+slope project 2675->2930 3498->3506 6173->6436 2055->2054 solved 193 over 0
|
||||||
|
defect safe 3131->3207 3368->3386 6499->6593 1943->1929 solved 193 over 0
|
||||||
|
plain safe 3036->3171 3363->3389 6399->6560 1950->1931 solved 193 over 0
|
||||||
|
dissim+slope safe 3123->3170 2683->2710 5806->5880 1228->1248 solved 193 over 0
|
||||||
|
slope safe 3020->3187 2903->2895 5923->6082 1484->1469 solved 193 over 0
|
||||||
|
shape+slope safe 2527->2986 3108->3223 5635->6209 1629->1687 solved 193 over 0
|
||||||
|
|
||||||
|
VERDICTS for the proposed rule (tent, 3-point, projection)
|
||||||
|
X1 CHA < 3220: 2925 -> PASS
|
||||||
|
X2 APS <= 2806: 2967 -> FAIL
|
||||||
|
X3 guarantee family with A: 0 violations over 39 cells; cells solving < 193: 0 -> PASS
|
||||||
|
X4 flat fun3-6 sum: 1823 -> 1524 -> PASS
|
||||||
|
X5 best pair with A over both sets: pure+dissim+ext+slope.ratio.derived3.project 2859+2545=5404 (reference 5511, Chandrupatla 5228)
|
||||||
|
proposed + A vs Chandrupatla, APS: 42/62/49 sign p=0.53 totals 2967 vs 2787
|
||||||
|
proposed + A vs Chandrupatla, CHA: 11/1/28 sign p=0.0095 totals 2925 vs 2441
|
||||||
|
|
||||||
|
nine functions, three-point, projection, no readings (without A -> with A), both sets:
|
||||||
|
cbrt 17993 -> 18181
|
||||||
|
cosine 19784 -> 19753
|
||||||
|
exp_abs 6652 -> 6869
|
||||||
|
exp_sq 19786 -> 19780
|
||||||
|
rat_sq 19793 -> 19796
|
||||||
|
ratio 6026 -> 5892
|
||||||
|
ratio_soft 9352 -> 9285
|
||||||
|
rational 6823 -> 7483
|
||||||
|
sqrt 13381 -> 13633
|
||||||
+24
-1
@@ -542,7 +542,8 @@ def _local_order(x2, f2, x1, f1, x0, f0):
|
|||||||
def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
|
def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
|
||||||
guard="step", stats=None, budget_factor=2.0,
|
guard="step", stats=None, budget_factor=2.0,
|
||||||
budget_extra=None, endowment=1.0, space="x", prestep="none",
|
budget_extra=None, endowment=1.0, space="x", prestep="none",
|
||||||
points="recent", direction="distance", exponent="none"):
|
points="recent", direction="distance", exponent="none",
|
||||||
|
extrapolate="none"):
|
||||||
"""The pure similarity root finder.
|
"""The pure similarity root finder.
|
||||||
|
|
||||||
similarity : key into PURE_SIMS
|
similarity : key into PURE_SIMS
|
||||||
@@ -594,6 +595,8 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
|
|||||||
if scale not in ("derived", "derived3", "derived_rho", "derived3_rho",
|
if scale not in ("derived", "derived3", "derived_rho", "derived3_rho",
|
||||||
"derived3_m", "derived3_cap", "start", "bracket", "far"):
|
"derived3_m", "derived3_cap", "start", "bracket", "far"):
|
||||||
raise ValueError(f"unknown scale {scale!r}")
|
raise ValueError(f"unknown scale {scale!r}")
|
||||||
|
if extrapolate not in ("none", "steps"):
|
||||||
|
raise ValueError(f"unknown extrapolate {extrapolate!r}")
|
||||||
if exponent not in EXPONENTS:
|
if exponent not in EXPONENTS:
|
||||||
raise ValueError(f"unknown exponent {exponent!r}")
|
raise ValueError(f"unknown exponent {exponent!r}")
|
||||||
if exponent == "order" and scale not in ("derived3", "derived3_cap", "bracket", "far"):
|
if exponent == "order" and scale not in ("derived3", "derived3_cap", "bracket", "far"):
|
||||||
@@ -863,6 +866,26 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
|
|||||||
step = _log_space_step(S, x, fx, far, slope, c_near, xtol)
|
step = _log_space_step(S, x, fx, far, slope, c_near, xtol)
|
||||||
if step is None:
|
if step is None:
|
||||||
step = (1.0 - S(z_now)) * (far - x)
|
step = (1.0 - S(z_now)) * (far - x)
|
||||||
|
if extrapolate == "steps":
|
||||||
|
# PREREG 2026-09-21 (A in the method). The similarity of THIS
|
||||||
|
# step to the previous displacement: two successive moves the
|
||||||
|
# same way, of similar length, mean the iterate creeps along a
|
||||||
|
# geometric series whose remainder is step*S/(1-S) - so the
|
||||||
|
# step is lengthened by 1/(1-S). On x^m under a Newton step
|
||||||
|
# S = 1 - 1/m and the factor is m, which lands on the root.
|
||||||
|
# At a simple root successive steps shrink superlinearly, S ->
|
||||||
|
# 0 and the step is untouched. Capped at the far end, the same
|
||||||
|
# bound the step itself has; no constant.
|
||||||
|
d_prev = x - x_prev
|
||||||
|
if (step != 0.0 and d_prev != 0.0
|
||||||
|
and (step > 0.0) == (d_prev > 0.0)):
|
||||||
|
s_steps = (min(abs(step), abs(d_prev))
|
||||||
|
/ max(abs(step), abs(d_prev)))
|
||||||
|
if s_steps < 1.0:
|
||||||
|
longer = step / (1.0 - s_steps)
|
||||||
|
reach = abs(far - x)
|
||||||
|
step = (longer if abs(longer) <= reach
|
||||||
|
else math.copysign(reach, step))
|
||||||
if stats is not None and "trace" in stats:
|
if stats is not None and "trace" in stats:
|
||||||
# DIAGNOSTIC HOOK, behaviour-neutral: records the state each
|
# DIAGNOSTIC HOOK, behaviour-neutral: records the state each
|
||||||
# step was taken from, so another model can be evaluated on the
|
# step was taken from, so another model can be evaluated on the
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+49
-8
@@ -85,10 +85,10 @@ def itp_oracle(f, a, b, xtol, maxfev):
|
|||||||
return best if best is not None else (0.5 * (a + b), maxfev, False)
|
return best if best is not None else (0.5 * (a + b), maxfev, False)
|
||||||
|
|
||||||
|
|
||||||
# Competitors are taken at the better of SciPy's implementation and ours,
|
# Competitors are the LIBRARY wherever SciPy ships the method (since 2026-09-21):
|
||||||
# problem by problem, wherever SciPy ships the method. Ours turned out to be
|
# Brent = brentq, Chandrupatla = elementwise.find_root. Until then they were the
|
||||||
# the cheaper of the two - 6991 against brentq's 7004, 5209 against
|
# per-problem better of the library and ours, which let a tolerance slip in our
|
||||||
# _chandrupatla's 5228 - so quoting the library alone would have flattered us.
|
# Chandrupatla (a bracket twice as wide) leak into the reported numbers.
|
||||||
BASELINES = {
|
BASELINES = {
|
||||||
"bisection": bisection, "regula_falsi": regula_falsi,
|
"bisection": bisection, "regula_falsi": regula_falsi,
|
||||||
"illinois": illinois, "pegasus": pegasus,
|
"illinois": illinois, "pegasus": pegasus,
|
||||||
@@ -115,11 +115,13 @@ def make_method(spec):
|
|||||||
points = spec[6] if len(spec) > 6 else "recent"
|
points = spec[6] if len(spec) > 6 else "recent"
|
||||||
direction = spec[7] if len(spec) > 7 else "distance"
|
direction = spec[7] if len(spec) > 7 else "distance"
|
||||||
exponent = spec[8] if len(spec) > 8 else "none"
|
exponent = spec[8] if len(spec) > 8 else "none"
|
||||||
|
extrapolate = spec[9] if len(spec) > 9 else "none"
|
||||||
|
|
||||||
def m(f, a, b, xtol, maxfev):
|
def m(f, a, b, xtol, maxfev):
|
||||||
return pure_root(f, a, b, xtol, maxfev, similarity=sim,
|
return pure_root(f, a, b, xtol, maxfev, similarity=sim,
|
||||||
scale=scale, guard=guard, space=space, prestep=prestep,
|
scale=scale, guard=guard, space=space, prestep=prestep,
|
||||||
points=points, direction=direction, exponent=exponent)
|
points=points, direction=direction, exponent=exponent,
|
||||||
|
extrapolate=extrapolate)
|
||||||
return m
|
return m
|
||||||
|
|
||||||
|
|
||||||
@@ -133,6 +135,9 @@ def label(spec):
|
|||||||
tag = f"{tag}+shape"
|
tag = f"{tag}+shape"
|
||||||
if len(spec) > 7 and spec[7] != "distance":
|
if len(spec) > 7 and spec[7] != "distance":
|
||||||
tag = f"{tag}+dissim"
|
tag = f"{tag}+dissim"
|
||||||
|
if len(spec) > 9 and spec[9] != "none":
|
||||||
|
# the pair (off, on) must be distinguishable in a results file
|
||||||
|
tag = f"{tag}+ext"
|
||||||
if len(spec) > 8 and spec[8] != "none":
|
if len(spec) > 8 and spec[8] != "none":
|
||||||
# the exponent must be visible in the label: (far, none) is false position and
|
# the exponent must be visible in the label: (far, none) is false position and
|
||||||
# (far, order) is the hypothesis, and a results file that cannot tell them apart
|
# (far, order) is the hypothesis, and a results file that cannot tell them apart
|
||||||
@@ -205,6 +210,39 @@ def build_grid_log(smoke):
|
|||||||
for sim in sims for sc in ("derived", "derived3") for gu in guards]
|
for sim in sims for sc in ("derived", "derived3") for gu in guards]
|
||||||
|
|
||||||
|
|
||||||
|
def _preflight_extrap_mode():
|
||||||
|
"""Fail LOUDLY if extrapolate is not deployed (see _preflight_log_mode)."""
|
||||||
|
x, _n, ok = pure_root(lambda t: t - 1.0, 0.0, 3.0, 1e-10, 100, similarity="ratio",
|
||||||
|
scale="derived3", guard="project", extrapolate="steps")
|
||||||
|
if not (ok and abs(x - 1.0) < 1e-8):
|
||||||
|
raise SystemExit("extrapolate preflight FAILED: refusing to report zeros")
|
||||||
|
|
||||||
|
|
||||||
|
def build_grid_extrap(smoke):
|
||||||
|
"""PREREG 2026-09-21: step extrapolation (A) inside the method, every cell as a pair
|
||||||
|
(none, steps). Tent with every reading and every guard; all nine functions without
|
||||||
|
readings, so the law across the grid is visible with and without A."""
|
||||||
|
_preflight_extrap_mode()
|
||||||
|
guards = ("project", "credit") if smoke else GUARD_MODES
|
||||||
|
readings = [("none", "recent", "distance")] if smoke else [
|
||||||
|
("none", "recent", "distance"), ("slope", "recent", "distance"),
|
||||||
|
("slope", "shape", "distance"), ("slope", "recent", "dissimilar"),
|
||||||
|
("defect", "recent", "distance")]
|
||||||
|
specs = []
|
||||||
|
for gu in guards:
|
||||||
|
for pre, pts, dirn in readings:
|
||||||
|
for ex in ("none", "steps"):
|
||||||
|
specs.append(("pure", "ratio", "derived3", gu, "x", pre, pts, dirn,
|
||||||
|
"none", ex))
|
||||||
|
sims = ["sqrt"] if smoke else [s for s in sorted(PURE_SIMS) if s != "ratio"]
|
||||||
|
for sim in sims:
|
||||||
|
for gu in guards:
|
||||||
|
for ex in ("none", "steps"):
|
||||||
|
specs.append(("pure", sim, "derived3", gu, "x", "none", "recent",
|
||||||
|
"distance", "none", ex))
|
||||||
|
return specs
|
||||||
|
|
||||||
|
|
||||||
def _preflight_decision_mode():
|
def _preflight_decision_mode():
|
||||||
"""Fail LOUDLY if the decision readings are not deployed (see _preflight_log_mode)."""
|
"""Fail LOUDLY if the decision readings are not deployed (see _preflight_log_mode)."""
|
||||||
for md in ("band", "defect", "simpair"):
|
for md in ("band", "defect", "simpair"):
|
||||||
@@ -335,10 +373,12 @@ def main():
|
|||||||
capmode = "--cap" in sys.argv # scale selected between the two computed ones
|
capmode = "--cap" in sys.argv # scale selected between the two computed ones
|
||||||
ordermode = "--order" in sys.argv # slot 1: the similarity read BY PLACE
|
ordermode = "--order" in sys.argv # slot 1: the similarity read BY PLACE
|
||||||
decmode = "--decision" in sys.argv # slot 2: the reading in the DECISION place
|
decmode = "--decision" in sys.argv # slot 2: the reading in the DECISION place
|
||||||
|
extmode = "--extrap" in sys.argv # A in the method: step extrapolation
|
||||||
combomode = "--combo" in sys.argv # modes A and B together
|
combomode = "--combo" in sys.argv # modes A and B together
|
||||||
pointsmode = "--points" in sys.argv # mode A: triple by shape
|
pointsmode = "--points" in sys.argv # mode A: triple by shape
|
||||||
dirmode = "--direction" in sys.argv # mode B: far end by dissimilarity
|
dirmode = "--direction" in sys.argv # mode B: far end by dissimilarity
|
||||||
specs = (build_grid_decision(smoke) if decmode else
|
specs = (build_grid_extrap(smoke) if extmode else
|
||||||
|
build_grid_decision(smoke) if decmode else
|
||||||
build_grid_order(smoke) if ordermode else
|
build_grid_order(smoke) if ordermode else
|
||||||
build_grid_cap(smoke) if capmode else
|
build_grid_cap(smoke) if capmode else
|
||||||
build_grid_combo(smoke) if combomode else
|
build_grid_combo(smoke) if combomode else
|
||||||
@@ -362,8 +402,9 @@ def main():
|
|||||||
|
|
||||||
# the published sweep_full.json is what verify_article.py checks the paper
|
# the published sweep_full.json is what verify_article.py checks the paper
|
||||||
# against, so the log grid never writes into it
|
# against, so the log grid never writes into it
|
||||||
if decmode or ordermode or capmode or combomode or pointsmode or dirmode:
|
if extmode or decmode or ordermode or capmode or combomode or pointsmode or dirmode:
|
||||||
base = ("sweep_decision" if decmode else
|
base = ("sweep_extrap" if extmode else
|
||||||
|
"sweep_decision" if decmode else
|
||||||
"sweep_order" if ordermode else
|
"sweep_order" if ordermode else
|
||||||
"sweep_cap" if capmode else
|
"sweep_cap" if capmode else
|
||||||
"sweep_combo" if combomode else
|
"sweep_combo" if combomode else
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
"""Pins for PREREG_extrapolate_in_method_2026-09-21.md: step extrapolation (A)."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from simroot.pure import pure_root
|
||||||
|
|
||||||
|
SMOOTH = (
|
||||||
|
(lambda t: t ** 3 - 2.0 * t - 5.0, 1.0, 4.0),
|
||||||
|
(lambda t: math.exp(t) - 2.0, 0.0, 3.0),
|
||||||
|
(lambda t: math.sin(t) - 0.5, 0.0, 1.5),
|
||||||
|
)
|
||||||
|
FLAT = (
|
||||||
|
(lambda t: 6.0 * (t - 2.0) ** 5, 0.0, 10.0),
|
||||||
|
(lambda t: t ** 9, -1.0, 2.0),
|
||||||
|
(lambda t: (t - 3.0) ** 3, 0.0, 10.0),
|
||||||
|
)
|
||||||
|
XTOL, MAXFEV = 1e-12, 200
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("f,a,b", SMOOTH + FLAT)
|
||||||
|
@pytest.mark.parametrize("guard", ("project", "safe", "credit"))
|
||||||
|
def test_default_is_the_published_method_bit_for_bit(f, a, b, guard):
|
||||||
|
ref = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio", scale="derived3", guard=guard)
|
||||||
|
off = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio", scale="derived3", guard=guard,
|
||||||
|
extrapolate="none")
|
||||||
|
assert ref == off
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("f,a,b", SMOOTH + FLAT)
|
||||||
|
@pytest.mark.parametrize("guard", ("project", "credit"))
|
||||||
|
def test_the_guarantee_survives_extrapolation(f, a, b, guard):
|
||||||
|
x, n, ok = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio", scale="derived3",
|
||||||
|
guard=guard, extrapolate="steps")
|
||||||
|
assert ok and min(a, b) <= x <= max(a, b)
|
||||||
|
half = math.ceil(math.log2(abs(b - a) / XTOL))
|
||||||
|
bound = 2 * half + 3 if guard == "project" else half + 4
|
||||||
|
assert n <= bound, (n, bound)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("f,a,b", FLAT[1:])
|
||||||
|
def test_extrapolation_breaks_the_flat_cycle(f, a, b):
|
||||||
|
"""The mechanism the pre-registration rests on: on a pure power the similar successive
|
||||||
|
undershoots are replaced by a longer step, and the run gets cheaper."""
|
||||||
|
_x, n_off, _ = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio", scale="derived3",
|
||||||
|
guard="project")
|
||||||
|
_x, n_on, _ = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio", scale="derived3",
|
||||||
|
guard="project", extrapolate="steps")
|
||||||
|
assert n_on < n_off, (n_on, n_off)
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_a_budget_bound_run_extrapolation_changes_the_path_not_the_count():
|
||||||
|
"""MEASURED, pinned as measured: on 6(x-2)^5 over [0, 10] both runs cost 90 evaluations
|
||||||
|
against a projection guarantee of 2*44 + 3 = 91 - the budget schedule, not the step,
|
||||||
|
decides when the run ends. The paths differ from the fourth evaluation on (87
|
||||||
|
positions), so the mode is active; its gain is absorbed by the safeguard here. The
|
||||||
|
expectation written first (strictly fewer evaluations) was wrong for this problem."""
|
||||||
|
f, a, b = FLAT[0]
|
||||||
|
ev = {}
|
||||||
|
for mode in ("none", "steps"):
|
||||||
|
seen = []
|
||||||
|
|
||||||
|
def g(t, _s=seen):
|
||||||
|
_s.append(t)
|
||||||
|
return f(t)
|
||||||
|
_x, n, ok = pure_root(g, a, b, XTOL, MAXFEV, similarity="ratio", scale="derived3",
|
||||||
|
guard="project", extrapolate=mode)
|
||||||
|
assert ok
|
||||||
|
ev[mode] = (n, seen)
|
||||||
|
assert ev["steps"][0] <= ev["none"][0]
|
||||||
|
assert ev["steps"][1] != ev["none"][1]
|
||||||
|
|
||||||
|
|
||||||
|
def test_unknown_mode_is_refused():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
pure_root(lambda t: t, -1.0, 1.0, XTOL, MAXFEV, extrapolate="aitken")
|
||||||
Reference in New Issue
Block a user