在 scipy 中求解离散边界值问题¶
日期 | 2016-10-22(最后修改) |
---|
数学公式¶
我们考虑一个在正方形域 $\Omega = [0, 1] \times [0, 1]$ 上的非线性椭圆边界值问题:$$ \Delta u + k f(u) = 0 \\ u = 0 \text{ on } \partial \Omega $$
这里 $u = u(x,y)$ 是一个未知函数,$\Delta$ 是 拉普拉斯算子,$k$ 是某个常数,$f(u)$ 是一个给定函数。
对这类问题,通常的计算方法是离散化。我们使用一个步长为 $h$ 的均匀网格,并定义 $u_{i, j} = u(i h , jh)$。通过使用 5 点有限差分 来近似拉普拉斯算子,我们得到一个方程组,形式如下:$$ u_{i - 1, j} + u_{i + 1, j} + u_{i, j - 1} + u_{i, j + 1} - 4 u_{i,j} + c f(u_{i,j}) = 0 \\ u_{i, j} = 0 \text{ on } \partial \Omega $$
这里 $c = k h^2$。
为 scipy 定义问题¶
从现在开始,我们关注离散版本,并考虑一个每个维度有 $n = 100$ 个刻度的网格,并设置 $c = 1$ 和 $f(u) = u^3$。
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
from scipy.optimize import least_squares
from scipy.sparse import coo_matrix
n = 100
c = 1
def f(u):
return u**3
def f_prime(u):
return 3 * u**2
为了求解方程组,我们将使用 scipy.optimize.least_squares
。
我们定义一个函数来计算每个方程的左侧。注意,我们假设边界上的值为零,并且在优化过程中不会改变它们。
def fun(u, n, f, f_prime, c, **kwargs):
v = np.zeros((n + 2, n + 2))
u = u.reshape((n, n))
v[1:-1, 1:-1] = u
y = v[:-2, 1:-1] + v[2:, 1:-1] + v[1:-1, :-2] + v[1:-1, 2:] - 4 * u + c * f(u)
return y.ravel()
始终建议尽可能提供解析雅可比矩阵。在我们的问题中,我们有 $n^2=10000$ 个方程和变量,但每个方程仅依赖于少数变量,因此我们应该以稀疏格式计算雅可比矩阵。
预先计算雅可比矩阵中非零元素的行和列索引很方便。我们定义了相应的函数
def compute_jac_indices(n):
i = np.arange(n)
jj, ii = np.meshgrid(i, i)
ii = ii.ravel()
jj = jj.ravel()
ij = np.arange(n**2)
jac_rows = [ij]
jac_cols = [ij]
mask = ii > 0
ij_mask = ij[mask]
jac_rows.append(ij_mask)
jac_cols.append(ij_mask - n)
mask = ii < n - 1
ij_mask = ij[mask]
jac_rows.append(ij_mask)
jac_cols.append(ij_mask + n)
mask = jj > 0
ij_mask = ij[mask]
jac_rows.append(ij_mask)
jac_cols.append(ij_mask - 1)
mask = jj < n - 1
ij_mask = ij[mask]
jac_rows.append(ij_mask)
jac_cols.append(ij_mask + 1)
return np.hstack(jac_rows), np.hstack(jac_cols)
之后,在 coo_matrix
格式中计算雅可比矩阵很简单
jac_rows, jac_cols = compute_jac_indices(n)
def jac(u, n, f, f_prime, c, jac_rows=None, jac_cols=None):
jac_values = np.ones_like(jac_cols, dtype=float)
jac_values[:n**2] = -4 + c * f_prime(u)
return coo_matrix((jac_values, (jac_rows, jac_cols)), shape=(n**2, n**2))
解决问题¶
在没有对问题的任何了解的情况下,我们最初将所有值设置为 0.5。请注意,不能保证给定的连续或离散问题具有唯一解。
u0 = np.ones(n**2) * 0.5
预先计算雅可比矩阵中非零元素的行和列
jac_rows, jac_cols = compute_jac_indices(n)
现在我们准备运行优化。第一个解将在不对方程施加任何约束的情况下计算。
res_1 = least_squares(fun, u0, jac=jac, gtol=1e-3, args=(n, f, f_prime, c), kwargs={'jac_rows': jac_rows, 'jac_cols': jac_cols}, verbose=1)
下面我们可视化第一个解。左侧图显示了扁平化的解,中间图显示了解在方形域中的外观,右侧图显示了每个节点的最终残差。
plt.figure(figsize=(16, 5))
plt.subplot(132)
plt.imshow(res_1.x.reshape((n, n)), cmap='coolwarm', vmin=-max(abs(res_1.x)), vmax=max(abs(res_1.x)))
plt.colorbar(use_gridspec=True, fraction=0.046, pad=0.04)
plt.subplot(131)
plt.plot(res_1.x)
plt.subplot(133)
plt.plot(res_1.fun)
plt.tight_layout()
某些物理考虑可能要求解在任何地方都必须非负。我们可以通过向求解器指定约束来实现这一点
res_2 = least_squares(fun, u0, jac=jac, bounds=(0, np.inf), gtol=1e-3,
args=(n, f, f_prime, c), kwargs={'jac_rows': jac_rows, 'jac_cols': jac_cols}, verbose=1)
plt.figure(figsize=(16, 5))
plt.subplot(132)
plt.imshow(res_2.x.reshape((n, n)), cmap='Reds')
plt.colorbar(use_gridspec=True, fraction=0.046, pad=0.04)
plt.subplot(131)
plt.plot(res_2.x)
plt.subplot(133)
plt.plot(res_2.fun)
plt.tight_layout()
我们看到设置下界允许我们找到不同的非负解。
您可以尝试从不同的起点运行优化,使用不同的约束或更改 $c$ 和 $f(u)$,并查看它如何影响 least_squares
求解器的结果。
章节作者:Nikolay Mayorov