Energy-Derived Nitsche Neural Fields / experiment.py

Mechanism failed

Raw ⬇ ZIP
  1import json, math, random, time
  2import numpy as np
  3import torch
  4from torch import nn
  5
  6SEED=362
  7random.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED)
  8torch.set_default_dtype(torch.float64)
  9device='cuda' if torch.cuda.is_available() else 'cpu'
 10try:
 11    if device=='cuda': torch.cuda.set_device(0)
 12except Exception:
 13    device='cpu'
 14
 15class MLP(nn.Module):
 16    def __init__(self):
 17        super().__init__(); self.net=nn.Sequential(nn.Linear(1,24),nn.Tanh(),nn.Linear(24,24),nn.Tanh(),nn.Linear(24,1))
 18    def forward(self,x): return self.net(x)
 19
 20def deriv(y,x,create=True):
 21    return torch.autograd.grad(y,x,torch.ones_like(y),create_graph=create,retain_graph=True)[0]
 22
 23def make_model(): return nn.ModuleList([MLP().to(device),MLP().to(device)])
 24
 25def eval_u(net,x,need2=False):
 26    x=x.clone().detach().requires_grad_(True); u=net(x); ux=deriv(u,x,True)
 27    if need2: return x,u,ux,deriv(ux,x,True)
 28    return x,u,ux
 29
 30def setup():
 31    # Gauss-like uniform quadrature; exact solution x(1-x), -u''=2.
 32    xL=torch.linspace(0.005,0.495,48,device=device).reshape(-1,1)
 33    xR=torch.linspace(0.505,0.995,48,device=device).reshape(-1,1)
 34    xf=torch.tensor([[0.5]],device=device)
 35    return xL,xR,xf
 36
 37def nitsche_loss(m, xL,xR,xf, gamma=20., ghost=1.):
 38    _,uL,dL=eval_u(m[0],xL); _,uR,dR=eval_u(m[1],xR)
 39    bulk=(0.5*(dL*dL)-2*uL).mean()*0.5 + (0.5*(dR*dR)-2*uR).mean()*0.5
 40    # exterior homogeneous Dirichlet: - n P u + gamma/(2h)u^2, h=.5
 41    xb=torch.tensor([[0.0],[1.0]],device=device)
 42    _,ul,dl=eval_u(m[0],xb[:1]); _,ur,dr=eval_u(m[1],xb[1:])
 43    # n=-1 left, +1 right
 44    ext=(dl*ul + gamma/(1.0)*ul.square()/2 - dr*ur + gamma/(1.0)*ur.square()/2).mean()
 45    # symmetric interface Nitsche for jump uL-uR, average flux; h=.5
 46    _,uiL,diL=eval_u(m[0],xf); _,uiR,diR=eval_u(m[1],xf)
 47    jump=uiL-uiR; avg=.5*(diL+diR)
 48    iface=(-avg*jump + gamma/(1.0)*jump.square()/2).mean()
 49    # p=1 ghost: gamma_A h^(1) [u']^2
 50    g=(ghost*0.5*(diL-diR).square()).mean()
 51    return bulk+ext+iface+g, {'bulk':bulk.item(),'boundary':(ul.square().mean()+ur.square().mean()).item(),'jump':jump.abs().item(),'djump':(diL-diR).abs().item()}
 52
 53def baseline_loss(m,xL,xR,xf):
 54    _,uL,dL,rL=eval_u(m[0],xL,True); _,uR,dR,rR=eval_u(m[1],xR,True)
 55    # standard PINN strong residual and pointwise penalties, including patch interface
 56    loss=(rL.add(2).square().mean()+rR.add(2).square().mean())
 57    xb=torch.tensor([[0.0],[1.0]],device=device)
 58    _,ul,_=eval_u(m[0],xb[:1]); _,ur,_=eval_u(m[1],xb[1:])
 59    _,uiL,diL=eval_u(m[0],xf); _,uiR,diR=eval_u(m[1],xf)
 60    loss=loss+100*(ul.square().mean()+ur.square().mean())+100*(uiL-uiR).square().mean()+1*(diL-diR).square().mean()
 61    return loss, {'boundary':(ul.square().mean()+ur.square().mean()).item(),'jump':(uiL-uiR).abs().item(),'djump':(diL-diR).abs().item()}
 62
 63def metrics(m):
 64    xx=torch.linspace(0,1,401,device=device).reshape(-1,1); vals=[]
 65    for a,b in [(0,.5),(.5,1)]:
 66        q=xx[(xx[:,0]>=a)&(xx[:,0]<=b)].reshape(-1,1); vals.append(m[0 if a==0 else 1](q))
 67    pred=torch.cat(vals).flatten(); q=torch.cat([xx[(xx[:,0]>=0)&(xx[:,0]<=.5)],xx[(xx[:,0]>=.5)&(xx[:,0]<=1)]])
 68    truth=q*(1-q); err=(pred-truth).abs().max().item()
 69    xL,xR,xf=setup(); _,uL,dL=eval_u(m[0],torch.tensor([[0.0]],device=device)); _,uR,dR=eval_u(m[1],torch.tensor([[1.0]],device=device)); _,a,da=eval_u(m[0],xf); _,b,db=eval_u(m[1],xf)
 70    return {'max_error':err,'boundary_rms':math.sqrt((uL.square()+uR.square()).mean().item()),'interface_value_jump':abs((a-b).item()),'interface_derivative_jump':abs((da-db).item())}
 71
 72def run(kind,steps=900):
 73    torch.manual_seed(SEED+ (0 if kind=='baseline' else 1)); m=make_model(); opt=torch.optim.Adam(m.parameters(),lr=2e-3)
 74    xL,xR,xf=setup(); curve=[]; t=time.time()
 75    for it in range(steps):
 76        opt.zero_grad(); loss,parts=(baseline_loss(m,xL,xR,xf) if kind=='baseline' else nitsche_loss(m,xL,xR,xf)); loss.backward(); opt.step()
 77        if it in [0,99,299,599,899]: curve.append(float(loss.detach().cpu()))
 78    out=metrics(m); out.update({'loss_curve':curve,'final_objective':float(loss.detach().cpu()),'seconds':time.time()-t})
 79    return out
 80
 81def math_check():
 82    # Constitutive AD identity plus a coercivity scan of the 1D symmetric
 83    # Nitsche quadratic form on u(x)=a*x+b.
 84    x=torch.tensor([[.2]],requires_grad=True)
 85    u=x*x/3; ux=deriv(u,x,True); F=1+ux
 86    W=.5*(F-1).square()
 87    P=torch.autograd.grad(W,F,torch.ones_like(W),retain_graph=True)[0]
 88    fp_err=abs((P-(F-1)).item())
 89    vals={}
 90    for gamma in [0.1,1.,2.,4.,10.,20.]:
 91        def q(v):
 92            aa,bb=v
 93            return .5*aa*aa-(aa*(aa+bb)+aa*bb)+gamma/2*(bb*bb+(aa+bb)**2)
 94        H=np.zeros((2,2)); H[0,0]=q((1,0)); H[1,1]=q((0,1))
 95        H[0,1]=H[1,0]=(q((1,1))-H[0,0]-H[1,1])/2
 96        vals[str(gamma)]=float(np.linalg.eigvalsh(H).min())
 97    jump=1.7-(-.4); ghost_energy=.8*.5*jump**2
 98    return {'F_identity_and_P_error':fp_err,
 99            'nitsche_min_eigenvalue_by_gamma':vals,
100            'ghost_energy_for_derivative_jump':ghost_energy,
101            'ghost_is_nonnegative':bool(ghost_energy>=0)}
102
103if __name__=='__main__':
104    try:
105        chk=math_check(); base=run('baseline'); idea=run('idea')
106    except Exception as e:
107        if device=='cuda':
108            device='cpu'; torch.cuda.empty_cache(); chk=math_check(); base=run('baseline'); idea=run('idea')
109        else: raise
110    print(json.dumps({'device':device,'math_check':chk,'baseline':base,'idea':idea},indent=2))