import json import random import numpy as np import torch import torch.nn as nn import torch.nn.functional as F def euler_soft(x, thresholds, temperature=0.1): if x.ndim == 3: x = x[:, None] t = torch.as_tensor(thresholds, device=x.device, dtype=x.dtype) s = torch.sigmoid((x[:, None] - t[None, :, None, None, None]) / temperature) p1 = s.mean((-1, -2, -3)) eh = (s[..., :, 1:] * s[..., :, :-1]).mean((-1, -2, -3)) ev = (s[..., 1:, :] * s[..., :-1, :]).mean((-1, -2, -3)) f = (s[..., :-1, :-1] * s[..., 1:, :-1] * s[..., :-1, 1:] * s[..., 1:, 1:]).mean((-1, -2, -3)) return p1 - eh - ev + f def euler_hard(x, thresholds): if x.ndim == 3: x = x[:, None] vals = [] for v in thresholds: s = (x > v).float() p1 = s.mean((-1, -2, -3)) eh = (s[..., :, 1:] * s[..., :, :-1]).mean((-1, -2, -3)) ev = (s[..., 1:, :] * s[..., :-1, :]).mean((-1, -2, -3)) f = (s[..., :-1, :-1] * s[..., 1:, :-1] * s[..., :-1, 1:] * s[..., 1:, 1:]).mean((-1, -2, -3)) vals.append(p1 - eh - ev + f) return torch.stack(vals, 1) def shapes(n, h=16, seed=0): rng = np.random.default_rng(seed); yy, xx = np.mgrid[:h, :h]; out=[] for _ in range(n): im = np.zeros((h,h), np.float32) if rng.random() < .5: cx,cy=rng.uniform(4,h-4,2); r=rng.uniform(2.5,5.5) im[((xx-cx)**2+(yy-cy)**2) 0 agrees with hard excursion away from threshold ties. x=torch.rand(32,1,12,12)*.8+.1; hard=euler_hard(x,thresholds) errs=[] for T in [.2,.1,.05,.02,.01]: errs.append(float((euler_soft(x,thresholds,T)-hard).abs().mean())) results['temperature_errors']={'T':[.2,.1,.05,.02,.01],'error':errs} # Check 2: constant field has chi density 1 if occupied, 0 otherwise. const=torch.full((3,1,10,11),.9); constv=float(euler_soft(const,[.5],.01).mean()) emptyv=float(euler_soft(torch.zeros_like(const),[.5],.01).mean()) results['constant_field']={'occupied':constv,'empty':emptyv,'predicted':[0.0,0.0]} # Check 3: iid Bernoulli occupancy predicts q-2q^2+q^4. q=.35; g=torch.Generator().manual_seed(11); z=(torch.rand((512,1,48,48),generator=g)