"""The FOURTH out-of-sample corpus for PREREG_close_sub_2026-09-21 - written and committed before the two repairs are implemented and before any method is run on it. Seed 20260922; the function families are NEW (none of corpus_new's closed forms is reused). 40 per class: D1 random polynomial, one simple root in the bracket (others outside) - neutral D2 random polynomial with one MULTIPLE root of odd multiplicity 3..9 - flat side D3 fractional power |x - r|^p sign(x - r) * g(x), p ~ U(0.15, 0.95) - TARGETED (repair 2) D4 sums of exponentials and sines with one root in the bracket - neutral D5 compositions tanh(q(x)), atan(q(x)), log1p(q(x)^2) - c with a cubic q - neutral D6 odd around the root: s(x - r) with s in {sin(k.), (.)+a(.)^3, arcsinh(k.)} - TARGETED (repair 1) Roots are known by construction for D1-D3 and D6; for D4-D5 success is judged by a sign change within 1e-6 (corpus_roots_jl.solved), which is used for every class for uniformity. Brackets: width log-uniform on [0.1, 100], the root at a fraction U(0.02, 0.98). A problem without finite values of opposite sign at the ends is redrawn (redraws reported). """ from __future__ import annotations import math import numpy as np SEED = 20260922 PER_CLASS = 40 def _bracket(rng, r, lo=0.1, hi=100.0): L = math.exp(rng.uniform(math.log(lo), math.log(hi))) u = rng.uniform(0.02, 0.98) return r - u * L, r + (1.0 - u) * L def _ok(f, a, b): try: fa, fb = f(a), f(b) except (OverflowError, ValueError, ZeroDivisionError): return False return (math.isfinite(fa) and math.isfinite(fb) and fa != 0.0 and fb != 0.0 and (fa < 0.0) != (fb < 0.0)) def _poly_from_roots(roots, scale): def f(x, roots=tuple(roots), scale=scale): v = scale for q in roots: v *= (x - q) return v return f def _d1(rng): r = rng.uniform(-1, 1) a, b = _bracket(rng, r) deg = int(rng.integers(3, 10)) others = [] while len(others) < deg - 1: # the other roots outside the bracket, or complex pairs if rng.uniform() < 0.5 and len(others) <= deg - 3: c, d = rng.uniform(-3, 3), rng.uniform(0.1, 3) others.append(("c", c, d)) others.append(None) else: side = a - rng.uniform(0.1, 5) if rng.uniform() < 0.5 else b + rng.uniform(0.1, 5) others.append(("r", side)) others = [o for o in others if o is not None][: deg - 1] def f(x, r=r, others=tuple(others)): v = x - r for o in others: v *= (x - o[1]) if o[0] == "r" else ((x - o[1]) ** 2 + o[2] ** 2) return v return f, r, (a, b) def _d2(rng): r = rng.uniform(-1, 1) m = int(rng.choice([3, 5, 7, 9])) a, b = _bracket(rng, r, hi=10.0) c = rng.uniform(0.5, 3) return (lambda x, r=r, m=m, c=c: (x - r) ** m * (1 + c * x * x)), r, (a, b) def _d3(rng): r = rng.uniform(-1, 1) p = rng.uniform(0.15, 0.95) a0, c = rng.uniform(0, 1), rng.uniform(0.5, 5) a, b = _bracket(rng, r) return (lambda x, r=r, p=p, a0=a0, c=c: math.copysign(abs(x - r) ** p, x - r) * math.exp(a0 * math.sin(c * x))), r, (a, b) def _d4(rng): r = rng.uniform(-1, 1) k1, k2 = rng.uniform(-2, 2), rng.uniform(-2, 2) w, ph = rng.uniform(0.5, 4), rng.uniform(0, 2 * math.pi) a, b = _bracket(rng, r, hi=20.0) def g(x, k1=k1, k2=k2, w=w, ph=ph): return math.exp(k1 * x) + 0.5 * math.exp(k2 * x) + math.sin(w * x + ph) gr = g(r) slope = abs(k1) + abs(k2) + w + 1.0 return (lambda x, g=g, gr=gr, r=r, s=slope: g(x) - gr + 1.5 * s * (x - r)), r, (a, b) def _d5(rng): r = rng.uniform(-1, 1) c1, c2 = rng.uniform(0.5, 3), rng.uniform(-1, 1) kind = int(rng.integers(3)) a, b = _bracket(rng, r, hi=10.0) q = lambda x, r=r, c1=c1, c2=c2: c1 * (x - r) + c2 * (x - r) ** 2 + (x - r) ** 3 if kind == 0: return (lambda x, q=q: math.tanh(q(x))), r, (a, b) if kind == 1: return (lambda x, q=q: math.atan(q(x))), r, (a, b) return (lambda x, q=q: math.copysign(math.log1p(q(x) ** 2), q(x))), r, (a, b) def _d6(rng): r = rng.uniform(-1, 1) k = math.exp(rng.uniform(math.log(0.3), math.log(3))) kind = int(rng.integers(3)) a, b = _bracket(rng, r, hi=3.0 / k if kind == 0 else 100.0) # sin: a single root if kind == 0: return (lambda x, r=r, k=k: math.sin(k * (x - r))), r, (a, b) if kind == 1: aa = rng.uniform(0.1, 2) return (lambda x, r=r, aa=aa: (x - r) + aa * (x - r) ** 3), r, (a, b) return (lambda x, r=r, k=k: math.asinh(k * (x - r))), r, (a, b) def build(): out, redraws = [], {} rng = np.random.default_rng(SEED) for cls, gen in (("D1", _d1), ("D2", _d2), ("D3", _d3), ("D4", _d4), ("D5", _d5), ("D6", _d6)): n = red = 0 while n < PER_CLASS: f, r, (a, b) = gen(rng) if _ok(f, a, b) and min(a, b) < r < max(a, b): out.append((cls, f"{cls}.{n:02d}", f, float(a), float(b), float(r))) n += 1 else: red += 1 redraws[cls] = red return out, redraws if __name__ == "__main__": from collections import Counter probs, red = build() print(len(probs), dict(Counter(p[0] for p in probs)), "redraws", red)