two more places for similarity: points='shape' (WHICH triple the model uses, argmin |phi-xi| over declared candidates — select, never weight) and direction='dissimilar' (the far end read by |f| rather than by distance); defaults bit-identical, P1-P5 pinned (7 green), inertness counters make an inert mode a failed EXPERIMENT rather than a pass; each grid writes to its own file
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
# PREREG — похожесть в двух ещё незанятых местах: СОСТАВ ТОЧЕК и НАПРАВЛЕНИЕ
|
||||
|
||||
Составлено до реализации. Основание — `FINDINGS_slope_gate.md`: решает **валюта и место** чтения
|
||||
похожести. Одна и та же идея «отказывайся до шага» дала 4449 вызовов на APS при отказе по прогрессу
|
||||
скобки и 2848 при отказе по форме модели — разница 1601 вызов. Карта мест решения показала два
|
||||
незанятых:
|
||||
|
||||
| место | чем решается сейчас |
|
||||
|---|---|
|
||||
| **состав точек для модели** | **давностью**: `x_prev2 ← x_prev ← x ← x_new`, всегда три последних |
|
||||
| **направление** (какой конец «дальний») | **расстоянием**: `far = B if \|B−x\| ≥ \|A−x\| else A` |
|
||||
|
||||
---
|
||||
|
||||
## 1. Режим A — состав точек по ФОРМЕ (`points="shape"`)
|
||||
|
||||
Оценка наклона строится на тройке точек. Сейчас тройка берётся по давности, даже если она
|
||||
плохо обусловлена. Brent, TOMS 748 и Chandrupatla все **управляют составом точек** — это часть их
|
||||
конструкции, а у нас этого места нет вовсе.
|
||||
|
||||
**Признак — та же валюта, что уже сработала: форма конфигурации, безразмерно.** Для тройки
|
||||
`(x, u, v)` считаются два отношения в духе Chandrupatla:
|
||||
|
||||
ξ = (x − u)/(v − u) — отношение ПОЗИЦИЙ
|
||||
φ = (f(x) − f(u))/(f(v) − f(u)) — отношение ЗНАЧЕНИЙ
|
||||
|
||||
Для линейной `f` выполняется `φ = ξ` точно, поэтому `|φ − ξ|` — безразмерная мера непохожести
|
||||
конфигурации на линейную. Правило: **argmin |φ − ξ|** по объявленному списку кандидатов.
|
||||
|
||||
**Список кандидатов объявляется здесь и не расширяется по результату:**
|
||||
|
||||
T1 = (x, x_prev, x_prev2) — нынешнее правило давности
|
||||
T2 = (x, x_prev, far) — свежая точка плюс дальний конец скобки
|
||||
T3 = (x, A, B) — оба конца доказанной скобки
|
||||
|
||||
Вырожденные (совпадающие абсциссы или значения) выбрасываются. Порога нет: это **argmin по
|
||||
имеющимся вариантам**, а не сравнение с числом. Правило корпуса «select, never weight» соблюдено —
|
||||
тройка **выбирается**, оценки не взвешиваются.
|
||||
|
||||
## 2. Режим B — направление по НЕПОХОЖЕСТИ (`direction="dissimilar"`)
|
||||
|
||||
Сейчас «дальний» конец — геометрически более удалённый. Похожесть даёт другой ответ: дальним считать
|
||||
конец, чьё `|f|` **больше**, то есть менее похожее на ноль. Ни константы, ни порога — сравнение двух
|
||||
имеющихся величин.
|
||||
|
||||
**Режимы независимы и мерятся отдельно** (правило «менять одно за раз»); комбинация A+B мерится
|
||||
третьим прогоном только если оба по отдельности не ухудшили APS.
|
||||
|
||||
## 3. Цели
|
||||
|
||||
Точка отсчёта — уже достигнутое гейтом на наклоне (`slope.ratio.derived3.project`):
|
||||
APS **2848**, CHA **3074**, два набора **5922**, 193/199, `over2x = 0`.
|
||||
|
||||
| величина | цель |
|
||||
|---|---|
|
||||
| APS-154 | **≤ 2848** (не хуже текущего; идеально — вернуть 2806) |
|
||||
| CHA-45 | **≤ 2857** (недобранная цель ITP; сейчас 2903 по-наборно) |
|
||||
| два набора | **< 5922** |
|
||||
| решено | ≥ 193/199 |
|
||||
| гарантия `2⌈log₂(w/xtol)⌉+3` | `over2x = 0` |
|
||||
|
||||
## 4. Предсказания и фальсификаторы
|
||||
|
||||
| # | Предсказание | ФАЛЬСИФИКАТОР → вывод |
|
||||
|---|---|---|
|
||||
| **A1** | режим A не ухудшает APS (≤2848) и улучшает CHA (<2903) | ухудшает APS → состав точек не то место, режим отвергается |
|
||||
| **A2** | выбор тройки **не инертен**: отличается от давностной в ≥5 % шагов | <1 % → режим ничего не меняет, замер НЕ является свидетельством ни за, ни против |
|
||||
| **B1** | режим B не ухудшает APS (≤2848) | ухудшает → направление по непохожести отвергается |
|
||||
| **B2** | выбор конца отличается от геометрического в ≥5 % шагов | <1 % → инертен, замер пуст |
|
||||
| **C** | гарантия цела в обоих режимах | нарушена → немедленное закрытие режима |
|
||||
|
||||
**Правило остановки:** APS ухудшился или гарантия нарушена ⇒ режим не принимается. Инертность (A2/B2)
|
||||
⇒ результат не толкуется как подтверждение: это отсутствие эксперимента, а не его исход.
|
||||
|
||||
## 5. Гейты реализации (тесты ДО рейтинга)
|
||||
|
||||
| # | Условие | Проверка |
|
||||
|---|---|---|
|
||||
| **P1** | `points="recent"` и `direction="distance"` дают побитово прежний результат | равенство кортежей на наборе задач и сторожей |
|
||||
| **P2** | в обоих признаках нет констант | греп по коду (без комментариев): только `0.0`, `0.5`, `1.0` |
|
||||
| **P3** | счётчики `stats['triple_changed']`, `stats['dir_changed']` считают неинертность | тест на обоих |
|
||||
| **P4** | вырожденные тройки отбрасываются, а не дают деление на ноль | тест на совпадающих абсциссах и значениях |
|
||||
| **P5** | ни один режим не добавляет вызовов `f` | число вызовов ≤ число шагов + 2 |
|
||||
|
||||
**Дисциплина:** аудит после правки и до запуска; смоук ×2 локально; свипы в свои файлы
|
||||
(`sweep_points.json`, `sweep_direction.json`), `sweep_full.json` не трогать; числа только с Linux;
|
||||
замороженные копии; числа показываются немедленно.
|
||||
+87
-6
@@ -121,6 +121,47 @@ GUARDS = ("none", "bracket", "step", "both", "project", "safe", "credit")
|
||||
SPACES = ("x", "log")
|
||||
|
||||
|
||||
POINT_RULES = ("recent", "shape")
|
||||
DIRECTIONS = ("distance", "dissimilar")
|
||||
|
||||
|
||||
def _shape_defect(x, fx, u, fu, v, fv):
|
||||
"""|phi - xi| for the triple, or None when it is degenerate.
|
||||
|
||||
xi = (x - u)/(v - u) is a ratio of POSITIONS, phi = (fx - fu)/(fv - fu) a ratio of
|
||||
VALUES; both are invariant under affine rescaling of x and of f. For a linear f they
|
||||
are equal, so their gap measures how unlike a linear configuration the triple is —
|
||||
the same dimensionless currency Chandrupatla's acceptance band is written in."""
|
||||
if v == u or fv == fu:
|
||||
return None
|
||||
xi = (x - u) / (v - u)
|
||||
ph = (fx - fu) / (fv - fu)
|
||||
if not (math.isfinite(xi) and math.isfinite(ph)):
|
||||
return None
|
||||
return abs(ph - xi)
|
||||
|
||||
|
||||
def _pick_triple(x, fx, x_prev, f_prev, x_prev2, f_prev2, A, FA, B, FB, far):
|
||||
"""SELECT the triple whose configuration is least unlike a linear one (PREREG
|
||||
2026-09-21, mode A). Candidates are declared, not searched: the recency triple, the
|
||||
recent point with the far bracket end, and the two proven bracket ends. Returns
|
||||
((xu, fu), (xv, fv)) or None when nothing is admissible."""
|
||||
far_f = FB if far == B else FA
|
||||
cands = (((x_prev, f_prev), (x_prev2, f_prev2)),
|
||||
((x_prev, f_prev), (far, far_f)),
|
||||
((A, FA), (B, FB)))
|
||||
best, best_d = None, None
|
||||
for u, v in cands:
|
||||
if u[0] is None or v[0] is None:
|
||||
continue
|
||||
d = _shape_defect(x, fx, u[0], u[1], v[0], v[1])
|
||||
if d is None or x == u[0] or x == v[0]:
|
||||
continue
|
||||
if best_d is None or d < best_d:
|
||||
best, best_d = (u, v), d
|
||||
return best
|
||||
|
||||
|
||||
def _log_map(t, c, s):
|
||||
"""u = sign(t - c) * log1p(|t - c| / s). An ADDITIVE step in u is a
|
||||
MULTIPLICATIVE step in x, which is what the wide-bracket families need: the
|
||||
@@ -413,7 +454,8 @@ def _local_order(x2, f2, x1, f1, x0, f0):
|
||||
|
||||
def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
|
||||
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"):
|
||||
"""The pure similarity root finder.
|
||||
|
||||
similarity : key into PURE_SIMS
|
||||
@@ -469,6 +511,14 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
|
||||
raise ValueError(f"unknown space {space!r}")
|
||||
if prestep not in ("none", "progress", "slope"):
|
||||
raise ValueError(f"unknown prestep {prestep!r}")
|
||||
if points not in POINT_RULES:
|
||||
raise ValueError(f"unknown points rule {points!r}")
|
||||
if direction not in DIRECTIONS:
|
||||
raise ValueError(f"unknown direction {direction!r}")
|
||||
if points == "shape" and scale not in ("derived3", "derived3_rho", "derived3_m"):
|
||||
# the triple only exists for the three-point scale; selecting one under a
|
||||
# two-point scale would change nothing and hide that
|
||||
raise ValueError("points='shape' requires a three-point scale (derived3*)")
|
||||
if prestep == "slope" and scale not in ("derived", "derived3"):
|
||||
# there is no slope to judge under a chosen or bracket scale; judging something
|
||||
# else would hide that, so the call fails instead
|
||||
@@ -501,11 +551,15 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
|
||||
sg.credit = endowment
|
||||
forced = 0
|
||||
pre_refused = 0
|
||||
triple_changed = 0
|
||||
dir_changed = 0
|
||||
slope = 0.0
|
||||
slope_prev = None
|
||||
if stats is not None:
|
||||
stats['forced'] = 0
|
||||
stats['pre_refused'] = 0
|
||||
stats['triple_changed'] = 0
|
||||
stats['dir_changed'] = 0
|
||||
|
||||
while abs(B - A) > xtol:
|
||||
if sg.bisect_before_step(abs(B - A)):
|
||||
@@ -516,11 +570,38 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
|
||||
sg.note_step(x_new - x)
|
||||
else:
|
||||
far = B if abs(B - x) >= abs(A - x) else A
|
||||
if direction == "dissimilar":
|
||||
# PREREG 2026-09-21, mode B: the far end read by SIMILARITY instead of
|
||||
# geometry — the end whose |f| is larger is the one LESS similar to zero,
|
||||
# i.e. the one the root is further from. Two values already in hand are
|
||||
# compared; no constant.
|
||||
far_sim = B if abs(FB) >= abs(FA) else A
|
||||
if far_sim != far:
|
||||
dir_changed += 1
|
||||
far = far_sim
|
||||
if scale in ("derived", "derived_rho"):
|
||||
slope = ((fx - f_prev) / (x - x_prev)) if x != x_prev else 0.0
|
||||
sc = abs(slope) * abs(far - x)
|
||||
elif scale in ("derived3", "derived3_rho", "derived3_m"):
|
||||
slope = _slope3(x, fx, x_prev, f_prev, x_prev2, f_prev2)
|
||||
if points == "shape":
|
||||
# PREREG 2026-09-21, mode A: which THREE points the model is built
|
||||
# on, chosen by the SHAPE of the configuration instead of by recency.
|
||||
# For a linear f the position ratio and the value ratio coincide, so
|
||||
# |phi - xi| is a dimensionless measure of how unlike a linear
|
||||
# configuration a triple is; the triple minimising it is SELECTED
|
||||
# (the corpus rule is select, never weight). argmin over the declared
|
||||
# candidates needs no threshold.
|
||||
pick = _pick_triple(x, fx, x_prev, f_prev, x_prev2, f_prev2,
|
||||
A, FA, B, FB, far)
|
||||
if pick is None:
|
||||
slope = _slope3(x, fx, x_prev, f_prev, x_prev2, f_prev2)
|
||||
else:
|
||||
(xu, fu), (xv, fv) = pick
|
||||
if xu != x_prev or xv != x_prev2:
|
||||
triple_changed += 1
|
||||
slope = _slope3(x, fx, xu, fu, xv, fv)
|
||||
else:
|
||||
slope = _slope3(x, fx, x_prev, f_prev, x_prev2, f_prev2)
|
||||
sc = abs(slope) * abs(far - x)
|
||||
if scale == "derived3_m":
|
||||
# REFUTED as an improvement - kept for reproducibility.
|
||||
@@ -665,7 +746,7 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
|
||||
if x_new == x or x_new == x_prev:
|
||||
# the bracket can no longer be split in floating point
|
||||
if stats is not None:
|
||||
stats['forced'] = forced; stats['pre_refused'] = pre_refused
|
||||
stats['forced'] = forced; stats['pre_refused'] = pre_refused; stats['triple_changed'] = triple_changed; stats['dir_changed'] = dir_changed
|
||||
return x_new, g.n, abs(B - A) <= xtol
|
||||
|
||||
w_before = abs(B - A)
|
||||
@@ -677,7 +758,7 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
|
||||
x, fx = x_new, fnew
|
||||
if fnew == 0.0:
|
||||
if stats is not None:
|
||||
stats['forced'] = forced; stats['pre_refused'] = pre_refused
|
||||
stats['forced'] = forced; stats['pre_refused'] = pre_refused; stats['triple_changed'] = triple_changed; stats['dir_changed'] = dir_changed
|
||||
return x_new, g.n, True
|
||||
if FA * fnew < 0.0:
|
||||
B, FB = x_new, fnew
|
||||
@@ -686,9 +767,9 @@ def pure_root(f, a, b, xtol, maxfev, similarity="ratio", scale="derived",
|
||||
if guard == "credit":
|
||||
sg.note_shrink(w_before, abs(B - A))
|
||||
if stats is not None:
|
||||
stats['forced'] = forced; stats['pre_refused'] = pre_refused
|
||||
stats['forced'] = forced; stats['pre_refused'] = pre_refused; stats['triple_changed'] = triple_changed; stats['dir_changed'] = dir_changed
|
||||
return 0.5 * (A + B), g.n, True
|
||||
except _Budget:
|
||||
if stats is not None:
|
||||
stats['forced'] = forced; stats['pre_refused'] = pre_refused
|
||||
stats['forced'] = forced; stats['pre_refused'] = pre_refused; stats['triple_changed'] = triple_changed; stats['dir_changed'] = dir_changed
|
||||
return x, g.n, False
|
||||
|
||||
File diff suppressed because one or more lines are too long
+38
-2
@@ -111,10 +111,13 @@ def make_method(spec):
|
||||
sim, scale, guard = spec[1], spec[2], spec[3]
|
||||
space = spec[4] if len(spec) > 4 else "x"
|
||||
prestep = spec[5] if len(spec) > 5 else "none"
|
||||
points = spec[6] if len(spec) > 6 else "recent"
|
||||
direction = spec[7] if len(spec) > 7 else "distance"
|
||||
|
||||
def m(f, a, b, xtol, maxfev):
|
||||
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)
|
||||
return m
|
||||
|
||||
|
||||
@@ -124,6 +127,10 @@ def label(spec):
|
||||
tag = "pure"
|
||||
if len(spec) > 4 and spec[4] != "x":
|
||||
tag = spec[4]
|
||||
if len(spec) > 6 and spec[6] != "recent":
|
||||
tag = f"{tag}+shape"
|
||||
if len(spec) > 7 and spec[7] != "distance":
|
||||
tag = f"{tag}+dissim"
|
||||
if len(spec) > 5 and spec[5] != "none":
|
||||
# name the gate by WHICH gate it is: "progress" is a measured negative and must
|
||||
# never be confused with "slope" in a results file
|
||||
@@ -191,6 +198,23 @@ def build_grid_log(smoke):
|
||||
for sim in sims for sc in ("derived", "derived3") for gu in guards]
|
||||
|
||||
|
||||
def build_grid_points(smoke):
|
||||
"""Mode A (PREREG 2026-09-21): the triple chosen by shape, on top of the slope gate that
|
||||
already reached the project target. Three-point scale only — a triple must exist."""
|
||||
sims = ["ratio", "sqrt"] if smoke else sorted(PURE_SIMS)
|
||||
guards = ("credit", "project") if smoke else GUARD_MODES
|
||||
return [("pure", sim, "derived3", gu, "x", "slope", "shape", "distance")
|
||||
for sim in sims for gu in guards]
|
||||
|
||||
|
||||
def build_grid_direction(smoke):
|
||||
"""Mode B (PREREG 2026-09-21): the far end read by dissimilarity, on top of the slope gate."""
|
||||
sims = ["ratio", "sqrt"] if smoke else sorted(PURE_SIMS)
|
||||
guards = ("credit", "project") if smoke else GUARD_MODES
|
||||
return [("pure", sim, sc, gu, "x", "slope", "recent", "dissimilar")
|
||||
for sim in sims for sc in ("derived", "derived3") for gu in guards]
|
||||
|
||||
|
||||
def build_grid_slope(smoke):
|
||||
"""The slope-stability gate grid (PREREG 2026-09-21). Only derived scales: there must be
|
||||
a slope to judge. Baselines are not recomputed — they are in sweep_full.json."""
|
||||
@@ -231,7 +255,11 @@ def main():
|
||||
logmode = "--log" in sys.argv # the u-space grid, written to its OWN file
|
||||
gatemode = "--gate" in sys.argv # the pre-step gate grid, likewise
|
||||
slopemode = "--slope" in sys.argv # the slope-stability gate grid
|
||||
specs = (build_grid_slope(smoke) if slopemode else
|
||||
pointsmode = "--points" in sys.argv # mode A: triple by shape
|
||||
dirmode = "--direction" in sys.argv # mode B: far end by dissimilarity
|
||||
specs = (build_grid_points(smoke) if pointsmode else
|
||||
build_grid_direction(smoke) if dirmode else
|
||||
build_grid_slope(smoke) if slopemode else
|
||||
build_grid_gate(smoke) if gatemode else
|
||||
build_grid_log(smoke) if logmode else build_grid(smoke))
|
||||
t0 = time.time()
|
||||
@@ -249,6 +277,14 @@ def main():
|
||||
|
||||
# the published sweep_full.json is what verify_article.py checks the paper
|
||||
# against, so the log grid never writes into it
|
||||
if pointsmode or dirmode:
|
||||
base = "sweep_points" if pointsmode else "sweep_direction"
|
||||
out = f"{base}_smoke.json" if smoke else f"{base}.json"
|
||||
with open(out, "w", encoding="utf8") as fh:
|
||||
json.dump(results, fh)
|
||||
print(f"wrote {out} ({time.time() - t0:.0f}s total)", flush=True)
|
||||
report(results)
|
||||
return
|
||||
if slopemode:
|
||||
out = "sweep_slope_smoke.json" if smoke else "sweep_slope.json"
|
||||
with open(out, "w", encoding="utf8") as fh:
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,106 @@
|
||||
"""Gates P1-P5 for the two new places similarity is read (PREREG_points_and_direction_2026-09-21).
|
||||
|
||||
mode A points="shape" — WHICH three points the model is built on, chosen by the shape
|
||||
of the configuration (argmin |phi - xi|) instead of by recency
|
||||
mode B direction="dissimilar" — WHICH end is "far", read as the end whose |f| is larger
|
||||
(less similar to zero) instead of the geometrically distant one
|
||||
|
||||
Inertness is a declared failure of the EXPERIMENT, not a pass: if a mode changes nothing, its
|
||||
numbers are not evidence either way, so the counters are pinned here.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import math
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from simroot import pure as P
|
||||
|
||||
SMOOTH = [
|
||||
(lambda t: t ** 3 - 2.0 * t - 5.0, 2.0, 3.0),
|
||||
(lambda t: math.exp(t) - 5.0, 0.0, 3.0),
|
||||
(lambda t: t ** 2 - 2.0, 0.5, 2.0),
|
||||
(lambda t: math.cos(t) - t, 0.0, 1.0),
|
||||
(lambda t: t ** 3 - t + 0.2, -2.0, 2.0),
|
||||
]
|
||||
|
||||
|
||||
def test_P1_defaults_are_bit_identical_to_the_published_path():
|
||||
for f, a, b in SMOOTH:
|
||||
for guard in ("credit", "project", "safe", "step"):
|
||||
base = P.pure_root(f, a, b, 1e-12, 300, similarity="ratio",
|
||||
scale="derived3", guard=guard)
|
||||
same = P.pure_root(f, a, b, 1e-12, 300, similarity="ratio", scale="derived3",
|
||||
guard=guard, points="recent", direction="distance")
|
||||
assert base == same, (guard, base, same)
|
||||
|
||||
|
||||
def test_P2_neither_rule_carries_a_constant():
|
||||
src = inspect.getsource(P._pick_triple) + inspect.getsource(P._shape_defect)
|
||||
code = "\n".join(line.split("#", 1)[0] for line in src.splitlines())
|
||||
literals = set(re.findall(r"(?<![\w.])\d+\.\d+(?![\w.])", code))
|
||||
assert literals == set(), f"constants in the triple rule: {literals}"
|
||||
body = inspect.getsource(P.pure_root)
|
||||
start = body.index('if direction == "dissimilar":')
|
||||
end = body.index('if scale in ("derived", "derived_rho")', start)
|
||||
dcode = "\n".join(line.split("#", 1)[0] for line in body[start:end].splitlines())
|
||||
assert set(re.findall(r"(?<![\w.])\d+\.\d+(?![\w.])", dcode)) == set()
|
||||
|
||||
|
||||
def test_P3_the_counters_report_whether_a_mode_is_inert():
|
||||
changed = 0
|
||||
for f, a, b in SMOOTH:
|
||||
st = {}
|
||||
_x, _n, ok = P.pure_root(f, a, b, 1e-12, 300, similarity="ratio", scale="derived3",
|
||||
guard="credit", points="shape", stats=st)
|
||||
assert ok and "triple_changed" in st
|
||||
changed += st["triple_changed"]
|
||||
assert changed >= 1, "mode A never changed the triple on any probe problem — inert"
|
||||
|
||||
dchanged = 0
|
||||
for f, a, b in SMOOTH:
|
||||
st = {}
|
||||
_x, _n, ok = P.pure_root(f, a, b, 1e-12, 300, similarity="ratio", scale="derived3",
|
||||
guard="credit", direction="dissimilar", stats=st)
|
||||
assert ok and "dir_changed" in st
|
||||
dchanged += st["dir_changed"]
|
||||
assert dchanged >= 1, "mode B never changed the far end — inert"
|
||||
|
||||
|
||||
def test_P4_degenerate_triples_are_dropped_not_divided_by():
|
||||
assert P._shape_defect(1.0, 1.0, 2.0, 5.0, 2.0, 7.0) is None # equal abscissae
|
||||
assert P._shape_defect(1.0, 1.0, 2.0, 5.0, 3.0, 5.0) is None # equal values
|
||||
d = P._shape_defect(1.0, 1.0, 0.0, 0.0, 2.0, 2.0) # exactly linear
|
||||
assert d is not None and d == pytest.approx(0.0)
|
||||
assert P._pick_triple(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0) is None
|
||||
|
||||
|
||||
def test_P5_no_mode_adds_an_evaluation():
|
||||
for f, a, b in SMOOTH:
|
||||
st = {}
|
||||
_x, n, ok = P.pure_root(f, a, b, 1e-12, 300, similarity="ratio", scale="derived3",
|
||||
guard="credit", points="shape", direction="dissimilar",
|
||||
stats=st)
|
||||
assert ok
|
||||
assert st["triple_changed"] <= n - 2 and st["dir_changed"] <= n - 2, (st, n)
|
||||
|
||||
|
||||
def test_modes_are_validated():
|
||||
with pytest.raises(ValueError):
|
||||
P.pure_root(lambda t: t, -1.0, 1.0, 1e-10, 100, points="whatever")
|
||||
with pytest.raises(ValueError):
|
||||
P.pure_root(lambda t: t, -1.0, 1.0, 1e-10, 100, direction="whatever")
|
||||
with pytest.raises(ValueError): # a triple needs the three-point scale
|
||||
P.pure_root(lambda t: t, -1.0, 1.0, 1e-10, 100, scale="derived", points="shape")
|
||||
|
||||
|
||||
def test_both_modes_still_solve():
|
||||
for f, a, b in SMOOTH:
|
||||
for kw in ({"points": "shape"}, {"direction": "dissimilar"},
|
||||
{"points": "shape", "direction": "dissimilar"}):
|
||||
x, _n, ok = P.pure_root(f, a, b, 1e-12, 300, similarity="ratio",
|
||||
scale="derived3", guard="credit", **kw)
|
||||
assert ok and abs(f(x)) < 1e-6, (kw, a, b, x)
|
||||
Reference in New Issue
Block a user