| name | shock-capturing-neural-operators |
| description | Architectures and techniques for neural operators on discontinuous PDE solutions (shocks, contact discontinuities, steep gradients). Covers local-global spectral design (ShockFNO), reflection padding for non-periodic BCs, resolution scaling for shock width, and frequency-band error diagnostics. Use for low-viscosity Burgers, compressible Euler, Riemann problems, or any PDE where standard FNO produces Gibbs oscillations. |
| category | physics |
| version | 1.0.0 |
| author | Synthetic Sciences |
| license | MIT |
| tags | ["Neural Operator","FNO","Shocks","Gibbs","Discontinuity","Boundary Conditions","Spectral"] |
| dependencies | ["torch>=2.1.0","numpy>=1.24.0"] |
Shock-Capturing Neural Operators
When to Use
- PDE solutions with shocks, contact discontinuities, or steep gradients
- Low-viscosity Burgers, compressible Euler, Riemann problems
- Non-periodic boundary conditions (outgoing, transmissive, Dirichlet)
- Any problem where standard FNO produces Gibbs-like oscillations
The Fundamental Problem: Gibbs Phenomenon in FNO
Standard FNO uses FFT → truncate modes → iFFT. For discontinuous functions, Fourier coefficients decay as O(1/k), producing oscillatory artifacts near discontinuities regardless of mode count. This is the Gibbs phenomenon — a mathematical limitation, not a training problem.
Impact: FNO nRMSE degrades 10× going from smooth to shock problems (e.g., Burgers ν=0.1: 2.9e-3 vs ν=0.001: 2.9e-2).
Solution 1: Local-Global Architecture (ShockFNO)
Add a parallel local convolution branch alongside the spectral path. Local conv captures sharp features without spectral artifacts.
class FNOBlock(nn.Module):
"""Gated local-global spectral block."""
def __init__(self, width, modes, local_kernel=7):
super().__init__()
self.spectral = SpectralConv1d(width, width, modes)
self.pointwise = nn.Conv1d(width, width, 1)
self.local_conv = nn.Conv1d(width, width, local_kernel,
padding=local_kernel//2)
self.gate = nn.Parameter(torch.tensor(0.3))
def forward(self, x):
global_out = self.spectral(x) + self.pointwise(x)
local_out = self.local_conv(x)
alpha = torch.sigmoid(.gate)
( - alpha) * global_out + alpha * local_out