FEniCS 倾向于隐藏有关它构建的实际矩阵的细节,并防止对其进行轻松操作。据我所知,这是一个设计决策,因为他们正试图创建一个包罗万象的封闭管道来离散化和求解 PDES。矩阵存储和矩阵元素的特定细节被认为是只有开发人员才应该关注的实现细节。可用的线性代数后端格式因版本而异,破坏了旧代码。没有内置函数可以将稀疏矩阵导出为 scipy 格式(.toarray() 转换为密集矩阵!);您必须指定后端类型,然后专门为它编写自己的导出函数。
对于只想指定 PDE 并获得解决方案的人来说,这可能很好,但对于像你我这样想要尝试构建不同的预条件子、求解器、研究矩阵的光谱特性等的人来说,这令人沮丧。所以,通常我做的是:
- 将矩阵从 FEniCS 中取出并放入 scipy
- 使用我自己的代码或pyamg之类的包在scipy中做线性代数
- 将结果带回 FEniCS 进行后处理。
无论如何,我经常这样做,以至于我只花了几分钟就编写了以下执行此过程的简单代码,在一个简单的椭圆 PDE 上测试了一个简单的 Gauss-Seidel 求解器和来自 pyAMG 的多重网格求解器:
from dolfin import *
import numpy as np
import scipy.sparse as sps
import scipy.sparse.linalg as spla
from pyamg import *
dolfin.parameters.linear_algebra_backend = 'Eigen'
def fenics_eigen_to_scipy_csr(assembled_fenics_form):
row,col,val = as_backend_type(assembled_fenics_form).data()
M = sps.csr_matrix((val,col,row))
M.eliminate_zeros()
return M
def do_gauss_seidel(b, A, tol=1e-5, maxiter=1000):
dd = A.diagonal()
D = sps.dia_matrix(A.shape)
D.setdiag(dd)
L = sps.tril(A, -1)
U = sps.triu(A, 1)
solve_DL = spla.factorized(D + L)
normb = np.linalg.norm(b)
x = np.zeros(b.shape)
for ii in range(maxiter):
normr = np.linalg.norm(b - A*x)
if normr/normb < tol:
break
x = solve_DL(b - U*x)
return x
mesh = RectangleMesh(Point(0.0, 0.0), Point(1.0, 1.5), 100, 200)
V = FunctionSpace(mesh, 'CG', 1)
u = TrialFunction(V)
v = TestFunction(V)
a = inner(nabla_grad(u), nabla_grad(v))*dx + 10*u*v*dx
A = fenics_eigen_to_scipy_csr(assemble(a))
b = np.random.rand(V.dim())
x_true = spla.spsolve(A, b)
x_gs = do_gauss_seidel(b, A, maxiter=1000)
print 'relative error in gauss-seidel solve:', np.linalg.norm(x_true - x_gs)/np.linalg.norm(x_true)
ml = ruge_stuben_solver(A)
print ml
x_amg = ml.solve(b, tol=1e-10, maxiter=2)
print "relative error in amg solve:", np.linalg.norm(x_true - x_amg)/np.linalg.norm(x_true)
# Get the vector back into a FEniCS Function and plot it:
xtrue_fct = Function(V)
xtrue_fct.vector()[:] = np.copy(x_true)
plot(xtrue_fct, interactive=True)
希望这可以帮助。