150 lines
5.3 KiB
Python
150 lines
5.3 KiB
Python
"""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)
|