diff --git a/apply_article_revision2.py b/apply_article_revision2.py new file mode 100644 index 0000000..e499790 --- /dev/null +++ b/apply_article_revision2.py @@ -0,0 +1,133 @@ +"""Revision 2: points 2-7 into article/sim_numerics.md. + + 2 'pre-registered' -> 'recorded in advance'; Appendix D says the order is attested only by the + author's git history + 3 why Dekker, Illinois and false position show the same totals under both criteria + 4 exact-zero stops in Table 11, per method + 5 control K3 (PREREG_estimated_transform): Table 12 and its paragraph; 6.6, conclusion, future work + 6 Algorithms 1-2: lo/hi, the return on a stall, L_x and L_f, an empty memory, the step pattern + as a formula, the O(n^2) search + 7 Table 3's two missing bracket-scaled rows; dashes in Table 5; the abstract's last sentence +Exact anchors, each found exactly once, or nothing is written. + + python -u apply_article_revision2.py +""" +from __future__ import annotations + +PATH = "article/sim_numerics.md" +R = [] + + +def E(old, new): + R.append((old, new)) + + +# ---- point 2 +E("The code, the corpora and the pre-registrations are released", "The code, the corpora and the documents that recorded each test before its run are released") +E("Every rule below was pre-registered with a stop rule (Appendix D);", "Every rule below was recorded, with its criteria and stop rule, before its code and run (Appendix D);") +E("because the pre-registered rule removes only a component", "because the rule recorded in advance removes only a component") +E("Pre-registration protects each decision but not the chain:", "Recording each decision in advance protects the decision but not the chain:") +E("by every pre-registered criterion (per problem", "by every criterion recorded in advance (per problem") +E("Both were pre-registered, with what the paper would say under every outcome, before the run", "Both were recorded, with what the paper would say under every outcome, before the run") +E("as the pre-registered criterion required.", "as the criterion recorded in advance required.") +E("the pre-registrations with the commits of Appendix D;", "the advance records with the commits of Appendix D;") +E("and it passed every pre-registered criterion there.", "and it passed every criterion recorded for it there.") +E("was rejected by its own pre-registration,", "was rejected by its own advance record,") +E("## Appendix D. Pre-registrations and the frozen code", "## Appendix D. Advance records and the frozen code") +E("The code of A′ was committed (`8027d7f`)", + "We call these documents advance records, not pre-registrations: they live in the author's repository, and their order relative to the runs is attested only by the author's git history, whose timestamps the author sets; no external registry was used. A deposit with an external timestamp (Zenodo, OSF) before a run would remove that limitation for future tests. The code of A′ was committed (`8027d7f`)") +E("**Table D1.** Pre-registrations, in the order they were written.", "**Table D1.** Advance records, in the order they were written.") +E("| pre-registration | commit | results file | commit | order |", "| advance record | commit | results file | commit | order |") + +# ---- point 3 +E("and 79/5/70 against Brent.", + "and 79/5/70 against Brent. Dekker, Illinois and false position fail `aps.13.00` under either criterion and so keep their totals: they spend the whole budget without converging, Dekker and Illinois stalled on one side of the zero where $|f|<10^{-62}$ but $f$ does not change sign within $10^{-6}$.") + +# ---- point 4 +E("With the multiplicity known the transformation is 2.4 to 6.8 times cheaper than A′.", + "With the multiplicity known the transformation is 2.4 to 6.8 times cheaper than A′. Every method, A′ included, stops on an exact zero of the function it evaluates, and on the pure powers of the Chandrupatla set $g$ is exactly linear, so the secant lands on the root: 15 of Brent's 20 runs and 4 of Chandrupatla's end that way there (A′: 2). On new corpus 1 no transformed run ends on a zero, and the transformation is still 3.4 times cheaper.") + +# ---- point 5: K3 +K3 = """ + +**The multiplicity estimated during the run.** Between A′ and the oracle lies the natural classical method: Chandrupatla's interpolation on $g=\\operatorname{sign}(f)|f|^{1/\\hat m}$ with $\\hat m$ estimated during the run. Our implementation of Chandrupatla's method (identical to the library on all 199 problems, and to itself when $\\hat m\\equiv1$) keeps the true $f$ at its three points and reads $G(v)=\\operatorname{sign}(v)|v|^{1/\\hat m}$ only in the acceptance band and the interpolation; $\\hat m$ starts at 1 and takes an order read from the last three evaluations whenever that order lies on a branch of §6.1 ($m\\ge2$ or $0 1/2, + # and step and x - xp point the same way + if the pattern repeats:""", + """ # step pattern: q_j <- |d_j|/|d_(j-1)| over successive nonzero + # moves d (k of them); rho(u,v) <- min(u,v)/max(u,v); repeats iff + # max over p = 2..floor(k/2) of mean_j rho(q_j, q_(j-p)) > 1/2 + if repeats and step, x - xp point the same way:""") +E(" xn <- clamp(xn, hi - allow, lo + allow) # projection (3)", + " lo, hi <- min(A,B), max(A,B)\n xn <- clamp(xn, hi - allow, lo + allow) # projection (3)") +E(" xn <- mid; if mid in {x, xp}: stop", " xn <- mid; if mid in {x, xp}: return mid") +E("implement exactly this and agree bit for bit.", + "implement exactly this and agree bit for bit. The nearest-configuration search runs over the run's memory: $O(n)$ per step, $O(n^2)$ per run of $n$ evaluations ($n\\le200$ here), arithmetic that matters only when $f$ is cheap (Appendix B).") + +# ---- point 7 +E("| 3 | similarity, $e^{-\\lvert z\\rvert}$, bracket scale, both guards | no | 45/45 | 1778 | 0 |", + "| 3 | similarity, $e^{-\\lvert z\\rvert}$, bracket scale, both guards | no | 45/45 | 1778 | 0 |\n| 4 | similarity, $e^{-\\lvert z\\rvert}$, bracket scale, bracket guard | no | 45/45 | 1848 | 0 |") +E("| 5 | ITP, recommended constants | yes | 45/45 | 1946 | 0 |", + "| 5 | ITP, recommended constants | yes | 45/45 | 1946 | 0 |\n| 6 | similarity, $e^{-\\lvert z\\rvert}$, bracket scale, earned projection | no | 45/45 | 1952 | 0 |") +E("| A′, earned budget (no free constant) | 3417 | 1852 | 5269 | | |", "| A′, earned budget (no free constant) | 3417 | 1852 | 5269 | — | — |") +E("| base rule (§3) | 2634 | 2357 | 4991 | | |", "| base rule (§3) | 2634 | 2357 | 4991 | — | — |") +E("We replace bisection's midpoint rule by a similarity rule — the step grows with how dissimilar $f(x)$ is to the target 0, under a similarity $S$ with $S(0)=1$ — and derive", + "We replace bisection's midpoint rule by a similarity rule — the step grows with the dissimilarity of $f(x)$ to 0 under a similarity $S$ — and derive") +E("With the multiplicity known, the classical transformation $\\operatorname{sign}(f)|f|^{1/m}$ is 2.4 to 6.8 times cheaper on multiple roots.", + "With the multiplicity known, the classical transformation $\\operatorname{sign}(f)|f|^{1/m}$ is 2.4 to 6.8 times cheaper on multiple roots; what A′ adds is an estimate of the order made without knowing it, within a guarantee of twice bisection.") + + +def main(): + src = open(PATH, encoding="utf8").read() + bad = [o[:80] for o, _ in R if src.count(o) != 1] + if bad: + for b in bad: + print("ANCHOR NOT FOUND EXACTLY ONCE:", repr(b)) + raise SystemExit(f"{len(bad)} anchors failed; nothing written") + for o, n in R: + src = src.replace(o, n) + open(PATH, "w", encoding="utf8").write(src) + ab = src.split("## Abstract\n\n")[1].split("\n\n---\n")[0] + code = src.split("**Algorithm 2 (A′).**")[1].split("```")[1] + print(f"applied {len(R)}; abstract {len(ab.split())} words; longest Algorithm 2 line " + f"{max(len(l) for l in code.split(chr(10)))}; 'pre-regist' left: {src.lower().count('pre-regist')}") + + +if __name__ == "__main__": + main() diff --git a/apply_revision2_fit.py b/apply_revision2_fit.py new file mode 100644 index 0000000..686b8b3 --- /dev/null +++ b/apply_revision2_fit.py @@ -0,0 +1,28 @@ +"""Revision 2 follow-up: the step-pattern comment of Algorithm 2 within 72 characters a line, and the +abstract back under 250 words (same claims). + + python -u apply_revision2_fit.py +""" +PATH = "article/sim_numerics.md" +R = [ + (""" # step pattern: q_j <- |d_j|/|d_(j-1)| over successive nonzero + # moves d (k of them); rho(u,v) <- min(u,v)/max(u,v); repeats iff + # max over p = 2..floor(k/2) of mean_j rho(q_j, q_(j-p)) > 1/2""", + """ # step pattern: q_j <- |d_j|/|d_(j-1)| over the + # successive nonzero moves d (k ratios); + # rho(u,v) <- min(u,v)/max(u,v); repeats iff + # max_{p=2..floor(k/2)} mean_j rho(q_j, q_(j-p)) > 1/2"""), + ("The derivation fixes a base rule before any benchmark is consulted:", "It fixes, before any benchmark, a base rule:"), + ("This decision layer, about ten discrete rules selected on two development sets, gives the method A′.", + "This decision layer — about ten discrete rules selected on two development sets — gives A′."), + ("what A′ adds is an estimate of the order made without knowing it, within a guarantee of twice bisection.", + "A′ adds an estimate of the order, within a guarantee of twice bisection."), +] +src = open(PATH, encoding="utf8").read() +for o, n in R: + assert src.count(o) == 1, o[:60] + src = src.replace(o, n) +open(PATH, "w", encoding="utf8").write(src) +ab = src.split("## Abstract\n\n")[1].split("\n\n---\n")[0] +code = src.split("**Algorithm 2 (A′).**")[1].split("```")[1] +print(f"abstract {len(ab.split())} words; longest Algorithm 2 line {max(len(l) for l in code.split(chr(10)))}") diff --git a/apply_verify_revision2.py b/apply_verify_revision2.py new file mode 100644 index 0000000..b2a16e3 --- /dev/null +++ b/apply_verify_revision2.py @@ -0,0 +1,106 @@ +"""verify_article.py checks the numbers the second revision added: Table 12 and its paragraph +(sweep_estimated.json), the exact-zero stops of Table 11 (diag_revision2.point4, fresh), the three +unchanged rows of Table 2 (fresh runs on aps.13.00), the two new rows of Table 3, and the fractional +readings of Lagouanelle's estimate on the canonical set (fresh). Adds verify_revision2() and its call. + + python -u apply_verify_revision2.py +""" +PATH = "verify_article.py" +FUNC = ''' + +def verify_revision2(): + """Revision 2 (points 3, 4, 5, 7).""" + import math + print("\\nRevision 2:") + + def cost(v): + return v[0] if v[1] else 200 + + def tot(r, keep=None): + return sum(cost(v) for p, v in r.items() if keep is None or keep(p)) + + 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) + sets = ("APS", "CHA", "NEW", "RJL", "APP", "D") + K = _load("sweep_estimated.json") + seen, four = _load("sweep_fourth_seen.json"), _load("sweep_fourth_fourth.json") + ap, ch = dict(seen["Aprime"]), dict(seen["chandrupatla"]) + ap.update(four["Aprime"]) + ch.update(four["chandrupatla"]) + K1 = _load("sweep_controls.json")["K1"] + nonsimple = lambda m: m >= 2.0 or m <= 2.0 / 3.0 # noqa: E731 + + def group(r): + return sum(tot(r[s], lambda p, _m=K1[s]["mstar"]: nonsimple(_m[p])) for s in ("CHA", "NEW", "D")) + rows = {"A'": ap, "K3A": K["k3_analogy"], "K3L": K["k3_lag"], "Ch(f)": ch} + want = {"A'": (3656, 13032, 4042, 2259), "K3A": (3806, 15733, 2809, 3453), + "K3L": (7526, 30991, 6041, 7215), "Ch(f)": (5717, 16481, 7011, 2595)} + for k, r in rows.items(): + check(f"Table 12 {k}", (tot(r["D"]), sum(tot(r[s]) for s in sets), group(r), tot(r["APS"])), want[k]) + oracle = sum(tot(K1[s]["chandrupatla_g"], lambda p, _m=K1[s]["mstar"]: nonsimple(_m[p])) + for s in ("CHA", "NEW", "D")) + check("Table 12 oracle row (Table 11 sum)", oracle, 1355) + ps = list(ap["D"]) + w = sum(cost(ap["D"][p]) < cost(K["k3_analogy"]["D"][p]) for p in ps) + l = sum(cost(ap["D"][p]) > cost(K["k3_analogy"]["D"][p]) for p in ps) + check("A' vs Chandrupatla-on-g with the analogy order, corpus 4", (w, len(ps) - w - l, l), (126, 38, 76)) + check("its p (1 digit)", f"{sign_p(w, l):.1g}", "0.0005") + shares = [] + for s in ("CHA", "NEW", "D"): + keep = lambda p, _m=K1[s]["mstar"]: nonsimple(_m[p]) # noqa: E731 + a_, k_, o_ = tot(ap[s], keep), tot(K["k3_analogy"][s], keep), tot(K1[s]["chandrupatla_g"], keep) + shares.append((k_, a_, round(100 * (a_ - k_) / (a_ - o_)))) + check("share of the A'-to-oracle gap closed by the analogy order", shares, + [(322, 654, 59), (721, 1229, 58), (1766, 2159, 31)]) + check("K3 bound violations", sum(v[2] for k in K for s in sets for v in K[k][s].values()), 0) + # Lagouanelle's fractional readings on the simple roots of the canonical set (fresh) + from simroot.transformed import chandrupatla_transformed + from sweep_full import load_problems + frac = 0 + for s, pid, f, a, b, _r in load_problems(): + if s != "APS": + continue + st = {} + chandrupatla_transformed(f, a, b, 1e-12, 200, estimator="lag", stats=st) + m = st.get("m_hat", 1.0) + frac += (0.0 < m <= 2.0 / 3.0) + check("Lagouanelle holds a fractional order on simple APS roots", frac, 115) + # point 4: exact-zero stops (fresh) + import contextlib + import io + import diag_revision2 + with contextlib.redirect_stdout(io.StringIO()): + z = diag_revision2.point4() + check("exact-zero stops on CHA: Brent(g), Chandrupatla(g), A'", + (z["CHA"]["brent_g"][1], z["CHA"]["chandrupatla_g"][1], z["CHA"]["Aprime"][1]), (15, 4, 2)) + check("exact-zero stops of the transformed runs on corpus 1", + (z["NEW"]["chandrupatla_g"][1], z["NEW"]["brent_g"][1]), (0, 0)) + check("corpus 1: transformation 3.4 times cheaper", round(1229 / 358, 1), 3.4) + # point 3: the three unchanged rows of Table 2 (fresh) + from corpus_roots_jl import solved + from sweep_full import make_method + f, a, b = next((f, a, b) for s, pid, f, a, b, _r in load_problems() if pid == "aps.13.00") + got = [] + for name in ("dekker", "illinois", "regula_falsi"): + x, n, conv = make_method(("base", name))(f, a, b, 1e-12, 200) + got.append((name, int(n), bool(conv), bool(solved(f, float(x))))) + check("aps.13.00: Dekker, Illinois, false position spend the budget unsolved", + got, [("dekker", 200, False, False), ("illinois", 200, False, False), + ("regula_falsi", 200, False, False)]) + small = [abs(float(f(float(make_method(("base", nm))(f, a, b, 1e-12, 200)[0])))) < 1e-62 + for nm in ("dekker", "illinois")] + check("Dekker and Illinois stall where |f| < 1e-62", small, [True, True]) + # point 7: Table 3's two added rows + full = {r["label"]: r for r in _load("sweep_full.json")} + for lb, want_ in (("pure.exp_abs.bracket.bracket", (1848, 45, 0)), ("pure.exp_abs.bracket.credit", (1952, 45, 0))): + c = full[lb]["CHA"] + check(f"Table 3 row {lb}", (c["total"], c["solved"], c["over"]), want_) +''' +src = open(PATH, encoding="utf8").read() +call_old = " verify_controls()\n\n print()" +marker = '\n\nif __name__ == "__main__":\n main()\n' +assert src.count(call_old) == 1 and src.count(marker) == 1 +src = src.replace(call_old, " verify_controls()\n verify_revision2()\n\n print()").replace(marker, FUNC + marker) +open(PATH, "w", encoding="utf8").write(src) +print("added verify_revision2") diff --git a/article/sim_numerics.md b/article/sim_numerics.md index 30e68ba..1b4f4ed 100644 --- a/article/sim_numerics.md +++ b/article/sim_numerics.md @@ -4,7 +4,7 @@ V. Sitnikov ## Abstract -We replace bisection's midpoint rule by a similarity rule — the step grows with how dissimilar $f(x)$ is to the target 0, under a similarity $S$ with $S(0)=1$ — and derive its contraction factor in closed form. The derivation fixes a base rule before any benchmark is consulted: with the tent similarity and a three-point divided-difference scale it is a Newton step of Muller's order 1.839, clipped to the bracket and projected onto a budget of twice bisection whose multiplier is its one numerical constant. On the 154 problems of Alefeld, Potra and Shi it is level with TOMS 748 and 1.5% behind Chandrupatla's method; on wide brackets it is clearly behind. The gap is a decision, when to give the model up, and we take it by analogy: at a flat root the dimensionless configuration of the last three points recurs under zoom, at a simple root it tends to a line, and the analogous pair gives the order that says whether and where to split. This decision layer, about ten discrete rules selected on two development sets, gives the method A′. On 240 problems fixed before its code was frozen it needs 3656 evaluations against 5717 for Chandrupatla, 5535 for ModAB and 4488 with Lagouanelle's multiplicity estimate in place of the analogy; the lead lies in multiple and fractional roots, present in two of the six families. With the multiplicity known, the classical transformation $\operatorname{sign}(f)|f|^{1/m}$ is 2.4 to 6.8 times cheaper on multiple roots. +We replace bisection's midpoint rule by a similarity rule — the step grows with the dissimilarity of $f(x)$ to 0 under a similarity $S$ — and derive its contraction factor in closed form. It fixes, before any benchmark, a base rule: with the tent similarity and a three-point divided-difference scale it is a Newton step of Muller's order 1.839, clipped to the bracket and projected onto a budget of twice bisection whose multiplier is its one numerical constant. On the 154 problems of Alefeld, Potra and Shi it is level with TOMS 748 and 1.5% behind Chandrupatla's method; on wide brackets it is clearly behind. The gap is a decision, when to give the model up, and we take it by analogy: at a flat root the dimensionless configuration of the last three points recurs under zoom, at a simple root it tends to a line, and the analogous pair gives the order that says whether and where to split. This decision layer — about ten discrete rules selected on two development sets — gives A′. On 240 problems fixed before its code was frozen it needs 3656 evaluations against 5717 for Chandrupatla, 5535 for ModAB and 4488 with Lagouanelle's multiplicity estimate in place of the analogy; the lead lies in multiple and fractional roots, present in two of six families. With the multiplicity known, the transformation $\operatorname{sign}(f)|f|^{1/m}$ is 2.4 to 6.8 times cheaper on multiple roots; A′ adds an estimate of the order, within a guarantee of twice bisection. --- @@ -21,7 +21,7 @@ The paper makes four claims, each measured. 3. The base rule's gap on wide brackets is a decision — when to give the model up — and a similarity of the run to its own past takes it (§6). The resulting method, A′, is tested once on a corpus fixed before its code was frozen, against simpler detectors put in its place, and against the classical transformation with the multiplicity known in advance (§7). 4. Every table uses one criterion, a sign change within $10^{-6}$ of the answer, because the benchmarks' published roots score six problems unsolved for every method (§4, §A.5). -The search that produced these results — readings of the similarity that did not pay, the decomposition of the wide-bracket gap, wall-clock time, and the rejected rules — is reported in Appendices A–C. The code, the corpora and the pre-registrations are released (Appendix D and the availability statement). +The search that produced these results — readings of the similarity that did not pay, the decomposition of the wide-bracket gap, wall-clock time, and the rejected rules — is reported in Appendices A–C. The code, the corpora and the documents that recorded each test before its run are released (Appendix D and the availability statement). --- @@ -116,8 +116,10 @@ while |B - A| > xtol: if sigma is 0 or not finite: xn <- midpoint(A,B) else: xn <- x + min(1, |f(x)|/sigma) * (far - x) # the step, eq (4) + lo, hi <- min(A,B), max(A,B) xn <- clamp(xn, hi - allow, lo + allow) # projection, eq (3) if xn outside (A,B) or already evaluated: xn <- midpoint(A,B) + (if the midpoint too was evaluated: return it) evaluate fn = f(xn); allow <- allow / 2 xp2, xp, x <- xp, x, xn update the bracket: whichever of A,B keeps the sign change @@ -211,7 +213,7 @@ The grid is nine similarity functions × four scales × seven safeguards = 252 c | 237 | Illinois [7] | yes | 153/154 | 7045 | 0 | | 244 | false position | no | 127/154 | 10560 | — | -The base rule and the same rule without any safeguard tie at 2634: the projection never binds on this set. By total, Chandrupatla's method is first with 2595, 1.5% fewer; the base rule and TOMS 748 are level (2634 against 2636). Problem by problem the base rule wins against Chandrupatla 87/24/43 (wins/ties/losses; exact two-sided sign test $p=1.4\cdot10^{-4}$), against TOMS 748 86/16/52, Brent 102/11/41, ITP at its recommended constants 132/3/19, the per-problem ITP oracle 121/4/29, and bisection 152/1/1. It trails Chandrupatla in total because its losses are the larger ones; by function family, the unit in which these problems are independent (§7), the comparison is 6/1/8 of 15 families, a tie. The constant-free variant goes 79/3/72 against Chandrupatla ($p=0.63$), 78/9/67 against TOMS 748 and 79/5/70 against Brent. +The base rule and the same rule without any safeguard tie at 2634: the projection never binds on this set. By total, Chandrupatla's method is first with 2595, 1.5% fewer; the base rule and TOMS 748 are level (2634 against 2636). Problem by problem the base rule wins against Chandrupatla 87/24/43 (wins/ties/losses; exact two-sided sign test $p=1.4\cdot10^{-4}$), against TOMS 748 86/16/52, Brent 102/11/41, ITP at its recommended constants 132/3/19, the per-problem ITP oracle 121/4/29, and bisection 152/1/1. It trails Chandrupatla in total because its losses are the larger ones; by function family, the unit in which these problems are independent (§7), the comparison is 6/1/8 of 15 families, a tie. The constant-free variant goes 79/3/72 against Chandrupatla ($p=0.63$), 78/9/67 against TOMS 748 and 79/5/70 against Brent. Dekker, Illinois and false position fail `aps.13.00` under either criterion and so keep their totals: they spend the whole budget without converging, Dekker and Illinois stalled on one side of the zero where $|f|<10^{-62}$ but $f$ does not change sign within $10^{-6}$. **Table 3.** Chandrupatla, 45 problems, brackets up to $10^{12}$ wide, out of 264 configurations. @@ -220,7 +222,9 @@ The base rule and the same rule without any safeguard tie at 2634: the projectio | 1 | Chandrupatla | no | 45/45 | 1505 | 0 | | 2 | ITP, per-problem oracle | yes | 45/45 | 1675 | 0 | | 3 | similarity, $e^{-\lvert z\rvert}$, bracket scale, both guards | no | 45/45 | 1778 | 0 | +| 4 | similarity, $e^{-\lvert z\rvert}$, bracket scale, bracket guard | no | 45/45 | 1848 | 0 | | 5 | ITP, recommended constants | yes | 45/45 | 1946 | 0 | +| 6 | similarity, $e^{-\lvert z\rvert}$, bracket scale, earned projection | no | 45/45 | 1952 | 0 | | 9 | constant-free variant: earned projection | no | 45/45 | 2073 | 0 | | 15 | bisection | no | 45/45 | 2136 | 0 | | **49** | **base rule: tent, 3-point, projection ($c=2$)** | **no** | 45/45 | **2357** | 0 | @@ -253,7 +257,7 @@ Ranked individually and best over scales, the similarities come out $1-|z|$ (542 ## 6. The flat regime, recognised by analogy -The base rule's gap on wide brackets is a decision, *when to give the model up* (§A.6): on the pure powers `fun3`–`fun6` the right move is to halve, Chandrupatla's method halves on up to all of its steps, and no reading of a single state measured takes that decision correctly. This section takes it by a similarity of the run to *its own past* — an analogy in Pólya's sense [8], two situations agreeing in more than one relation. Every rule below was pre-registered with a stop rule (Appendix D); the rejected ones are in Appendix C. +The base rule's gap on wide brackets is a decision, *when to give the model up* (§A.6): on the pure powers `fun3`–`fun6` the right move is to halve, Chandrupatla's method halves on up to all of its steps, and no reading of a single state measured takes that decision correctly. This section takes it by a similarity of the run to *its own past* — an analogy in Pólya's sense [8], two situations agreeing in more than one relation. Every rule below was recorded, with its criteria and stop rule, before its code and run (Appendix D); the rejected ones are in Appendix C. ### 6.1 The flag: a promise that was not kept @@ -305,7 +309,7 @@ which is exact for a pure power and becomes the midpoint as $m\to\infty$. It is The method this paper proposes, **A′**, is the base rule of §3 — tent similarity, three-point scale, projection with the declared multiplier $c=2$ — with the regime of §6.2–6.3 (the analogy remembered, its translation limit, the split (7), the exit) and the five readings of §6.4. Its constants are bisection's $\tfrac12$ and what follows from it: $\tfrac14=(1-\tfrac12)^2$ and the two branches $m\ge2$, $m\le\tfrac23$ of $|1-1/m|>\tfrac12$. Three choices are declared rather than derived: the multiplier $c$ of the projection (§3.1), the Euclidean geometry of the $(\xi,\varphi)$ plane, and the pattern test of the step lengthening (lags from 2 to half the history, threshold $\tfrac12$). The projection carries the guarantee, $2\lceil\log_2(w_0/\varepsilon_x)\rceil+3$ evaluations; it is verified with no violation on every corpus of this paper and under every safeguard of the grid. -Counting numerical constants does not measure the method's freedom, however. Beyond these numbers A′ is a decision layer of about ten discrete rules — the flag, the analogy and its memory, the entry, the exit, the overshoot branch, the split, the step pattern, the half, the contradiction, the tolerance step — chosen on the two development sets among the many candidates of Appendix C. That selection is why the method was tested once on a corpus fixed before its code was frozen, and against simpler rules put in its place (§7). Algorithm 2 is the complete method; `simroot/pure.py` and the compiled `simbracket` implement exactly this and agree bit for bit. +Counting numerical constants does not measure the method's freedom, however. Beyond these numbers A′ is a decision layer of about ten discrete rules — the flag, the analogy and its memory, the entry, the exit, the overshoot branch, the split, the step pattern, the half, the contradiction, the tolerance step — chosen on the two development sets among the many candidates of Appendix C. That selection is why the method was tested once on a corpus fixed before its code was frozen, and against simpler rules put in its place (§7). Algorithm 2 is the complete method; `simroot/pure.py` and the compiled `simbracket` implement exactly this and agree bit for bit. The nearest-configuration search runs over the run's memory: $O(n)$ per step, $O(n^2)$ per run of $n$ evaluations ($n\le200$ here), arithmetic that matters only when $f$ is cheap (Appendix B). **Algorithm 2 (A′).** Notation of Algorithm 1; $S(z)=\max(0,1-|z|)$. @@ -327,9 +331,11 @@ while |B - A| > xtol: # unkept promise, and unkept promise with a crossing flag <- not in_regime and last_model and fx/fp >= 1/4 flag_over <- not in_regime and last_model and fx/fp <= -1/4 - # order (6) of the analogous pair: the past configuration - # nearest to c (Euclidean in (xi, phi)), if nearer than the - # line phi = xi; inf if the pair shows L_f < 1 <= L_x; else none + # order (6) of the analogous pair. q <- the configuration in + # configs nearest to c (Euclidean in (xi, phi)); no m if c or + # configs is empty or q is not nearer than the line phi = xi. + # L_x <- |x-xp| / q.dx, L_f <- |fx-fp| / q.df + # m <- inf if L_f < 1 <= L_x (translation), else ln L_f / ln L_x m <- order of the analogous pair if m >= 2: analogy_seen <- true; m_a <- m if 0 < m <= 2/3: shallow_seen <- true; m_s <- m @@ -343,18 +349,20 @@ while |B - A| > xtol: else: step <- (1 - S(z)) * (far - x) # eq. (4) append x - xp to moves - # step pattern: the lag 2 .. half the history of highest - # mean similarity of successive length ratios has mean > 1/2, - # and step and x - xp point the same way - if the pattern repeats: + # step pattern: q_j <- |d_j|/|d_(j-1)| over the + # successive nonzero moves d (k ratios); + # rho(u,v) <- min(u,v)/max(u,v); repeats iff + # max_{p=2..floor(k/2)} mean_j rho(q_j, q_(j-p)) > 1/2 + if repeats and step, x - xp point the same way: s <- min(|step|,|x-xp|) / max(|step|,|x-xp|) step <- step / (1 - s), at most |far - x| long if |step| < xtol/2 and last_model and |fx/fp| < 1/4: step <- sign(far - x) * xtol/2 # tolerance step xn <- x + step; model <- true + lo, hi <- min(A,B), max(A,B) xn <- clamp(xn, hi - allow, lo + allow) # projection (3) if xn outside (A,B) or xn in {x, xp}: - xn <- mid; if mid in {x, xp}: stop + xn <- mid; if mid in {x, xp}: return mid last_model <- model and xn is the model's own point fn <- f(xn) if in_regime: # exit @@ -371,8 +379,8 @@ return midpoint(A,B) | | APS-154 | CHA-45 | both | A′ per problem, APS | A′ per problem, CHA | |---|---|---|---|---|---| | **A′ (proposed)** | **2259** | **1074** | **3333** | — | — | -| A′, earned budget (no free constant) | 3417 | 1852 | 5269 | | | -| base rule (§3) | 2634 | 2357 | 4991 | | | +| A′, earned budget (no free constant) | 3417 | 1852 | 5269 | — | — | +| base rule (§3) | 2634 | 2357 | 4991 | — | — | | Chandrupatla | 2595 | 1505 | 4100 | 103/20/31, $p=3\cdot10^{-10}$ | 31/4/10, $p=0.001$ | | TOMS 748 | 2636 | 3088 | 5724 | 127/6/21 | 42/2/1 | | Brent | 2707 | 3238 | 5945 | 118/17/19 | 39/3/3 | @@ -394,7 +402,7 @@ A′ is 12.9% below Chandrupatla on the canonical set and 28.6% below on the wid | repeated step pattern | −22 | −6 | +123 | −30 | −10 | +34 | +89 | | sign contradiction | +11 | −12 | +29 | 0 | +1 | −1 | +28 | -Every component is positive in sum; the regime and the split carry the method. The step pattern (earns on two corpora of six) and the sign contradiction (+28 over 855 problems) are marginal — together under one per cent, 117 of 13032 evaluations — and are kept because the pre-registered rule removes only a component that earns nowhere. +Every component is positive in sum; the regime and the split carry the method. The step pattern (earns on two corpora of six) and the sign contradiction (+28 over 855 problems) are marginal — together under one per cent, 117 of 13032 evaluations — and are kept because the rule recorded in advance removes only a component that earns nowhere. **Table 7.** The similarity in the step of A′: the same method with each of the nine similarity functions of §5.2 in place of the tent, everything else unchanged. Totals on the two sets of §4 and over the six corpora of §7; the guarantee holds with every function. @@ -427,13 +435,13 @@ Several mechanisms of A′ coincide with classical ones, reached from similarity | the tolerance step | Dekker [9], Brent [2] — borrowed and named as such | | the exit judgment of §6.2 (the value at a split against the line) | ModAB's linearity check at the midpoint [10, 20], $|y_m-y_3|= 2.0 or m <= 2.0 / 3.0 # noqa: E731 + + def group(r): + return sum(tot(r[s], lambda p, _m=K1[s]["mstar"]: nonsimple(_m[p])) for s in ("CHA", "NEW", "D")) + rows = {"A'": ap, "K3A": K["k3_analogy"], "K3L": K["k3_lag"], "Ch(f)": ch} + want = {"A'": (3656, 13032, 4042, 2259), "K3A": (3806, 15733, 2809, 3453), + "K3L": (7526, 30991, 6041, 7215), "Ch(f)": (5717, 16481, 7011, 2595)} + for k, r in rows.items(): + check(f"Table 12 {k}", (tot(r["D"]), sum(tot(r[s]) for s in sets), group(r), tot(r["APS"])), want[k]) + oracle = sum(tot(K1[s]["chandrupatla_g"], lambda p, _m=K1[s]["mstar"]: nonsimple(_m[p])) + for s in ("CHA", "NEW", "D")) + check("Table 12 oracle row (Table 11 sum)", oracle, 1355) + ps = list(ap["D"]) + w = sum(cost(ap["D"][p]) < cost(K["k3_analogy"]["D"][p]) for p in ps) + l = sum(cost(ap["D"][p]) > cost(K["k3_analogy"]["D"][p]) for p in ps) + check("A' vs Chandrupatla-on-g with the analogy order, corpus 4", (w, len(ps) - w - l, l), (126, 38, 76)) + check("its p (1 digit)", f"{sign_p(w, l):.1g}", "0.0005") + shares = [] + for s in ("CHA", "NEW", "D"): + keep = lambda p, _m=K1[s]["mstar"]: nonsimple(_m[p]) # noqa: E731 + a_, k_, o_ = tot(ap[s], keep), tot(K["k3_analogy"][s], keep), tot(K1[s]["chandrupatla_g"], keep) + shares.append((k_, a_, round(100 * (a_ - k_) / (a_ - o_)))) + check("share of the A'-to-oracle gap closed by the analogy order", shares, + [(322, 654, 59), (721, 1229, 58), (1766, 2159, 31)]) + check("K3 bound violations", sum(v[2] for k in K for s in sets for v in K[k][s].values()), 0) + # Lagouanelle's fractional readings on the simple roots of the canonical set (fresh) + from simroot.transformed import chandrupatla_transformed + from sweep_full import load_problems + frac = 0 + for s, pid, f, a, b, _r in load_problems(): + if s != "APS": + continue + st = {} + chandrupatla_transformed(f, a, b, 1e-12, 200, estimator="lag", stats=st) + m = st.get("m_hat", 1.0) + frac += (0.0 < m <= 2.0 / 3.0) + check("Lagouanelle holds a fractional order on simple APS roots", frac, 115) + # point 4: exact-zero stops (fresh) + import contextlib + import io + import diag_revision2 + with contextlib.redirect_stdout(io.StringIO()): + z = diag_revision2.point4() + check("exact-zero stops on CHA: Brent(g), Chandrupatla(g), A'", + (z["CHA"]["brent_g"][1], z["CHA"]["chandrupatla_g"][1], z["CHA"]["Aprime"][1]), (15, 4, 2)) + check("exact-zero stops of the transformed runs on corpus 1", + (z["NEW"]["chandrupatla_g"][1], z["NEW"]["brent_g"][1]), (0, 0)) + check("corpus 1: transformation 3.4 times cheaper", round(1229 / 358, 1), 3.4) + # point 3: the three unchanged rows of Table 2 (fresh) + from corpus_roots_jl import solved + from sweep_full import make_method + f, a, b = next((f, a, b) for s, pid, f, a, b, _r in load_problems() if pid == "aps.13.00") + got = [] + for name in ("dekker", "illinois", "regula_falsi"): + x, n, conv = make_method(("base", name))(f, a, b, 1e-12, 200) + got.append((name, int(n), bool(conv), bool(solved(f, float(x))))) + check("aps.13.00: Dekker, Illinois, false position spend the budget unsolved", + got, [("dekker", 200, False, False), ("illinois", 200, False, False), + ("regula_falsi", 200, False, False)]) + small = [abs(float(f(float(make_method(("base", nm))(f, a, b, 1e-12, 200)[0])))) < 1e-62 + for nm in ("dekker", "illinois")] + check("Dekker and Illinois stall where |f| < 1e-62", small, [True, True]) + # point 7: Table 3's two added rows + full = {r["label"]: r for r in _load("sweep_full.json")} + for lb, want_ in (("pure.exp_abs.bracket.bracket", (1848, 45, 0)), ("pure.exp_abs.bracket.credit", (1952, 45, 0))): + c = full[lb]["CHA"] + check(f"Table 3 row {lb}", (c["total"], c["solved"], c["over"]), want_) + + if __name__ == "__main__": main()