Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 48 additions & 8 deletions pinn/pinn_1d.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import torch.optim as optim
import numpy as np
from enum import Enum
import warnings
from utils import parse_args, get_activation, print_args, save_frame, make_video_from_frames
from utils import is_notebook, cleanfiles, fourier_analysis, get_scheduler_generator, scheduler_step
# from SOAP.soap import SOAP
Expand Down Expand Up @@ -126,21 +127,60 @@ def set_pde(self, pde: PDE):


# %%
class FourierEmbedding(nn.Module):
def __init__(self, dim_inputs:int, half_dim_output:int, sigma:int=5):
"""
Fourier Features Embedding for the input data. This can help learning high frequency functions.
Args:
dim_inputs: Dimension of the input data
half_dim_outputs: The output dimension is 2*half_dim_output.
sigma: Scaling factor for the frequencies. Recommended is [1, 10]
Ref: https://arxiv.org/abs/2006.10739
"""
super().__init__()
self.sigma = sigma
m = half_dim_output # number of frequencies pairs (cos, sin)
B = torch.rand(m, dim_inputs) * sigma
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be Gaussian not rand

Copy link

Copilot AI Jun 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reference specifies sampling B from a Gaussian distribution, but torch.rand uses a uniform distribution. Replace with torch.randn(m, dim_inputs) * sigma for correct Gaussian sampling.

Suggested change
B = torch.rand(m, dim_inputs) * sigma
B = torch.randn(m, dim_inputs) * sigma

Copilot uses AI. Check for mistakes.
self.B = nn.Parameter(B, requires_grad=False) # fixed frequencies coefficients

def forward(self, x):
"""
x: Tensor of shape [batch_size, dim_inputs]
"""
if len(x.size()) == 1:
x = x.unsqueeze(0) # X mush be 2D
Bxs = torch.einsum("BD, MD-> BM", x, self.B)
sin_xs = torch.sin(Bxs) # (B, M)
cos_xs = torch.cos(Bxs) # (B, M)
feature = torch.cat([cos_xs, sin_xs], dim=-1) # (B, 2M)
return feature

# Define one level NN
class Level(nn.Module):
def __init__(self, dim_inputs, dim_outputs, dim_hidden: list,
act: nn.Module = nn.Tanh()) -> None:
act: nn.Module = nn.Tanh(), fourier_embedding_sigma:int=None) -> None:
"""Simple neural network with linear layers and non-linear activation function
This class is used as universal function approximate for the solution of
partial differential equations using PINNs
"""
super().__init__()
self.dim_inputs = dim_inputs
self.dim_outputs = dim_outputs
# multi-layer MLP
layer_dim = [dim_inputs] + dim_hidden + [dim_outputs]
self.linear = nn.ModuleList([nn.Linear(layer_dim[i], layer_dim[i + 1])
for i in range(len(layer_dim) - 1)])
if fourier_embedding_sigma is not None:
# Check output dim is divisible by 2
if dim_hidden[0] != 2 * dim_hidden[0]//2:
Comment thread
stevengogogo marked this conversation as resolved.
dim_hidden[0] = 2 * dim_hidden[0]//2
warnings.warn(f"dim_hidden[0] is changed to {dim_hidden[0]} to be divisible by 2 for Fourier embedding.")
# Fourier embedding
layer_dim = dim_hidden + [dim_outputs]
layer_fourier = FourierEmbedding(dim_inputs=dim_inputs, half_dim_output=dim_hidden[0]//2,sigma=fourier_embedding_sigma)
layers = [layer_fourier]
layers.extend([nn.Linear(layer_dim[i], layer_dim[i + 1]) for i in range(len(layer_dim) - 1)])
else:
layer_dim = [dim_inputs] + dim_hidden + [dim_outputs]
# multi-layer MLP
layers = [nn.Linear(layer_dim[i], layer_dim[i + 1]) for i in range(len(layer_dim) - 1)]
self.linear = nn.ModuleList(layers)
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to find a way to make this part more comprehensible.

Basically, if fourier embedding is enabled with output dimension 2m=hidden. The dimension of m is defined by first hidden layer argument.

Fourier layer: dim_input -> 2m
First linear: hidden -> hidden

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe there is a better way to ddefine this like

FourierLayer(input, output, sigma)

but how to make argument simple is challenging

# activation function
self.act = act

Expand All @@ -165,12 +205,12 @@ class LevelStatus(Enum):
# Define multilevel NN
class MultiLevelNN(nn.Module):
def __init__(self, mesh: Mesh, num_levels: int, dim_inputs, dim_outputs, dim_hidden: list,
act: nn.Module = nn.ReLU(), enforce_bc: bool = False) -> None:
act: nn.Module = nn.ReLU(), enforce_bc: bool = False, fourier_embedding_sigma:int=None) -> None:
super().__init__()
self.mesh = mesh
# currently the same model on each level
self.models = nn.ModuleList([
Level(dim_inputs=dim_inputs, dim_outputs=dim_outputs, dim_hidden=dim_hidden, act=act)
Level(dim_inputs=dim_inputs, dim_outputs=dim_outputs, dim_hidden=dim_hidden, act=act, fourier_embedding_sigma= fourier_embedding_sigma)
for _ in range(num_levels)
])
self.dim_inputs = dim_inputs
Expand Down Expand Up @@ -449,7 +489,7 @@ def main(args=None):
dim_inputs=dim_inputs, dim_outputs=dim_outputs,
dim_hidden=args.hidden_dims,
act=get_activation(args.activation),
enforce_bc=args.enforce_bc)
enforce_bc=args.enforce_bc, fourier_embedding_sigma=args.fourier_embedding_sigma)
print(model)
model.to(device)
# Plotting
Expand Down
1 change: 1 addition & 0 deletions pinn/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ def parse_args(args=None):
help="Configuration for learning rate scheduler. "
"Follow https://docs.pytorch.org/docs/stable/optim.html for full list of schedulers. "
"The setting is corresponding to `--scheduler` setting.")
parser.add_argument("--fourier_embedding_sigma", type=float, default=-1, help="Sigma for Fourier embedding. Recommended [1,10] ")
Copy link

Copilot AI Jun 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a default of -1 means the code will treat a negative sigma as 'provided' and apply Fourier embedding with sigma = -1. Consider defaulting to None and checking if fourier_embedding_sigma is not None and fourier_embedding_sigma > 0.

Suggested change
parser.add_argument("--fourier_embedding_sigma", type=float, default=-1, help="Sigma for Fourier embedding. Recommended [1,10] ")
parser.add_argument("--fourier_embedding_sigma", type=float, default=None, help="Sigma for Fourier embedding. Recommended [1,10] ")

Copilot uses AI. Check for mistakes.

args = parser.parse_args(args)

Expand Down