In [3]:
"""
Title: 1D ODE with Zero Boundary Conditions using PINNs

We solve the boundary value problem:
    y''(x) = -1,  x in (0,1),
with boundary conditions:
    y(0) = 0
    y(1) = 0

Analytical solution:
    y(x) = -x^2/2 + x/2

PINN approach:
    - A small neural network y_theta(x) approximates y(x).
    - We compute y'(x) and y''(x) via automatic differentiation.
    - Residual: y''(x) + 1 = 0
    - Loss = MSE(residual) + MSE(BC at x=0) + MSE(BC at x=1)
"""

import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt

# Use GPU if available for performance
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print("Using device:", device)

class ODE_Net(nn.Module):
    """
    Simple fully-connected network for y(x).
    We'll use 3 layers: input -> hidden -> hidden -> output,
    each hidden with Tanh activation.
    """
    def __init__(self, hidden_units=20):
        super(ODE_Net, self).__init__()
        self.layer1 = nn.Linear(1, hidden_units)
        self.layer2 = nn.Linear(hidden_units, hidden_units)
        self.layer3 = nn.Linear(hidden_units, 1)
        self.activation = nn.Tanh()

    def forward(self, x):
        """
        Forward pass for input x of shape (N,1).
        """
        out = self.activation(self.layer1(x))
        out = self.activation(self.layer2(out))
        out = self.layer3(out)
        return out

def residual(model, x):
    """
    Compute the ODE residual for y''(x) + 1 = 0.

    Args:
        model: The ODE_Net instance
        x: A tensor of shape (N,1) in the interior domain (0,1)

    Returns:
        A tensor of shape (N,1) with the PDE residual.
    """
    # Enable gradient for x
    x.requires_grad_(True)

    # Predict y(x)
    y = model(x)

    # First derivative
    dydx = torch.autograd.grad(
        y, x,
        grad_outputs=torch.ones_like(y),
        create_graph=True
    )[0]

    # Second derivative
    d2ydx2 = torch.autograd.grad(
        dydx, x,
        grad_outputs=torch.ones_like(dydx),
        create_graph=True
    )[0]

    # PDE residual: y''(x) + 1 = 0 -> y''(x) + 1
    res = d2ydx2 + 1.0
    return res

def main():
    # Instantiate the network
    model = ODE_Net().to(device)

    # Setup the optimizer
    optimizer = optim.Adam(model.parameters(), lr=1e-3)
    num_epochs = 5000

    # Generate some sample points in (0,1) for the interior
    N_interior = 50
    x_interior = torch.rand(N_interior, 1, device=device)  # uniform in [0,1]

    # Boundary points
    x_bc0 = torch.tensor([[0.0]], device=device)  # for x=0
    x_bc1 = torch.tensor([[1.0]], device=device)  # for x=1

    for epoch in range(num_epochs):
        model.train()
        optimizer.zero_grad()

        # 1) PDE residual on the interior
        r_interior = residual(model, x_interior)
        loss_res = torch.mean(r_interior**2)

        # 2) Boundary losses: y(0)=0, y(1)=0
        y_bc0 = model(x_bc0)
        y_bc1 = model(x_bc1)
        loss_bc0 = torch.mean(y_bc0**2)  # want y(0)=0
        loss_bc1 = torch.mean(y_bc1**2)  # want y(1)=0

        # 3) Total loss
        loss = loss_res + loss_bc0 + loss_bc1
        loss.backward()
        optimizer.step()

        if epoch % 500 == 0:
            print(f"Epoch {epoch}, Loss: {loss.item():.6e}")

    # --- Evaluate / visualize ---
    model.eval()
    x_test = torch.linspace(0, 1, 100, device=device).unsqueeze(1)
    y_pred = model(x_test).detach().cpu().numpy().flatten()
    x_test_np = x_test.cpu().numpy().flatten()

    # Analytical solution: y(x) = -x^2/2 + x/2
    y_true = -0.5 * x_test_np**2 + 0.5 * x_test_np

    # Plot
    plt.figure(figsize=(8,4))
    plt.plot(x_test_np, y_pred, label="PINN Solution")
    plt.plot(x_test_np, y_true, '--', label="Analytical")
    plt.legend()
    plt.title("1D ODE with Zero BCs: y''(x) = -1")
    plt.show()

if __name__ == "__main__":
    main()
Using device: cpu
Epoch 0, Loss: 1.216961e+00
Epoch 500, Loss: 2.444098e-04
Epoch 1000, Loss: 5.029851e-05
Epoch 1500, Loss: 4.248089e-05
Epoch 2000, Loss: 3.561299e-05
Epoch 2500, Loss: 2.956747e-05
Epoch 3000, Loss: 2.423421e-05
Epoch 3500, Loss: 1.944715e-05
Epoch 4000, Loss: 1.509242e-05
Epoch 4500, Loss: 1.113579e-05
No description has been provided for this image
In [ ]: