import json, math, os import numpy as np import torch from torch import nn SEED=1234 rng=np.random.default_rng(SEED) torch.manual_seed(SEED) try: device=torch.device('cuda' if torch.cuda.is_available() else 'cpu') if device.type=='cuda': torch.empty(1, device=device) except Exception: device=torch.device('cpu') def random_rotations(n, rng): # Haar rotations from QR, with proper determinant a=rng.normal(size=(n,3,3)); out=[] for x in a: q,r=np.linalg.qr(x); q=q@np.diag(np.sign(np.diag(r))) if np.linalg.det(q)<0: q[:,0]*=-1 out.append(q) return np.asarray(out) def lattice_offsets(shells): # shells are squared Euclidean distances on a cubic periodic lattice alloff=[] for d in range(1,shells+1): off=[] for x in range(-d,d+1): for y in range(-d,d+1): for z in range(-d,d+1): if (x,y,z)!=(0,0,0) and x*x+y*y+z*z==d*d: off.append((x,y,z)) alloff.append(off) return alloff def p2(x): return 0.5*(3*x*x-1) def corr_features(u,L,shells=3): # u: [N,3], row-major cubic lattice a=u.reshape(L,L,L,3); chans=[] for offsets in lattice_offsets(shells): c=np.zeros((L,L,L)) for dx,dy,dz in offsets: shifted=np.roll(a,(dx,dy,dz),(0,1,2)) c += p2(np.sum(a*shifted,axis=-1)) c /= len(offsets) chans.append(c) return np.stack(chans,axis=0).astype(np.float32).reshape(-1) def S_order(u): Q=np.einsum('ni,nj->ij',u,u)/len(u) Q=0.5*(3*Q-np.eye(3)) return float(np.linalg.eigvalsh(Q)[-1]) def make_config(L, phase, rng): n=L**3 if phase=='iso': u=rng.normal(size=(n,3)); u/=np.linalg.norm(u,axis=1,keepdims=True) else: d=rng.normal(size=3); d/=np.linalg.norm(d) # Gaussian tangent noise gives a controllable nematic cloud u=d+rng.normal(scale=.23,size=(n,3)); u/=np.linalg.norm(u,axis=1,keepdims=True) # random apolar signs make the physical representation genuinely apolar u*=rng.choice([-1.,1.],size=(n,1)) R=random_rotations(1,rng)[0] return u@R.T def transform(u,R,flip_prob,rng): v=u@R.T signs=np.where(rng.random(len(u))