explained, with numbers: Chandrupatla wins the probe by REFUSING its model cheaply, not by trusting it — it accepts the interpolation on only 28% of steps, its gate costs zero evaluations (read from data already in hand) and its refusal branch still halves the bracket; ours repairs 77.5% of evaluations after the fact and drops to 0.92 halvings/eval on fun8/fun9, worse than bisection. In similarity terms: their similarity is a PRE-step GATE on dimensionless ratios (a neighbourhood of the linear configuration), ours is an IN-step SCALE on the residual magnitude — the PLACEMENT of the reading matters more than its form

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
vvs
2026-09-20 23:05:22 +01:00
co-authored by Claude Opus 5
parent 84ff1c796f
commit be10fc0b22
3 changed files with 251 additions and 0 deletions
+48
View File
@@ -88,3 +88,51 @@
Chandrupatla-1997 (2423 против наших 4516) при **нуле** настроечных констант, то есть существующий
алгоритм уже делает то, чего мы добиваемся, и объяснение его преимущества — отдельная задача
(качество пробы, `stage4_why.py`), а не новая шкала шага.
---
## 3. 🟢 ЧЕМ Chandrupatla выигрывает пробу — измерено, и это переводится на язык похожести
Инструмент: `explain_chandrupatla.py` (лог `results_explain_chandrupatla.txt`). Трассируемая копия
опубликованной процедуры; её верность **проверена** — совпадение с оригиналом по числу вызовов и
исходу на всех 45 задачах. Метрика пробы беспараметрическая: **половинок скобки на вызов**
`= log₂(w₀/xtol)/nfev`, деление пополам = 1.0.
| величина (45 задач CHA) | Chandrupatla | наш `ratio.derived3.credit` |
|---|---|---|
| вызовов всего (провал = 200) | **1488** | 2073 |
| **половинок на вызов** | **2.310** | 1.422 |
| модель принята | **28.0 % шагов** | — |
| сторож ремонтировал | — | **77.5 % вызовов** |
Семейства `fun8`/`fun9`: у них 8–15 вызовов при 3–5 половинках, у нас 44–50 вызовов при **0.92** —
то есть **хуже деления пополам**, при 93 % ремонтов.
### Ответ
**Он выигрывает не тем, что больше доверяет модели, а тем, что дешевле от неё отказывается.**
Интерполяция принимается лишь в 28 % шагов; в остальных 72 % он делит пополам. Гейт считается из уже
имеющихся `x1,x2,x3,f1,f2,f3` — **ноль новых вызовов**, а ветвь отказа сама снимает половину скобки.
Наш отказ **платный**: шаг сделан, лёг плохо, сторож ремонтирует, ремонт — это вызов.
### Перевод
| | где читается похожесть | на чём | цена отказа |
|---|---|---|---|
| Chandrupatla | **до** шага, как **ГЕЙТ** | на форме конфигурации: `ξ = (x1−x2)/(x3−x2)`, `φ = (f1−f2)/(f3−f2)` — два отношения, инвариантные к масштабу по `x` и по `f`; полоса `1−√(1−ξ) < φ < √ξ` есть окрестность **линейной** конфигурации (для линейной `f` точно `φ = ξ`, и она внутри полосы) | **0 вызовов** |
| наш метод | **внутри** шага, как **МАСШТАБ** | на величине остатка `\|f\|/σ` | **1 вызов** |
**Следствие, которое дороже самого объяснения:** решает не форма функции похожести, а **её место в
алгоритме**. Похожесть, прочитанная ДО шага на отношениях, стоит ноль и отсекает недостоверную
модель; похожесть, прочитанная ВНУТРИ шага на величине, требует сторожа, и сторож платит. Это
дополняет закон корпуса «рассуждение платит тем больше, чем хуже похожесть»: здесь **расположение
чтения важнее его формы**.
### Что из этого следует как проверяемый шаг (пред-регистрировать до реализации)
Добавить в наш метод **предшаговый гейт на отношениях** — читать похожесть конфигурации к
хорошо обусловленной ДО того, как шаг сделан, и при отказе сразу делить пополам, вместо
масштабированного шага с последующим ремонтом. Новой константы это не вводит: границы полосы у
Чандрупатлы выведены из требования «проба модели не должна выйти за скобку» — того же требования,
которое наш проекционный сторож выполняет, но **после** факта. Цель замера: довести долю ремонтов с
77.5 % к уровню их 72 % отказов **без** роста цены на APS-154.
+149
View File
@@ -0,0 +1,149 @@
"""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()
+54
View File
@@ -0,0 +1,54 @@
CHA problems: 45 metric: halvings per evaluation = log2(w0/xtol)/nfev (bisection = 1.0)
problem CH nfev CH h/ev gate ok% ours nfev ours h/ev forced%
fun1.1 8 4.983 100.0% 25 1.595 20.0%
fun1.2 12 3.586 55.6% 47 0.916 93.6%
fun1.3 15 3.099 41.7% 50 0.930 94.0%
fun1.4 24 2.256 33.3% 58 0.934 93.1%
fun1.5 44 1.684 17.1% 78 0.950 84.6%
fun2.1 9 4.431 66.7% 12 3.323 25.0%
fun2.2 23 2.311 25.0% 57 0.932 94.7%
fun2.3 29 2.062 19.2% 63 0.949 95.2%
fun2.4 42 1.740 10.3% 77 0.949 77.9%
fun2.5 49 1.627 10.9% 83 0.961 65.1%
fun3.1 44 0.959 0.0% 46 0.917 93.5%
fun3.2 46 0.961 0.0% 48 0.921 93.8%
fun3.3 61 0.888 8.6% 58 0.934 65.5%
fun3.4 69 0.881 13.6% 64 0.950 65.6%
fun3.5 82 0.903 7.6% 58 1.277 1.7%
fun4.1 44 0.959 0.0% 46 0.917 93.5%
fun4.2 46 0.961 0.0% 48 0.921 93.8%
fun4.3 56 0.967 5.7% 58 0.934 75.9%
fun4.4 67 0.907 7.8% 64 0.950 51.6%
fun4.5 80 0.926 5.2% 78 0.950 51.3%
fun5.1 44 0.959 0.0% 46 0.917 93.5%
fun5.2 44 0.970 0.0% 46 0.928 93.5%
fun5.3 45 0.963 0.0% 47 0.922 93.6%
fun5.4 47 0.971 0.0% 49 0.932 93.9%
fun5.5 48 0.972 0.0% 50 0.933 94.0%
fun6.1 44 0.959 0.0% 46 0.917 93.5%
fun6.2 44 0.970 0.0% 46 0.928 93.5%
fun6.3 45 0.963 0.0% 47 0.922 91.5%
fun6.4 47 0.971 0.0% 49 0.932 91.8%
fun6.5 48 0.972 0.0% 50 0.933 92.0%
fun7.1 8 5.273 40.0% 9 4.687 66.7%
fun7.2 8 5.334 40.0% 8 5.334 62.5%
fun7.3 11 3.938 37.5% 10 4.332 70.0%
fun7.4 18 2.536 40.0% 13 3.511 53.8%
fun7.5 19 2.455 43.8% 13 3.588 38.5%
fun8.1 10 4.086 71.4% 44 0.929 93.2%
fun8.2 11 3.768 62.5% 45 0.921 93.3%
fun8.3 13 3.310 60.0% 47 0.916 93.6%
fun8.4 14 3.187 45.5% 48 0.930 93.8%
fun8.5 15 3.080 41.7% 36 1.283 63.9%
fun9.1 8 4.983 100.0% 16 2.491 25.0%
fun9.2 9 4.605 83.3% 45 0.921 93.3%
fun9.3 11 3.912 62.5% 47 0.916 93.6%
fun9.4 13 3.432 60.0% 48 0.930 93.8%
fun9.5 14 3.300 45.5% 50 0.924 94.0%
mirror fidelity: OK
totals (failures charged 200): Chandrupatla 1488, ours 2073
halvings per evaluation: Chandrupatla 2.310, ours 1.422
Chandrupatla accepted the model on 28.0% of steps (the rest it REFUSED and halved)
our safeguard had to repair 77.5% of evaluations