shinka-backup / ccevolve /tx_workspace /ac1 /step_evolve.py
JustinTX's picture
Add files using upload-large-folder tool
2facf1f verified
"""
Evolutionary search over step function PROGRAMS, inspired by alphaevolve.
The key insight: alphaevolve evolves CODE that generates step functions.
This means the optimal function might have a specific ALGORITHMIC structure.
Let's search over step function programs parameterized by:
1. Number of steps K
2. Height formula: h_i = f(i, K, params) for some simple formula
3. Width formula: w_i = g(i, K, params)
Examples of formulas:
- h_i = a * sin(b * i/K + c)² (sinusoidal)
- h_i = a * (i/K)^b (power law)
- h_i = a * exp(-b * (i/K - c)²) (Gaussian)
- h_i = a if i in S else 0 (indicator of set S)
"""
import numpy as np
from scipy.optimize import minimize, differential_evolution
import time
def compute_c1_fft(f_values, dx):
f = np.maximum(f_values, 0.0)
N = len(f)
M = 2 * N
fft_f = np.fft.rfft(f, n=M)
conv = np.fft.irfft(fft_f * fft_f, n=M) * dx
integral_sq = (np.sum(f) * dx) ** 2
if integral_sq < 1e-20:
return 1e10
return float(np.max(conv) / integral_sq)
def compute_c1_smooth_and_grad(f, N, dx, alpha=200.0):
M = 2 * N
fft_f = np.fft.rfft(f, n=M)
conv = np.fft.irfft(fft_f * fft_f, n=M) * dx
integral = np.sum(f) * dx
if integral < 1e-15:
return 1e10, np.zeros(N)
integral_sq = integral ** 2
max_val = np.max(conv)
shifted = conv - max_val
mask = shifted > -50.0 / alpha
weights = np.zeros_like(conv)
weights[mask] = np.exp(alpha * shifted[mask])
sum_w = np.sum(weights)
if sum_w < 1e-30:
weights[np.argmax(conv)] = 1.0
sum_w = 1.0
smooth_max = max_val + np.log(sum_w) / alpha
softmax_w = weights / sum_w
c1 = smooth_max / integral_sq
fft_sw = np.fft.rfft(softmax_w, n=M)
fft_fp = np.fft.rfft(f, n=M)
corr = np.fft.irfft(fft_sw * np.conj(fft_fp), n=M)
grad_f = 2.0 * corr[:N] * dx / integral_sq - 2.0 * smooth_max * dx / (integral**3)
return c1, grad_f
def opt(params, N, dx, alpha, maxiter):
def obj(p, a=alpha):
f = p ** 2
c1, g = compute_c1_smooth_and_grad(f, N, dx, a)
return c1, g * 2 * p
result = minimize(obj, params, jac=True, method='L-BFGS-B',
options={'maxiter': maxiter, 'ftol': 1e-16, 'gtol': 1e-15})
return result.x
def full_opt_at_N(f_init, N):
dx = 0.5 / N
params = np.sqrt(np.maximum(f_init, 0.0) + 1e-12)
for alpha in [0.5, 5.0, 50.0, 500.0, 5000.0, 50000.0]:
params = opt(params, N, dx, alpha, 2000)
# Alpha cycling
for _ in range(3):
for alpha in [0.5, 2.0, 10.0]:
params = opt(params, N, dx, alpha, 300)
for alpha in [100.0, 1000.0, 10000.0, 100000.0]:
params = opt(params, N, dx, alpha, 1000)
f_out = params ** 2
return f_out, compute_c1_fft(f_out, dx)
# Generate step functions from various formulas
N_eval = 5000 # Resolution for evaluation
t0 = time.time()
best_c1 = np.inf
best_f = None
# Formula 1: Equally spaced steps with sinusoidal heights
# h_i = A + B * sin(2*pi*freq*i/K + phase)^2
print("=== Sinusoidal step heights ===")
for K in [20, 30, 50, 75, 100]:
for freq in range(1, 10):
bounds = [(0.01, 5.0), (0.01, 5.0), (0, 2*np.pi)] # A, B, phase
def make_f(params, K=K, freq=freq):
A, B, phase = params
i = np.arange(K)
heights = A + B * np.sin(2*np.pi*freq*i/K + phase)**2
heights = np.maximum(heights, 0.0)
# Map to N_eval points
f = np.repeat(heights, N_eval // K + 1)[:N_eval]
return f
def obj(params, K=K, freq=freq):
f = make_f(params, K, freq)
return compute_c1_fft(f, 0.5/N_eval)
result = differential_evolution(obj, bounds, maxiter=200, seed=42, tol=1e-12)
if result.fun < best_c1:
f_step = make_f(result.x)
f_opt, c1 = full_opt_at_N(f_step, N_eval)
if c1 < best_c1:
best_c1 = c1
best_f = f_opt.copy()
print(f" K={K} freq={freq}: C1 = {c1:.10f} ***")
# Formula 2: Power law heights with varying support
print("\n=== Power law + support fraction ===")
for K in [30, 50, 100]:
for power in np.linspace(0.2, 3.0, 8):
for frac in np.linspace(0.3, 1.0, 8):
i = np.arange(K)
heights = (i + 1.0) ** (-power)
# Randomly shuffle (use a specific pattern)
# Actually, let's use different orderings
for ordering in ['asc', 'desc', 'interleave', 'random']:
if ordering == 'asc':
h = np.sort(heights)
elif ordering == 'desc':
h = np.sort(heights)[::-1]
elif ordering == 'interleave':
h_sorted = np.sort(heights)
h = np.zeros_like(h_sorted)
h[::2] = h_sorted[:len(h[::2])]
h[1::2] = h_sorted[len(h[::2]):][::-1]
else:
np.random.seed(42 + K + int(power*10))
h = heights.copy()
np.random.shuffle(h)
# Zero out (1-frac) of the steps
n_active = max(1, int(K * frac))
h[n_active:] = 0.0
f = np.repeat(h, N_eval // K + 1)[:N_eval]
c1_raw = compute_c1_fft(f, 0.5/N_eval)
if c1_raw < 2.0: # Only optimize promising ones
f_opt, c1 = full_opt_at_N(f, N_eval)
if c1 < best_c1:
best_c1 = c1
best_f = f_opt.copy()
print(f" K={K} power={power:.1f} frac={frac:.1f} {ordering}: C1 = {c1:.10f} ***")
break # Move to next K/power/frac
elapsed = time.time() - t0
print(f"\nPhase 1 complete: best C1 = {best_c1:.10f} ({elapsed:.0f}s)")
# Formula 3: Binary patterns from number theory
print("\n=== Number-theoretic binary patterns ===")
for p in [31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]:
# Quadratic residues
qr = set()
for a in range(p):
qr.add((a * a) % p)
# Pattern: indicator of QR
h = np.array([1.0 if i in qr else 0.0 for i in range(p)])
f = np.repeat(h, N_eval // p + 1)[:N_eval]
f_opt, c1 = full_opt_at_N(f, N_eval)
if c1 < best_c1:
best_c1 = c1
best_f = f_opt.copy()
print(f" QR p={p}: C1 = {c1:.10f} ***")
# Also try: indicator of non-QR
h_nqr = 1.0 - h
h_nqr[0] = 0.0 # 0 is neither QR nor NQR
f = np.repeat(h_nqr, N_eval // p + 1)[:N_eval]
f_opt, c1 = full_opt_at_N(f, N_eval)
if c1 < best_c1:
best_c1 = c1
best_f = f_opt.copy()
print(f" NQR p={p}: C1 = {c1:.10f} ***")
# Try with height proportional to Legendre symbol value
for h_val in [0.5, 1.0, 2.0]:
h_leg = np.zeros(p)
for i in range(p):
if i in qr and i > 0:
h_leg[i] = h_val
elif i > 0:
h_leg[i] = 1.0
f = np.repeat(h_leg, N_eval // p + 1)[:N_eval]
f_opt, c1 = full_opt_at_N(f, N_eval)
if c1 < best_c1:
best_c1 = c1
best_f = f_opt.copy()
print(f" Leg p={p} h={h_val}: C1 = {c1:.10f} ***")
elapsed = time.time() - t0
print(f"\nFinal best: C1 = {best_c1:.10f} ({elapsed:.0f}s)")
print(f"Score: {1.5052939684401607 / best_c1:.10f}")
# Compare with existing
f_existing = np.load('/workspace/best_f_5000.npy')
c1_existing = compute_c1_fft(f_existing, 0.5/len(f_existing))
print(f"Existing best at N=5000: C1 = {c1_existing:.10f}")
if best_c1 < c1_existing:
np.save('/workspace/best_f_5000_evolve.npy', best_f)
print("NEW BEST! Saved.")