Here is a basic implementation of solving the problem $-\Delta u = f$ using matrix-free methods, which are prefereble for large PDE discretizations. The efficiency could be improved in the Laplacian evaluation and a preconditioner would save lots of time, but this runs in $\approx$ 7 minutes on my machine.
The method used to solve the linear system is conjugate-gradient, which only requires a function that compute matrix-vector products $Av$ for symmetric-positive definite matrices $A$. That is why I had to flip the sign to solve $-\Delta u = f$ to ensure positivity. The nonhomogeneous boundary conditions then have to be moved into the RHS vector. This can be derived from the finite-difference equations for the nodes near the edges of the domain, but it is easier to implement by reshaping u
to be a matrix then just applying the loads on the edges. Also note that the plot doesn't display the boundary conditions exactly since it is only plotting the function values at the interior nodes.
import numpy as np
import matplotlib.pyplot as plt
from scipy.sparse.linalg import LinearOperator, cg
def lap(u):
# apply the discrete negative Laplacian with homogeneous Dirichlet boundary conditions
N_dof = len(u)
N = int(np.sqrt(N_dof))
h2 = (N+1)*(N+1)
u_mat = np.reshape(u, (N, N))
Lu = 4*u_mat
Lu[:-1, :] = Lu[:-1, :] - u_mat[1:, :]
Lu[1:, :] = Lu[1:, :] - u_mat[:-1, :]
Lu[:, 1:] = Lu[:, 1:] - u_mat[:, :-1]
Lu[:, :-1] = Lu[:, :-1] - u_mat[:, 1:]
Lu *= h2
Lu = np.reshape(Lu, (N_dof))
return Lu
def load(F, f1, f2, f3, f4):
# construct load vector for system -\Delta u = f
N = len(f1)
b = F
h2 = (N+1)*(N+1)
# y=0
b[0, :] += f1*h2
# x=0
b[:, 0] += f2*h2
# y=L
b[-1, :] += f3*h2
# x=L
b[:, -1] += f4*h2
b = np.reshape(b, (N ** 2))
return b
N = 2 ** 11
L = 1
h = L/(N+1)
N_dof = N ** 2
x = np.linspace(h, L-h, N)
xv = np.reshape(x, (1, N))
y = np.linspace(h, L-h, N)
yv = np.reshape(y, (N, 1))
# source
sig = 0.1
F = 100*np.exp((-(yv-0.5) ** 2)/sig ** 2) * \
np.exp(-((xv-0.5) ** 2)/sig ** 2) / (2*np.pi*sig)
# y=0
f1 = 1*np.sin(x*np.pi)
# x=0
f2 = 2*np.sin(y*np.pi)
# y=L
f3 = 3*np.sin(x*np.pi)
# x=L
f4 = 4*np.sin(y*np.pi)
A = LinearOperator((N_dof, N_dof), matvec=lap)
b = load(F, f1, f2, f3, f4)
u, exitcode = cg(A, b)
print(exitcode)
(xg, yg) = np.meshgrid(x, y)
u_mat = np.reshape(u, (N, N))
ax = plt.axes(projection="3d")
ax.plot_surface(xg, yg, u_mat)
plt.show()
scipy.sparse.linalg.cg
in Python. To do this, you need to write a function that applies the 2D Laplacian via the stencil above, then the RHS will be a vector containing your sources and boundary conditions. $\endgroup$