Reputation: 1
I am working with a Physics-Informed Neural Network (PINN) to solve a system of partial differential equations (PDEs). The model is designed to predict both the real and imaginary parts of the solution, but after training, I am getting multiple values instead of a single solution. I'm unsure how to extract a single solution from the model, given the initial and boundary conditions.
Real Part of the Solution:
[-0.06334841 -0.062509 -0.06163921 -0.06073928 -0.05980941 -0.05884991 -0.05786142 -0.05684437 -0.0557996 -0.05472792 -0.05363033 -0.052508 -0.05136216 -0.05019434 -0.04900604 -0.04779914 -0.04657538 -0.04533695 -0.04408599 -0.04282483 -0.04155596 -0.04028207 -0.03900566 -0.03772958 -0.03645669 -0.03518973 -0.03393145 -0.03268468 -0.0314519 -0.03023547 -0.02903758 -0.0278598 -0.02670354 -0.02556948 -0.02445777 -0.0233678 -0.02229816 -0.02124669 -0.02021031 -0.01918506 -0.01816619 -0.01714829 -0.01612531 -0.01509104 -0.01403899 -0.01296301 -0.0118577 -0.01071849 -0.00954226 -0.00832763 -0.00707515 -0.00578765 -0.00447029 -0.00313038 -0.00177728 -0.00042213 0.00092289 0.00224471 0.00353035 0.00476716 0.0059436 0.00704944 0.00807618 0.00901716 0.00986777 0.01062508 0.01128795 0.01185676 0.01233314 0.01271974 0.01302004 0.01323804 0.01337828 0.01344543 0.01344434 0.01338004 0.01325734 0.01308119 0.0128563 0.01258733 0.01227872 0.01193491 0.01156 0.011158 0.01073269 0.01028769 0.00982638 0.00935204 0.00886751 0.00837567 0.00787903 0.00737998 0.00688063 0.0063829 0.00588859 0.00539917 0.00491606 0.00444037 0.00397322 0.00351539]
Imaginary Part of the Solution:
[ 0.00741371 0.00815623 0.00891515 0.00969044 0.01048192 0.01128931 0.01211199 0.01294932 0.01380036 0.01466383 0.01553827 0.01642179 0.01731227 0.01820713 0.01910323 0.01999709 0.02088475 0.02176158 0.02262247 0.02346156 0.0242725 0.02504807 0.0257806 0.02646159 0.02708185 0.02763153 0.02810027 0.02847715 0.02875073 0.0289094 ... 0.00787903-0.12341416j 0.00737998-0.12441608j 0.00688063-0.12536152j 0.0063829 -0.12625429j 0.00588859-0.12709787j 0.00539917-0.12789568j 0.00491606-0.1286508j 0.00444037-0.1293662j 0.00397322-0.13004452j 0.00351539-0.13068835j]
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
def sech(x):
return 2 / (torch.exp(x) + torch.exp(-x))
class PINN(nn.Module):
def __init__(self, layers):
super(PINN, self).__init__()
self.layers = nn.ModuleList()
for i in range(len(layers) - 1):
self.layers.append(nn.Linear(layers[i], layers[i + 1]))
self.activation = torch.tanh
def forward(self, x):
for i, layer in enumerate(self.layers):
x = layer(x)
if i < len(self.layers) - 1:
x = self.activation(x)
return x
# Initial condition
def initial_condition(x, A=1.0, k=1.0, x0=0.0, omega=0.0):
q_real = A * sech(k * (x - x0)) * torch.cos(omega * x)
q_imag = A * sech(k * (x - x0)) * torch.sin(omega * x)
return q_real, q_imag
# Boundary condition
def boundary_condition(t, A=0.0):
u_bc = torch.full_like(t, A) # Real part at boundary
v_bc = torch.full_like(t, A) # Imaginary part at boundary
return u_bc, v_bc
# Model definition
layers = [2, 50, 50, 50, 2] # 2 outputs for real and imaginary parts
model = PINN(layers).to(device)
# Loss function for PINN (based on the PDE residual)
def pde_loss(x, t):
# Ensure x has requires_grad=True
x.requires_grad = True
t.requires_grad = True # Ensure t has requires_grad=True as well
# Forward pass: Get the model predictions (real and imaginary parts)
q_pred = model(torch.cat([x, t], dim=-1)) # Forward pass
u_pred = q_pred[:, 0] # Real part (first column)
v_pred = q_pred[:, 1] # Imaginary part (second column)
# Calculate second derivatives (u_xx and v_xx)
u_xx = torch.autograd.grad(u_pred, x, grad_outputs=torch.ones_like(u_pred), create_graph=True)[0]
v_xx = torch.autograd.grad(v_pred, x, grad_outputs=torch.ones_like(v_pred), create_graph=True)[0]
# Calculate time derivatives (u_t and v_t)
u_t = torch.autograd.grad(u_pred, t, grad_outputs=torch.ones_like(u_pred), create_graph=True)[0]
v_t = torch.autograd.grad(v_pred, t, grad_outputs=torch.ones_like(v_pred), create_graph=True)[0]
u_abs_sq = u_pred**2 + v_pred**2
v_abs_sq = u_pred**2 + v_pred**2
# Define the PDE residuals
f_r = u_t - 0.5 * u_xx - u_abs_sq * u_pred - v_abs_sq * u_pred
f_i = v_t + 0.5 * v_xx + u_abs_sq * v_pred + v_abs_sq * v_pred
# Compute the loss as the sum of squared residuals
loss = torch.mean(f_r**2 + f_i**2)
return loss
# Test metric (Mean Squared Error)
def test_metric(q_pred, q_true):
return torch.mean((q_pred - q_true)**2)
# Optimizer
optimizer = optim.Adam(model.parameters(), lr=0.0001)
# Training loop
n_iterations = 1000
train_losses = []
test_losses = []
test_metrics = []
for iterations in range(n_iterations):
# Define x and t for the initial and boundary conditions
x = torch.linspace(-5, 5, 100).reshape(-1, 1).to(device)
t = torch.linspace(0, 10, 100).reshape(-1, 1).to(device)
# Apply initial and boundary conditions
u_init, v_init = initial_condition(x)
u_bc, v_bc = boundary_condition(t)
optimizer.zero_grad()
# Compute the PDE loss (Train loss)
loss = pde_loss(x, t)
# Backpropagation
loss.backward()
optimizer.step()
train_losses.append(loss.item())
# Every 100 iterations, print the training loss
if iterations % 100 == 0:
print(f"iterations {iterations}, Train Loss: {loss.item()}")
# Evaluate on test data (at multiple x values, for example)
if iterations % 100 == 0:
x_test = torch.linspace(-5, 5, 100).reshape(-1, 1).to(device) # Test points over the domain
t_test = torch.zeros_like(x_test).to(device) # Test at t = 0
q_pred = model(torch.cat([x_test, t_test], dim=-1)) # Model output
q_real_pred, q_imag_pred = q_pred[:, 0], q_pred[:, 1]
q_pred_full = q_real_pred + 1j * q_imag_pred # Complex solution
# Create the true solution (use exact solution for the test points)
q_real_true, q_imag_true = initial_condition(x_test.cpu()) # Compute on CPU, since we are using CPU tensors here
q_true_full = torch.stack([q_real_true, q_imag_true], dim=1).to(device) # Stack and move to device
# Calculate test loss
test_loss = pde_loss(x_test, t_test).item()
test_losses.append(test_loss)
# Calculate test metric (Mean Squared Error)
test_metric_val = test_metric(q_pred_full, q_true_full).item()
test_metrics.append(test_metric_val)
# Print test metric (MSE)
print(f"iterations {iterations}, Test MSE: {test_metric_val:.6f}")
How can I ensure that the model gives a single, unique solution for both the real and imaginary parts after considering the initial and boundary conditions?
Are there any specific adjustments to the training procedure or model architecture that would help achieve a consistent solution with only one real and one imaginary part, rather than multiple values at each point?
Upvotes: 0
Views: 24