150 lines
6.1 KiB
Python
150 lines
6.1 KiB
Python
"""Why Chandrupatla wins the probe — measured, and stated in similarity terms.
|
|
|
|
ANALYSIS ONLY. The traced copy below mirrors `simroot.baselines.chandrupatla` so its
|
|
decisions can be counted; it is NEVER used for a ranking number, and its fidelity is
|
|
asserted against the original (identical evaluation count on all 45 problems). If that
|
|
assert fails, the mirror has drifted and nothing here may be believed.
|
|
|
|
The reading being tested:
|
|
|
|
Chandrupatla = similarity(configuration, well-conditioned configuration) used as a
|
|
GATE on a model step, with bisection as the refusal branch.
|
|
ours = similarity(residual, 0) used as a SCALE for the step, with the
|
|
safeguard bolted on afterwards.
|
|
|
|
* the decision variable is t in (0,1): the probe is a FRACTION of the bracket, so the
|
|
method is scale-free by construction (what the logarithmic attempt tried to buy);
|
|
* the gate is written in two RATIOS, xi = (x1-x2)/(x3-x2) and phi = (f1-f2)/(f3-f2),
|
|
both invariant under affine rescaling of x and of f — relations, not magnitudes;
|
|
* for a LINEAR f, phi = xi exactly, and that point lies strictly inside the band
|
|
1 - sqrt(1-xi) < phi < sqrt(xi), so the band is a neighbourhood of the linear
|
|
configuration: the model is trusted exactly where the data still look linear enough.
|
|
|
|
Metric for "probe quality", parameter-free and comparable across methods:
|
|
halvings per evaluation = log2(w0 / xtol) / nfev
|
|
Bisection is 1.0 by definition; more is better.
|
|
|
|
python -u explain_chandrupatla.py
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
|
|
from simroot.baselines import Counter, chandrupatla
|
|
from simroot.pure import pure_root
|
|
from sweep_full import ACC, MAXFEV, XTOL, load_problems
|
|
|
|
|
|
def chandrupatla_traced(f, a, b, xtol, maxfev):
|
|
"""Mirror of the published routine plus counters. Returns (x, nfev, ok, trace)."""
|
|
g = Counter(f, maxfev)
|
|
trace = {"iqi": 0, "bisect": 0, "gate_rejected": 0, "no_third_point": 0}
|
|
x1, x2 = b, a
|
|
f1 = f2 = 0.0
|
|
try:
|
|
f1, f2 = g(x1), g(x2)
|
|
if f1 == 0.0:
|
|
return x1, g.n, True, trace
|
|
if f2 == 0.0:
|
|
return x2, g.n, True, trace
|
|
x3, f3 = x2, f2
|
|
t = 0.5
|
|
while True:
|
|
x = x1 + t * (x2 - x1)
|
|
fx = g(x)
|
|
if (fx > 0.0) == (f1 > 0.0):
|
|
x3, f3 = x1, f1
|
|
x1, f1 = x, fx
|
|
else:
|
|
x3, f3 = x2, f2
|
|
x2, f2 = x1, f1
|
|
x1, f1 = x, fx
|
|
xm, fm = (x1, f1) if abs(f1) < abs(f2) else (x2, f2)
|
|
if fm == 0.0:
|
|
return xm, g.n, True, trace
|
|
tol = 2.0 * 2.22e-16 * abs(xm) + xtol
|
|
span = abs(x2 - x1)
|
|
if span == 0.0:
|
|
return xm, g.n, True, trace
|
|
tl = tol / span
|
|
if tl > 0.5:
|
|
return xm, g.n, True, trace
|
|
t = 0.5
|
|
took_iqi = False
|
|
if x3 != x2 and f3 != f2:
|
|
xi = (x1 - x2) / (x3 - x2)
|
|
ph = (f1 - f2) / (f3 - f2)
|
|
if 0.0 < xi < 1.0 and 1.0 - math.sqrt(1.0 - xi) < ph < math.sqrt(xi):
|
|
d1, d2, d3 = f1 - f2, f1 - f3, f2 - f3
|
|
if d1 != 0.0 and d2 != 0.0 and d3 != 0.0:
|
|
t = (f1 / d1) * (f3 / (-d3)) + \
|
|
((x3 - x1) / (x2 - x1)) * (f1 / d2) * (f2 / d3)
|
|
took_iqi = True
|
|
else:
|
|
trace["gate_rejected"] += 1
|
|
else:
|
|
trace["no_third_point"] += 1
|
|
if not math.isfinite(t):
|
|
t = 0.5
|
|
took_iqi = False
|
|
if t < tl:
|
|
t = tl
|
|
elif t > 1.0 - tl:
|
|
t = 1.0 - tl
|
|
trace["iqi" if took_iqi else "bisect"] += 1
|
|
except Exception:
|
|
return float("nan"), g.n, False, trace
|
|
|
|
|
|
def main():
|
|
probs = [p for p in load_problems() if p[0] == "CHA"]
|
|
print(f"CHA problems: {len(probs)} metric: halvings per evaluation "
|
|
f"= log2(w0/xtol)/nfev (bisection = 1.0)\n")
|
|
print(f"{'problem':<16} {'CH nfev':>8} {'CH h/ev':>8} {'gate ok%':>9} "
|
|
f"{'ours nfev':>10} {'ours h/ev':>10} {'forced%':>8}")
|
|
|
|
agg = {"ch_n": 0, "our_n": 0, "ch_h": [], "our_h": [], "gate": [], "forced": []}
|
|
drift = 0
|
|
for _s, pid, f, a, b, root in probs:
|
|
w0 = abs(b - a)
|
|
decades = math.log2(w0 / XTOL)
|
|
|
|
x_ref, n_ref, ok_ref = chandrupatla(f, a, b, XTOL, MAXFEV)
|
|
x_t, n_t, ok_t, tr = chandrupatla_traced(f, a, b, XTOL, MAXFEV)
|
|
if (n_ref, ok_ref) != (n_t, ok_t):
|
|
drift += 1
|
|
steps = tr["iqi"] + tr["bisect"]
|
|
gate_ok = 100.0 * tr["iqi"] / steps if steps else float("nan")
|
|
|
|
stats = {}
|
|
x_o, n_o, ok_o = pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio",
|
|
scale="derived3", guard="credit", stats=stats)
|
|
forced = 100.0 * stats.get("forced", 0) / max(n_o, 1)
|
|
|
|
ch_h = decades / n_ref if n_ref else float("nan")
|
|
our_h = decades / n_o if n_o else float("nan")
|
|
agg["ch_n"] += n_ref if ok_ref else MAXFEV
|
|
agg["our_n"] += n_o if ok_o else MAXFEV
|
|
if ok_ref:
|
|
agg["ch_h"].append(ch_h)
|
|
agg["gate"].append(gate_ok)
|
|
if ok_o:
|
|
agg["our_h"].append(our_h)
|
|
agg["forced"].append(forced)
|
|
print(f"{pid:<16} {n_ref:8d} {ch_h:8.3f} {gate_ok:8.1f}% "
|
|
f"{n_o:10d} {our_h:10.3f} {forced:7.1f}%")
|
|
|
|
mean = lambda v: sum(v) / len(v) if v else float("nan") # noqa: E731
|
|
print(f"\nmirror fidelity: {'OK' if drift == 0 else f'DRIFTED on {drift} problems'}")
|
|
print(f"totals (failures charged {MAXFEV}): Chandrupatla {agg['ch_n']}, ours {agg['our_n']}")
|
|
print(f"halvings per evaluation: Chandrupatla {mean(agg['ch_h']):.3f}, "
|
|
f"ours {mean(agg['our_h']):.3f}")
|
|
print(f"Chandrupatla accepted the model on {mean(agg['gate']):.1f}% of steps "
|
|
f"(the rest it REFUSED and halved)")
|
|
print(f"our safeguard had to repair {mean(agg['forced']):.1f}% of evaluations")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|