import numpy as np
from scipy.optimize import minimize
# EVOLVE-BLOCK-START
def scaling_law_func(data_points, params):
"""
6-parameter U-shaped model: convex parabola + Cauchy bump
f(x) = a*(x - mu)^2 + d*x + e + b / (1 + (|x - mu|/c)^2)
params = [log_a, mu, b, log_c, d, e]
"""
X = np.asarray(data_points).ravel()
p = np.asarray(params).ravel()
a = np.exp(p[0])
mu = p[1]
b = p[2]
c = np.exp(p[3]) + 1e-12
d = p[4]
e = p[5]
dx = X - mu
# convex parabola + Cauchy-style bump
return a * dx * dx + d * X + e + b / (1.0 + (np.abs(dx) / c)**2)
def fit_scaling_law(data_points, loss_values):
"""
Fit the 6-parameter Cauchy-bump U-shaped model:
1) Quadratic baseline (a, d, e) via least squares on [X^2, X, 1]
2) Bump center mu & amplitude b from largest positive residual
3) Bump width c from IQR of X
4) Multi-start L-BFGS-B to refine all parameters
"""
X = np.asarray(data_points).ravel()
y = np.asarray(loss_values).ravel()
# 1) Quadratic baseline init: y ≈ a*X^2 + d*X + e
A = np.vstack([X**2, X, np.ones_like(X)]).T
coef, *_ = np.linalg.lstsq(A, y, rcond=None)
a0 = max(coef[0], 1e-8)
d0 = coef[1]
e0 = coef[2]
# 2) Residuals for bump init
baseline = a0 * X**2 + d0 * X + e0
resid = y - baseline
idx = np.argmax(resid)
mu0 = X[idx]
b0 = resid[idx]
# 3) Width init via interquartile range
iqr = np.percentile(X, 75) - np.percentile(X, 25)
c0 = max(iqr, 1e-6)
# Pack into params: [log_a, mu, b, log_c, d, e]
p0 = np.array([np.log(a0), mu0, b0, np.log(c0), d0, e0])
# Parameter bounds
bounds = [
(-20, 20), # log_a
(X.min(), X.max()), # mu
(None, None), # b
(-20, 20), # log_c
(None, None), # d
(None, None) # e
]
# Objective: mean squared error
def mse(p):
return np.mean((scaling_law_func(X, p) - y)**2)
# 4) Multi-start L-BFGS-B refinement
best_p = p0.copy()
best_val = mse(best_p)
# one baseline start + several small perturbations
for init in [p0] + [p0 + np.random.randn(6) * 0.1 for _ in range(6)]:
res = minimize(mse, init, method='L-BFGS-B',
bounds=bounds,
options={'maxiter': 1000, 'ftol': 1e-9})
if res.success and res.fun < best_val:
best_val, best_p = res.fun, res.x
return best_p
# EVOLVE-BLOCK-END