Why the constant boundary conditions are changing with time?

parisa71: <p>Hi friends,<br>
I’m trying to solve transient groundwater flow equation using PINNs. I have a rectangular domain 10<em>4 and the boundary conditions and initial conditions are presented below:<br>
x-direction BCs: u(0, y, t) = 5 , u(10, y, t) = 1<br>
y-direction BCs: du/dy = 0 @(x, 0, t)  ,  du/dy = 0 @(x, 4, t)<br>
Initial condition: u(x, y, 0) = -0.4</em>x+5<br>
I wrote the following code for this situation but I saw the water heads in the left and right boundaries are varying with time. Is it physically possible? I think it shouldn’t happen!<br>
I’ve checked the code but I can’t find anything wrong.<br>
Could you please help me find the problem with the code?<br>
import torch<br>
import torch.nn as nn<br>
import torch.optim as optim<br>
import numpy as np<br>
import matplotlib.pyplot as plt</p>
<h1><a name="p-456754-neural-network-model-simple-feedforward-network-1" class="anchor" href="#p-456754-neural-network-model-simple-feedforward-network-1"></a>Neural network model (simple feedforward network)</h1>
<p>class Net(nn.Module):<br>
def <strong>init</strong>(self):<br>
super(Net, self).<strong>init</strong>()<br>
self.fc1 = nn.Linear(3, 50)<br>
self.fc2 = nn.Linear(50, 50)<br>
self.fc3 = nn.Linear(50, 1)</p>
<pre><code>def forward(self, x):
    x = torch.tanh(self.fc1(x))
    x = torch.tanh(self.fc2(x))
    x = self.fc3(x)
    return x
</code></pre>
<h1><a name="p-456754-physics-informed-neural-network-2" class="anchor" href="#p-456754-physics-informed-neural-network-2"></a>Physics-Informed Neural Network</h1>
<p>alpha = 0.05  # hydraulic conductivity per Ss (0.00005/0.001)<br>
mse = nn.MSELoss()</p>
<p>def flow_equation_loss(model, x, y, t):<br>
# Compute prediction<br>
u = model(torch.cat((x, y, t), dim=1))</p>
<pre><code># Gradients w.r.t. inputs (required for PDE)
u_t = torch.autograd.grad(u, t, torch.ones_like(u), create_graph=True, retain_graph=True)[0]
u_x = torch.autograd.grad(u, x, torch.ones_like(u), create_graph=True, retain_graph=True)[0]
u_xx = torch.autograd.grad(u_x, x, torch.ones_like(u_x), create_graph=True, retain_graph=True)[0]
u_y = torch.autograd.grad(u, y, torch.ones_like(u), create_graph=True, retain_graph=True)[0]
u_yy = torch.autograd.grad(u_y, y, torch.ones_like(u_y), create_graph=True, retain_graph=True)[0]

