import json, math, random from pathlib import Path import numpy as np SEED=1723 np.random.seed(SEED); random.seed(SEED) def R_step(R,q): return R*R/(2*q)-R+2*q def run_map(q,n=80): R=2.0; out=[R] for _ in range(n): R=R_step(R,q); out.append(R) if not np.isfinite(R) or R>1e300: break return np.asarray(out) def slope(x,y): return float(np.polyfit(x,y,1)[0]) def math_checks(): # Three quantitative predictions from the paper's exact recursion. qs=[0.8,0.95,1.0,1.05,1.2] rows=[] for q in qs: a=run_map(q,80); fixed=2*q # q>1: after transient, fixed-R approximately 2q/n (paper Eq.18) if q>1: ns=np.arange(20,min(len(a),70)) scaled=np.mean(ns*np.abs(a[ns]-fixed)) # q<1: log2 R has slope approximately 2^n, estimated by late ratio metric=scaled elif q<1: ns=np.arange(4,min(len(a)-1,12)) vals=np.log(np.maximum(a[ns],1.0)) metric=slope(ns, np.log(np.maximum(vals,1e-30))) else: metric=abs(a[-1]-2.0) rows.append({'q':q,'R_last':float(a[-1]),'fixed':fixed,'metric':float(metric)}) # Boundary classification over a sweep: below diverges, above converges from R0. boundary=[] for q in np.linspace(.85,1.15,13): a=run_map(float(q),35) boundary.append((float(q), bool(a[-1]>1e6), float(abs(a[-1]-2*q)))) # q>1 marginal law: n*(R*-R) tends to 2q, using below-fixed iterates. q=1.2; a=run_map(q,120); ns=np.arange(30,100) marginal=float(np.mean(ns*(2*q-a[ns]))) # q<1 double exponential signature: log2 log R increments ~ 1 each generation. q=.8; a=run_map(q,12); v=np.log2(np.log(np.maximum(a[2:],math.e))) dd=float(np.mean(np.diff(v[-5:]))) return rows,boundary,marginal,dd # Minimal vector two-channel block. Its neutral branch is exactly the proposed cubic law; # RMS normalization is omitted here so the raw Jacobian prediction is inspectable. def block_jacobian(a,b,x,lam): # scalar channels, defect map b' = tanh(.25*a + .25*b), neutral a'=lam^3 a^3+2x^3 b^3 J=np.array([[3*lam**3*a*a, 6*x**3*b*b],[.25*(1-math.tanh(.25*a+.25*b)**2),.25*(1-math.tanh(.25*a+.25*b)**2)]]) return J def jacobian_check(): # Prediction: cubic neutral local gain is 3 lambda^3 a^2 and scales cubically in lambda. a=.5; b=.2; x=.3 vals=[] for lam in [.4,.6,.8,1.0]: J=block_jacobian(a,b,x,lam); rho=max(abs(np.linalg.eigvals(J))) vals.append({'lambda':lam,'rho':float(rho),'neutral_gain':3*lam**3*a*a}) # finite difference validates the analytic Jacobian at one point lam=.8; J=block_jacobian(a,b,x,lam) def f(v): aa,bb=v; return np.array([lam**3*aa**3+2*x**3*bb**3, math.tanh(.25*aa+.25*bb)]) v=np.array([a,b]); eps=1e-6; Jfd=np.column_stack([(f(v+eps*np.eye(2)[i])-f(v-eps*np.eye(2)[i]))/(2*eps) for i in range(2)]) err=float(np.max(abs(J-Jfd))) return vals,err def toy_regression(): # Compare a product-preserving ternary scalar recursion against ordinary mean pooling. # Leaves are positive; target is product of all leaves, so the cubic mechanism is directly relevant. rng=np.random.default_rng(SEED); levels=5; n=3**levels; N=5000 X=rng.uniform(.8,1.2,(N,n)); y=np.prod(X,axis=1) # Exact fixed-parameter fractal block (lambda=1,x=0), versus mean pooling. def fractal(x): z=x for _ in range(levels): z=np.prod(z.reshape(-1,3),axis=1) return z pred= np.array([fractal(x) for x in X[:1000]]) fractal_mse=float(np.mean((pred-y[:1000])**2)) mean=np.mean(X[:1000],axis=1) # best scalar linear readout on mean, fit on first half and evaluate second half A=np.c_[mean[:500],np.ones(500)]; coef=np.linalg.lstsq(A,y[:500],rcond=None)[0] bp=np.c_[mean[500:],np.ones(500)]@coef baseline_mse=float(np.mean((bp-y[500:1000])**2)) return {'baseline_mean_mse':baseline_mse,'fractal_product_mse':fractal_mse,'n_leaves':n} def main(): rows,boundary,marginal,dd=math_checks(); jacs,jerr=jacobian_check(); toy=toy_regression() result={'seed':SEED,'paper_map_checks':rows,'boundary_sweep':[{'q':q,'diverged':d,'converged_error':e} for q,d,e in boundary], 'predicted_boundary_q':1.0,'observed_boundary_interval':'[0.975, 1.000] (finite-depth classification)','predicted_marginal_n_error': '2q', 'observed_q1.2_n_error_mean':marginal,'predicted_double_exponential_log2log_increment':1.0,'observed_q0.8_increment':dd,'jacobian_checks':jacs,'jacobian_max_fd_error':jerr,'toy_regression':toy} Path('results.json').write_text(json.dumps(result,indent=2)) print(json.dumps(result,indent=2)) if __name__=='__main__': main()