My code works without error but it doesn't give correct results

parisa71: <p>Hi everyone,<br>
I wrote the following code to solve Laplace equation in a rectangular (200x100) domain using PINN. The bottom, left and right boundaries are no-flow boundaries. Top boundary condition is h=0.05*x+100.<br>
My code works without error but the results aren’t reliable (For example it can’t capture the linear boundary condition on the top or the contour lines aren’t perpendicular to the left, right and bottom boundaries). Do you think where is the problem?<br>
I think it’s related to the gradients computation.<br>
Could you please help me to correct the code?<br>
Thank you!</p>
<pre><code class="lang-auto">import numpy as np
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
x_min, x_max, y_min, y_max = 0, 200, 0, 100
#boundary collocations
y_range, x_range =np.linspace(0,100,102)[1:-1] , np.linspace(0, 200, 202)[1:-1]
y_range.shape
x_zeros = np.zeros(200)
y_zeros = np.zeros(100)
x_ones = np.ones(200)
y_ones = np.ones(100)

x_range = torch.autograd.Variable(torch.from_numpy(x_range).float(), requires_grad=True)
x_zeros = torch.autograd.Variable(torch.from_numpy(x_zeros).float(), requires_grad=True)
x_ones = torch.autograd.Variable(torch.from_numpy(x_ones).float(), requires_grad=True)
y_range = torch.autograd.Variable(torch.from_numpy(y_range).float(), requires_grad=True)
y_zeros = torch.autograd.Variable(torch.from_numpy(y_zeros).float(), requires_grad=True)
y_ones = torch.autograd.Variable(torch.from_numpy(y_ones).float(), requires_grad=True)

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.neurons_per_layer = 60
        self.fc1 = nn.Linear(2, self.neurons_per_layer)
        self.fc2 = nn.Linear(self.neurons_per_layer, self.neurons_per_layer)
        self.fc3 = nn.Linear(self.neurons_per_layer, self.neurons_per_layer)
        self.fc4 = nn.Linear(self.neurons_per_layer, self.neurons_per_layer)
        self.fc5 = nn.Linear(self.neurons_per_layer, self.neurons_per_layer)
        self.fc6 = nn.Linear(self.neurons_per_layer, self.neurons_per_layer)
        self.fc7 = nn.Linear(self.neurons_per_layer, self.neurons_per_layer)
        self.fc8 = nn.Linear(self.neurons_per_layer, 1)
        self.act_func = nn.Tanh()

    def forward(self, x, y):
        inputs = torch.cat([x.reshape(-1, 1), y.reshape(-1, 1)], axis=1)
        output = self.act_func(self.fc1(inputs))
        output = self.act_func(self.fc2(output))
        output = self.act_func(self.fc3(output))
        output = self.act_func(self.fc4(output))
        output = self.act_func(self.fc5(output))
        output = self.act_func(self.fc6(output))
        output = self.act_func(self.fc7(output))
        output = self.fc8(output)
        return output
net = Net()
epochs = 1000
optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)
criterion = torch.nn.MSELoss()
# PDE as a loss function, this function calculates the first and second derivatives, then the PDE-based loss
def f(x, y, net):
    u = net(x, y)
    u_x = torch.autograd.grad(u, x, create_graph=True, retain_graph=True, grad_outputs=torch.ones_like(u))[0]
    u_xx = torch.autograd.grad(u_x, x, create_graph=True, retain_graph=True, grad_outputs=torch.ones_like(u_x))[0]
    u_y = torch.autograd.grad(u, y, create_graph=True, retain_graph=True, grad_outputs=torch.ones_like(u))[0]
    u_yy = torch.autograd.grad(u_y, y, create_graph=True, retain_graph=True, grad_outputs=torch.ones_like(u_y))[0]
    loss_f = u_xx + u_yy
    return loss_f
# Neumann boundary conditions for left, right, and bottom (no-flow boundaries)
def neumann_bc_loss(net, x, y, target_gradient_value):
    # x.requires_grad = True
    # y.requires_grad = True
    u = net(x, y)
    u_y = torch.autograd.grad(u, y, retain_graph=True, grad_outputs=torch.ones_like(u))[0]
    neumann_loss = torch.nn.functional.mse_loss(u_y, target_gradient_value * torch.ones_like(u_y))
    return neumann_loss

def neumann_x_loss(net, x, y, target_gradient_value):
    # x.requires_grad = True
    # y.requires_grad = True
    u = net(x, y)
    u_x = torch.autograd.grad(u, x, retain_graph=True, grad_outputs=torch.ones_like(u))[0]
    neumann_loss_x = torch.nn.functional.mse_loss(u_x, target_gradient_value * torch.ones_like(u_x))
    return neumann_loss_x

