import numpy as np
# EVOLVE-BLOCK-START
def scaling_law_func(data_points, params):
"""
Predict multi-domain losses for 5 domains using a compact
5-feature per-domain linear model (25 parameters total):
f0 = bias
f1 = -log(p_i)
f2 = 1 - p_i
f3 = global pairwise interaction sum
f4 = p_i * (-log(p_i))
"""
X = np.asarray(data_points, dtype=float)
N, D = X.shape
assert D == 5, "Expected 5 mixture proportions"
p = np.asarray(params, dtype=float).ravel()
assert p.size == 25, "Parameter vector length must be 25"
W = p.reshape(5, 5) # 5 features × 5 domains
# numerical safety for log
eps = 1e-6
Xs = np.clip(X, eps, 1.0)
# per-domain local features
neglog = -np.log(Xs) # (N,5)
complement = 1.0 - X # (N,5)
local_int = X * neglog # (N,5)
# global mixture feature: sum_{i<j} p_i * p_j
s1 = X.sum(axis=1) # (N,)
s2 = (X * X).sum(axis=1) # (N,)
pairwise = 0.5 * (s1 * s1 - s2) # (N,)
# linear combination per domain
# W[0]=bias, W[1]=neglog, W[2]=complement, W[3]=pairwise, W[4]=local interaction
preds = (
W[0][None, :] +
W[1][None, :] * neglog +
W[2][None, :] * complement +
W[3][None, :] * pairwise[:, None] +
W[4][None, :] * local_int
)
return preds
def fit_scaling_law(data_points, loss_values):
"""
Fit the 25 parameters via independent ridge regressions per domain
on the 5-feature design:
f0 = 1
f1 = -log(p_i)
f2 = 1 - p_i
f3 = sum_{j<k} p_j * p_k
f4 = p_i * (-log p_i)
"""
X = np.asarray(data_points, dtype=float)
y = np.asarray(loss_values, dtype=float)
N, D = X.shape
assert D == 5, "Expected 5 mixture proportions"
if y.ndim == 1:
y = y[:, None]
assert y.shape == (N, 5), "Expected loss_values shape (N,5)"
# build features
eps = 1e-6
Xs = np.clip(X, eps, 1.0)
neglog = -np.log(Xs) # (N,5)
complement = 1.0 - X # (N,5)
local_int = X * neglog # (N,5)
s1 = X.sum(axis=1) # (N,)
s2 = (X * X).sum(axis=1) # (N,)
pairwise = 0.5 * (s1 * s1 - s2) # (N,)
# placeholder for 5×5 weights
P = np.zeros((5, 5), dtype=float)
base_reg = 1e-6
# fit one small ridge-regression per domain
for i in range(5):
Fi = np.column_stack([
np.ones(N),
neglog[:, i],
complement[:, i],
pairwise,
local_int[:, i]
]) # shape (N,5)
A = Fi.T.dot(Fi)
# scale regularization by trace for stability
reg = base_reg * np.trace(A) / A.shape[0]
A += reg * np.eye(5)
b = Fi.T.dot(y[:, i])
P[:, i] = np.linalg.solve(A, b)
return P.ravel()
# EVOLVE-BLOCK-END