# Flow equation: u_t = alpha * (u_xx + u_yy)
pde_loss = mse(u_t, alpha * (u_xx + u_yy))
return pde_loss
</code></pre>
<p>def boundary_loss(model, x, y, t, u_b):<br>
u_pred = model(torch.cat((x, y, t), dim=1))<br>
return mse(u_pred, u_b)</p>
<p>def neumann_loss(model, x, y, t, target_flux, normal):<br>
u = model(torch.cat((x, y, t), dim=1))<br>
u_y = torch.autograd.grad(u, y, torch.ones_like(u), create_graph=True, retain_graph=True)[0]<br>
flux_loss = mse(u_y * normal, target_flux)<br>
return flux_loss</p>
<p>def train(model, x_interior, y_interior, t_interior,<br>
x_boundary_left, y_boundary_left, t_boundary_left, u_boundary_left,<br>
x_boundary_right, y_boundary_right, t_boundary_right, u_boundary_right,<br>
x_neumann, y_neumann_top, y_neumann_bottom, t_neumann,<br>
x_initial, y_initial, t_initial, u_initial,<br>
optimizer, epochs=5000):</p>
<pre><code>for epoch in range(epochs):
    optimizer.zero_grad()
    
    # PDE loss (interior points)
    pde_loss = flow_equation_loss(model, x_interior, y_interior, t_interior)
    
    # Boundary condition loss
    boundary_loss_left = boundary_loss(model, x_boundary_left, y_boundary_left, t_boundary_left, u_boundary_left)
    boundary_loss_right = boundary_loss(model, x_boundary_right, y_boundary_right, t_boundary_right, u_boundary_right)
    
    # Neumann boundary condition loss for top and bottom (impermeable boundary, zero flux)
    neumann_loss_top = neumann_loss(model, x_neumann, y_neumann_top, t_neumann, torch.zeros_like(x_neumann), normal=-1)
    neumann_loss_bottom = neumann_loss(model, x_neumann, y_neumann_bottom, t_neumann, torch.zeros_like(x_neumann), normal=1)

    # Initial condition loss
    initial_loss = boundary_loss(model, x_initial, y_initial, t_initial, u_initial)
    
    # Total loss
    loss = pde_loss + 100*(boundary_loss_left + boundary_loss_right) + neumann_loss_top + neumann_loss_bottom + initial_loss
    
    # Backpropagation
    loss.backward(retain_graph=True)
    optimizer.step()
    
    if epoch % 500 == 0:
        print(f"Epoch {epoch}, Loss: {loss.item()}")

