yushuang88's picture
Upload folder using huggingface_hub
eae86f2 verified
Raw History Blame Contribute Delete
5.15 kB
"""Independent PyTorch implementation of Appendix C.1, equations C.1--C.7.
The jet path is forward automatic differentiation carrying value, dx, dy and
Laplacian. Parameter derivatives are reverse AD through these exact operations;
no finite differences, finite-volume targets or reference values enter the PDE.
"""
import math
from typing import Tuple
import torch
from torch import Tensor, nn
import torch.nn.functional as F
def jet_swish(z: Tensor) -> Tensor:
a, ax, ay, al = z.unbind(0)
s = torch.sigmoid(a)
sp = s * (1.0 - s)
first = s + a * sp
second = sp * (2.0 + a * (1.0 - 2.0 * s))
return torch.stack((a * s, first * ax, first * ay,
first * al + second * (ax.square() + ay.square())))
def jet_product(a: Tensor, b: Tensor) -> Tensor:
return torch.stack((a[0] * b[0], a[1] * b[0] + a[0] * b[1],
a[2] * b[0] + a[0] * b[2],
a[3] * b[0] + a[0] * b[3]
+ 2.0 * (a[1] * b[1] + a[2] * b[2])))
class Dense(nn.Module):
"""LeCun truncated-normal initialization; layout is [in, out]."""
def __init__(self, nin: int, nout: int, bias: bool = True):
super().__init__()
self.weight = nn.Parameter(torch.empty(nin, nout))
sigma = (1.0 / math.sqrt(nin)) / 0.87962566103423978
nn.init.trunc_normal_(self.weight, std=sigma, a=-2 * sigma, b=2 * sigma)
self.bias = nn.Parameter(torch.zeros(nout)) if bias else None
def forward(self, x: Tensor) -> Tensor:
y = x @ self.weight
if self.bias is not None:
y = y + self.bias
return y
@torch.jit.export
def jet(self, x: Tensor) -> Tensor:
z = x @ self.weight
if self.bias is not None:
z = torch.stack((z[0] + self.bias, z[1], z[2], z[3]))
return z
class ResidualBlock(nn.Module):
def __init__(self, width: int):
super().__init__()
self.first = Dense(width, width)
self.second = Dense(width, width)
self.third = Dense(width, width)
self.alpha = nn.Parameter(torch.zeros(1))
def forward(self, h: Tensor, u: Tensor, v: Tensor) -> Tensor:
f = F.silu(self.first(h))
z = v + f * (u - v)
g = F.silu(self.second(z))
z = v + g * (u - v)
q = F.silu(self.third(z))
return self.alpha * q + (1.0 - self.alpha) * h
@torch.jit.export
def jet(self, h: Tensor, u: Tensor, v: Tensor) -> Tensor:
f = jet_swish(self.first.jet(h))
z = v + jet_product(f, u - v)
g = jet_swish(self.second.jet(z))
z = v + jet_product(g, u - v)
q = jet_swish(self.third.jet(z))
return self.alpha * q + (1.0 - self.alpha) * h
class PseudoTimePINN(nn.Module):
"""BFS PirateNet mapping physical (x,y) to (u,v,p)."""
def __init__(self, width: int = 256, depth: int = 3,
rff_scale: float = 2.0, rff_trainable: bool = True,
activation: str = "swish", output_bias: bool = False,
length: float = 15.0, height: float = 1.0):
super().__init__()
if width % 2 or activation != "swish":
raise ValueError("Table 4 BFS requires an even RFF width and Swish")
self.register_buffer("coordinate_scale", torch.tensor([length, height]))
self.frequencies = nn.Parameter(torch.randn(2, width // 2) * rff_scale,
requires_grad=rff_trainable)
self.gate_u = Dense(width, width)
self.gate_v = Dense(width, width)
self.blocks = nn.ModuleList([ResidualBlock(width) for _ in range(depth)])
self.readout = Dense(width, 3, output_bias)
def forward(self, xy: Tensor) -> Tensor:
phase = (xy / self.coordinate_scale) @ self.frequencies
h = torch.cat((torch.cos(phase), torch.sin(phase)), dim=-1)
u, v = F.silu(self.gate_u(h)), F.silu(self.gate_v(h))
for block in self.blocks:
h = block(h, u, v)
return self.readout(h)
@torch.jit.export
def derivatives(self, xy: Tensor) -> Tensor:
b = self.frequencies / self.coordinate_scale[:, None]
phase = xy @ b
cs, sn = torch.cos(phase), torch.sin(phase)
h0 = torch.cat((cs, sn), -1)
hx = torch.cat((-sn * b[0], cs * b[0]), -1)
hy = torch.cat((-sn * b[1], cs * b[1]), -1)
bnorm = b.square().sum(0)
hl = torch.cat((-cs * bnorm, -sn * bnorm), -1)
h = torch.stack((h0, hx, hy, hl))
u, v = jet_swish(self.gate_u.jet(h)), jet_swish(self.gate_v.jet(h))
for block in self.blocks:
h = block.jet(h, u, v)
return self.readout.jet(h)
@torch.jit.export
def physics(self, xy: Tensor, viscosity: float) -> Tuple[Tensor, Tensor]:
d = self.derivatives(xy)
value, dx, dy, lap = d.unbind(0)
ru = value[:, 0] * dx[:, 0] + value[:, 1] * dy[:, 0] + dx[:, 2] - viscosity * lap[:, 0]
rv = value[:, 0] * dx[:, 1] + value[:, 1] * dy[:, 1] + dy[:, 2] - viscosity * lap[:, 1]
rc = dx[:, 0] + dy[:, 1]
return value, torch.stack((ru, rv, rc), dim=-1)