import json, math, random from pathlib import Path import numpy as np import torch from torch import nn SEED = 2043 def seed_all(seed=SEED): random.seed(seed); np.random.seed(seed); torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed) def ego_intersections(n, edges): """Exact n=1 closed ego sets S[u]={u and its neighbors}; undirected edges.""" nbr = [set([i]) for i in range(n)] for u,v in edges: nbr[u].add(v); nbr[v].add(u) stats = {} for u,v in edges: I = nbr[u] & nbr[v] # Formula in prompt, with epsilon only in denominator. C = 2.0*len(I)/(len(nbr[u])+len(nbr[v])+1e-12) r = len(I)/(len(nbr[u])*len(nbr[v])+1e-12) stats[(u,v)] = (sorted(I), C, r, len(nbr[u]), len(nbr[v])) stats[(v,u)] = stats[(u,v)] return nbr, stats def math_verification(): # Prediction 1: for a regular edge, adding t common neighbors gives # C=(2(t+2))/(2(k+1))=(t+2)/(k+1), hence slope 1/(k+1). k=6 rows=[] for t in range(0, k-1): # endpoints have k-1 other neighbors, t shared among them, plus edge. # Build only sets directly to avoid irrelevant graph edges. su=set(range(k+1)); sv={k+1}; sv.update(range(0,t+1)); sv.add(k+2) # normalize desired sizes: endpoint sets each k+1 and intersection is edge endpoints + t su={0}|set(range(2,k+1)); sv={1}|set(range(2, t+2))|set(range(k+2, 2*k-t+1)) # u=0, v=1; force both sizes k+1 and common {0,1,2..t+1} # easier use abstract sets with exact sizes su={0,1}|set(range(2,t+2))|set(range(100,100+(k-1-t))) sv={0,1}|set(range(2,t+2))|set(range(200,200+(k-1-t))) C=2*len(su&sv)/(len(su)+len(sv)+1e-12) pred=(t+2)/(k+1) rows.append((t,C,pred)) max_err=max(abs(x-y) for _,x,y in rows) slope=np.polyfit([t for t,_,_ in rows],[c for _,c,_ in rows],1)[0] slope_pred=1/(k+1) # Prediction 2: alpha=0.5 at C*=-(a0+a2*r)/a1 and is increasing in C. a0,a1,a2=-1.2,4.0,0.7 r=0.08 cstar=-(a0+a2*r)/a1 Cs=np.linspace(0,1,101) alphas=1/(1+np.exp(-(a0+a1*Cs+a2*r))) crossing=float(Cs[np.argmin(abs(alphas-.5))]) monotone=bool(np.all(np.diff(alphas)>0)) # Prediction 3: for fixed ordinary o and overlap q, update is affine in alpha, # with endpoint values o and q and slope q-o. o=np.array([1.3,-0.4,0.7]); q=np.array([-0.2,0.8,0.1]) aa=np.linspace(0,1,11) updates=np.array([(1-x)*o+x*q for x in aa]) fit=np.polyfit(aa, updates[:,0], 1)[0] predicted_slope=float(q[0]-o[0]) return { 'closure_sweep': {'k':k,'rows':rows,'max_abs_error':float(max_err), 'observed_slope':float(slope),'predicted_slope':float(slope_pred)}, 'gate_sweep': {'a0':a0,'a1':a1,'a2':a2,'r':r,'predicted_C_at_alpha_half':float(cstar), 'observed_C_at_alpha_half':crossing,'monotone_in_C':monotone, 'alpha_at_0':float(alphas[0]),'alpha_at_1':float(alphas[-1])}, 'update_sweep': {'predicted_slope_coordinate0':predicted_slope,'observed_slope_coordinate0':float(fit), 'endpoint_error':float(np.max(np.abs(updates[0]-o))+np.max(np.abs(updates[-1]-q)))} } def make_graph(n=48, p_in=.30, p_out=.045, triangle_bias=0.55): y=np.array([i < n//2 for i in range(n)],dtype=np.int64) edges=[] rng=np.random.RandomState(SEED+int(1000*p_in)) for i in range(n): for j in range(i+1,n): p=p_in if y[i]==y[j] else p_out if rng.rand()