neumann_loss_bottom = neumann_bc_loss(net, x_range, x_zeros, 0)
neumann_loss_left = neumann_x_loss(net, y_zeros, y_range, 0)
neumann_loss_right = neumann_x_loss(net, 4*y_ones, y_range, 0)

# Dirichlet boundary condition on the top boundary: h = 0.05 * x + 100
def dirichlet_top_bc_loss(net, x, y):
    u = net(x, y)
    boundary_value = 0.05 * x + 100
    dirichlet_loss = torch.nn.functional.mse_loss(u, boundary_value)
    return dirichlet_loss

drichlet_loss_top = dirichlet_top_bc_loss(net, x_range, 4*x_ones)

x, y = np.linspace(0,200,202)[1:-1] , np.linspace(0, 100, 102)[1:-1]
xv, yv = np.meshgrid(x, y)
a=np.reshape(xv, (20000, 1))
b=np.reshape(yv, (20000, 1))
r = np.hstack((a,b))
x_collocation = r[:,0]
y_collocation = r[:,1]
x_collocation = torch.autograd.Variable(torch.from_numpy(x_collocation).float(), requires_grad=True)
y_collocation = torch.autograd.Variable(torch.from_numpy(y_collocation).float(), requires_grad=True)

losses = []

# Training loop
for epoch in range(epochs):
    optimizer.zero_grad()

    f_out = f(x_collocation, y_collocation, net) # Recalculate f_out inside the loop
    mse_f = criterion(torch.zeros_like(f_out), f_out)

    # Boundary points (no-flow boundaries) - Recalculate boundary losses inside the loop
    neumann_loss_bottom = neumann_bc_loss(net, x_range, x_zeros, 0)
    neumann_loss_left = neumann_x_loss(net, y_zeros, y_range, 0)
    neumann_loss_right = neumann_x_loss(net, 4*y_ones, y_range, 0)

    # Dirichlet condition for the top boundary - Recalculate Dirichlet loss inside the loop
    drichlet_loss_top = dirichlet_top_bc_loss(net, x_range, 4*x_ones) 

    total_loss = mse_f + neumann_loss_bottom + neumann_loss_left + neumann_loss_right + drichlet_loss_top
    losses.append(total_loss.item())

    # Backward and optimize
    total_loss.backward()  
    optimizer.step()

    if epoch % 100 == 0:
        print(f"Epoch {epoch}, Loss: {total_loss.item()}")

# Visualization of the solution over the entire domain
x_test = torch.FloatTensor(np.linspace(x_min, x_max, 100))
y_test = torch.FloatTensor(np.linspace(y_min, y_max, 50))

# Create a meshgrid of x and y
X, Y = torch.meshgrid(x_test, y_test, indexing='ij')

# Flatten the grid for passing through the network
X_flat = X.flatten()
Y_flat = Y.flatten()

# Predict the hydraulic head for all points in the grid
h_test_pred = net(X_flat, Y_flat).detach().numpy()

# Reshape the predictions to the grid shape
H_test_pred = h_test_pred.reshape(X.shape)

# Plot the predicted hydraulic head over the domain
plt.contourf(X, Y, H_test_pred, levels=100, cmap="viridis")
plt.colorbar(label="Hydraulic Head (h)")
plt.xlabel("x")
plt.ylabel("y")
plt.title("Predicted Hydraulic Head Over the Domain")
plt.show()

</code></pre>

albanD: <p>Hey!</p>
<p>I haven’t looked at the math or implementation in details but this is most likely a modeling issue.<br>
Increasing the learning rate to <code>1e-2</code> makes the gradient descent smoothly converge to a stable loss that is constant (completely failing to solve your problem basically).</p>
<p>Also if you have any kind of GPU lying around, I would suggest updating your code to use the cuda device as it will be significantly faster.<br>
You can use <code>x_collocation = torch.from_numpy(x_collocation).to(dtype=torch.float32, device=device).requires_grad_()</code> and similar to do that. Also there is <code>.numpy(force=True)</code> to avoid having to detach/move back to cpu/etc.</p>

parisa71: <p>Thank you very much <a class="mention" href="/u/alband">@albanD</a> for your answer. I tried 1e-2 but the results didn’t improved.</p>

albanD: <p>Ho yeah my comment above didn’t mean that it would improve to give you the right solution. Just that the training problem is “properly solved” to convergence.<br>
But since that does not solve your problem indeed, then I am afraid the problem is with the model or the loss function you’re using.</p>

parisa71: <p>I checked each and every term of the loss function and the main problem is related to the value of the loss function at the top boundary which is very high.</p>