print("Training completed!")
</code></pre>
<p>def predict(model, x, y, t):<br>
return model(torch.cat((x, y, t), dim=1)).detach().numpy()</p>
<h1><a name="p-456754-training-parameters-3" class="anchor" href="#p-456754-training-parameters-3"></a>Training parameters</h1>
<p>alpha = 0.05  # hydraulic conductivity per Ss (0.00005/0.001)<br>
epochs = 5000<br>
lr = 1e-3  # learning rate</p>
<h1><a name="p-456754-generate-collocation-points-4" class="anchor" href="#p-456754-generate-collocation-points-4"></a>Generate collocation points</h1>
<p>N_interior = 5000  # number of points inside the domain<br>
N_boundary = 1000  # number of points on the boundary<br>
N_initial = 1000   # number of points for initial condition</p>
<h1><a name="p-456754-interior-points-requires_gradtrue-for-differentiation-5" class="anchor" href="#p-456754-interior-points-requires_gradtrue-for-differentiation-5"></a>Interior points (requires_grad=True for differentiation)</h1>
<p>x_interior = 10<em>torch.rand(N_interior, 1, requires_grad=True)<br>
y_interior = 4</em>torch.rand(N_interior, 1, requires_grad=True)<br>
t_interior = torch.rand(N_interior, 1, requires_grad=True)</p>
<h1><a name="p-456754-boundary-points-for-dirichlet-boundaries-left-and-right-with-gradient-tracking-6" class="anchor" href="#p-456754-boundary-points-for-dirichlet-boundaries-left-and-right-with-gradient-tracking-6"></a>Boundary points for Dirichlet boundaries (left and right) with gradient tracking</h1>
<p>x_boundary_left = 10<em>torch.zeros(N_boundary // 2, 1, requires_grad=True)<br>
y_boundary_left = 4</em>torch.rand(N_boundary // 2, 1, requires_grad=True)<br>
t_boundary_left = torch.rand(N_boundary // 2, 1, requires_grad=True)</p>
<p>x_boundary_right = 10<em>torch.ones(N_boundary // 2, 1, requires_grad=True)<br>
y_boundary_right = 4</em>torch.rand(N_boundary // 2, 1, requires_grad=True)<br>
t_boundary_right = torch.rand(N_boundary // 2, 1, requires_grad=True)</p>
<h1><a name="p-456754-boundary-condition-u-5-on-the-left-boundary-u-1-on-the-right-boundary-7" class="anchor" href="#p-456754-boundary-condition-u-5-on-the-left-boundary-u-1-on-the-right-boundary-7"></a>Boundary condition: u = 5 on the left boundary, u = 1 on the right boundary</h1>
<p>u_boundary_left = torch.ones(N_boundary // 2, 1) * 5  # Left boundary at 5°C<br>
u_boundary_right = torch.ones(N_boundary // 2, 1) * 1  # Right boundary at 1°C</p>
<h1><a name="p-456754-boundary-points-for-neumann-boundaries-top-and-bottom-with-gradient-tracking-8" class="anchor" href="#p-456754-boundary-points-for-neumann-boundaries-top-and-bottom-with-gradient-tracking-8"></a>Boundary points for Neumann boundaries (top and bottom) with gradient tracking</h1>
<p>x_neumann = 10<em>torch.rand(N_boundary // 2, 1, requires_grad=True)<br>
y_neumann_top = 4</em>torch.ones(N_boundary // 2, 1, requires_grad=True)<br>
y_neumann_bottom = 4*torch.zeros(N_boundary // 2, 1, requires_grad=True)<br>
t_neumann = torch.rand(N_boundary // 2, 1, requires_grad=True)</p>
<h1><a name="p-456754-initial-condition-ux-y-t0-04x-5-9" class="anchor" href="#p-456754-initial-condition-ux-y-t0-04x-5-9"></a>Initial condition: u(x, y, t=0) = -0.4*x + 5</h1>
<p>x_initial = 10<em>torch.rand(N_initial, 1, requires_grad=True)<br>
y_initial = 4</em>torch.rand(N_initial, 1, requires_grad=True)<br>
t_initial = torch.zeros(N_initial, 1, requires_grad=True)<br>
u_initial = -0.4 * x_initial + 5</p>
<h1><a name="p-456754-initialize-the-neural-network-and-optimizer-10" class="anchor" href="#p-456754-initialize-the-neural-network-and-optimizer-10"></a>Initialize the neural network and optimizer</h1>
<p>model = Net()<br>
optimizer = optim.Adam(model.parameters(), lr=lr)</p>
<h1><a name="p-456754-train-the-model-11" class="anchor" href="#p-456754-train-the-model-11"></a>Train the model</h1>
<p>train(model, x_interior, y_interior, t_interior,<br>
x_boundary_left, y_boundary_left, t_boundary_left, u_boundary_left,<br>
x_boundary_right, y_boundary_right, t_boundary_right, u_boundary_right,<br>
x_neumann, y_neumann_top, y_neumann_bottom, t_neumann,<br>
x_initial, y_initial, t_initial, u_initial, optimizer, epochs)</p>
<h1><a name="p-456754-predict-temperature-distributions-at-10-time-steps-12" class="anchor" href="#p-456754-predict-temperature-distributions-at-10-time-steps-12"></a>Predict temperature distributions at 10 time steps</h1>
<p>time_steps = np.linspace(0, 20, 10)<br>
x_test = np.linspace(0, 10, 200)  # finer mesh<br>
y_test = np.linspace(0, 4, 200)  # finer mesh<br>
X, Y = np.meshgrid(x_test, y_test)</p>
<p>fig, axes = plt.subplots(2, 5, figsize=(20, 8))  # Plotting 10 figures<br>
fig.subplots_adjust(hspace=0.3, wspace=0.3)</p>
<p>for i, t in enumerate(time_steps):<br>
t_test = np.ones_like(X) * t<br>
x_test_tensor = torch.tensor(X.reshape(-1, 1), dtype=torch.float32)<br>
y_test_tensor = torch.tensor(Y.reshape(-1, 1), dtype=torch.float32)<br>
t_test_tensor = torch.tensor(t_test.reshape(-1, 1), dtype=torch.float32)</p>
<pre><code>u_pred = predict(model, x_test_tensor, y_test_tensor, t_test_tensor)
U_pred = u_pred.reshape(200, 200)  # Use finer mesh for plotting

ax = axes.flat[i]
c = ax.contourf(X, Y, U_pred, 20, cmap='hot')
fig.colorbar(c, ax=ax)
ax.set_title(f"Time = {t:.2f}")
ax.set_xlabel("X")
ax.set_ylabel("Y")
</code></pre>
<p>plt.show()</p>