Download model/PseudoTimePINN.py from OneScience-Group/PseudoTimePINN-BackwardFacingStep: direct link, hf CLI and curl.
- Browser
- Download file 5.15 kB
-
https://huggingface.co/OneScience-Group/PseudoTimePINN-BackwardFacingStep/resolve/main/model/PseudoTimePINN.py
- Command line
-
hf download hf://OneScience-Group/PseudoTimePINN-BackwardFacingStep/model/PseudoTimePINN.py
-
curl -L -o PseudoTimePINN.py https://huggingface.co/OneScience-Group/PseudoTimePINN-BackwardFacingStep/resolve/main/model/PseudoTimePINN.py
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 | |
| 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 | |
| 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) | |
| 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) | |
| 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) | |