7.1. Eigenvalue solvers in NGSolve#
The goal of this section is to solve (generalized) matrix eigenvalue problems to find \(u\in\mathbb C^N\), \(\lambda\in\mathbb C\) such that
for given matrices \(A,M\in\mathbb C^{N\times N}\)
Shift-and-invert#
Algorithms like the power iteration or the Arnoldi method approximate eigenvectors corresponding to the largest magnitude eigenvalues. However in wave applications typically the smallest magnitude eigenvalues or ones in a specific region are sought.
The following observation transforms the generalized eigenvalue problem into a problem with the same eigenvectors but eigenvalues sorted by closeness to a given shift:
Let \(\lambda_0\in \mathbb C\) be not an eigenvalue. Then the problem to find \(u\in \mathbb C^N\), \(\tilde\lambda\in\mathbb C\) such that
has the same eigenvectors as the original problem with the eigenvalues given by $\( \tilde \lambda = \frac{1}{\lambda-\lambda_0}. \)$
Thus the largest magnitude eigenvalues \(\tilde \lambda\) of the shifted problem correspond to the ones of the original problem closest to the shift \(\lambda_0\).
Arnoldi algorithm#
The idea of the arnoldi algorithm is similiar to the one of the power iteration: for a given starting vector \(v_0\) the Krylow space of order \(k\) is defined by $\(V_k:=\mathrm{span}\{v_0,v_1=M^{-1}A v_0,\ldots,v_k=(M^{-1}A)^kv_0\}\)$
After a given number of steps the problem (i.e., the large matrices) is projected onto the (small) Krylow space and the small eigenvalue problem is solved. The resulting approximations are approximations to the largest magnitude eigenvalues.
Shift-and-invert Arnoldi algorithm#
It is common to combine these two ideas.
Suppose we want to compute the eigenvalues of the Laplacian on the unit_square
. Then the matrices in NGSolve
are assembled as usual
from ngsolve import *
from ngsolve.webgui import Draw
mesh = Mesh(unit_square.GenerateMesh(maxh=0.1))
fes = H1(mesh,order = 4,complex=True)
u,v = fes.TnT()
A = BilinearForm(grad(u)*grad(v)*dx).Assemble().mat
M = BilinearForm(u*v*dx).Assemble().mat
To use ArnoldiSolver
we have to supply a multidim
GridFunction
to store the eigenvectors.
The actual Krylow space is twice as large as the dimension of the GridFunction
, however half of the approximated eigenvalues are thrown away (rule of thumb)
gfu = GridFunction(fes,multidim=100)
shift = 1
lam = ArnoldiSolver(A,M, shift=shift, vecs = list(gfu.vecs),freedofs=fes.FreeDofs())
import numpy as np
print(np.array(lam)/np.pi**2)
[-6.74934666e-16-8.78821179e-20j 1.00000000e+00-6.08403476e-16j
1.00000000e+00+8.05443239e-16j 2.00000000e+00+5.20489798e-16j
4.00000000e+00+2.01681759e-15j 4.00000000e+00-1.30117264e-15j
5.00000001e+00+8.01484341e-16j 5.00000001e+00+6.46410877e-16j
8.00000006e+00-2.47620708e-14j 9.00000016e+00+1.52618897e-15j
9.00000009e+00-1.10193340e-16j 1.00000002e+01+1.15520533e-14j
1.00000002e+01-1.08967891e-15j 1.30000007e+01+1.11715711e-14j
1.30000009e+01+1.88158637e-14j 1.60000028e+01-4.32291758e-13j
1.60000015e+01+5.12239111e-14j 1.80000042e+01+3.73278413e-13j
1.70000035e+01-1.92334169e-13j 1.70000021e+01-1.24099531e-15j
2.00000073e+01-3.71454602e-15j 2.00000056e+01-7.93094869e-14j
2.60000162e+01-1.65079680e-14j 2.60000288e+01+1.27398572e-14j
2.50000277e+01+4.62916153e-13j 2.50000142e+01-8.14723057e-15j
2.50000220e+01+3.73688515e-13j 2.50000187e+01+4.26481558e-14j
2.90000474e+01-3.28919139e-13j 2.90000339e+01-9.75418779e-14j
3.20000788e+01+8.60500533e-14j 3.40000783e+01+6.17382516e-14j
3.40001126e+01+4.86709431e-14j 3.60000803e+01-1.52990145e-13j
3.60001502e+01+1.10338670e-14j 3.70001838e+01+2.72452232e-13j
3.70000935e+01+1.20941288e-13j 4.00001506e+01+6.88834080e-14j
4.00002180e+01-9.75347516e-14j 4.10002660e+01+7.79875926e-15j
4.10002006e+01+2.63619619e-15j 4.50003008e+01+1.82009881e-13j
4.50003802e+01-3.21046569e-14j 4.90004482e+01-3.34645685e-14j
4.90008512e+01-2.61089528e-14j 5.00004637e+01+2.45789066e-13j
5.00009064e+01+1.23895874e-13j 5.00006775e+01-1.46127832e-13j
5.20006175e+01+3.89780986e-14j 5.20009507e+01+1.78267542e-13j
5.30006263e+01-3.36937613e-13j 5.30011080e+01+1.19616276e-13j
5.80013329e+01-4.42517799e-13j 5.80009120e+01+5.05880085e-13j
6.10014716e+01-4.78070899e-13j 6.10016770e+01+1.30385657e-13j
6.40027880e+01+7.43520634e-14j 6.40016106e+01+2.03544840e-13j
6.80032402e+01+2.86235584e-14j 6.80020988e+01-3.85101131e-13j
6.50033885e+01-6.86418175e-15j 6.50020873e+01+1.79386381e-13j
6.50017260e+01+2.21141052e-13j 6.50018360e+01-1.08542399e-13j
7.20040556e+01-4.11383105e-13j 7.30042300e+01-4.10773212e-15j
7.30028447e+01-4.15033689e-13j 7.40035331e+01+9.78191276e-14j
7.40052227e+01-2.27287059e-13j 8.00064645e+01+6.73536110e-13j
8.00044111e+01+3.78685690e-13j 8.10092967e+01-1.18899667e-12j
8.10046709e+01-7.65123230e-14j 8.20053210e+01+2.10845626e-13j
8.20099275e+01+7.33332118e-13j 8.90099731e+01+8.68987104e-13j
8.90068408e+01-1.73751869e-13j 9.00077004e+01+1.63789643e-12j
9.00133902e+01-5.97674248e-13j 8.50110027e+01+6.10089734e-13j
8.50060808e+01+2.03110687e-12j 8.50077161e+01-3.80393249e-11j
8.50077806e+01+3.08080810e-12j 9.70109077e+01-1.05184791e-09j
9.70154319e+01-2.74004793e-09j 9.80150627e+01+6.89073404e-11j
1.01025450e+02-3.13008097e-06j 1.01014447e+02+5.64661220e-06j
1.04016863e+02+7.27162850e-06j 1.04027639e+02+4.83998586e-06j
1.00025792e+02+4.20671505e-04j 1.00020378e+02+2.03286777e-04j
1.00014558e+02-2.74822442e-04j 1.06016864e+02+9.98479517e-05j
1.06024000e+02+1.04940924e-04j 1.09034108e+02-3.36169721e-05j
1.09019233e+02+1.02890581e-04j 1.13030593e+02-2.67955914e-03j
1.13035965e+02+3.42096879e-04j 1.37066144e+02-5.94184599e-03j]
Draw(gfu)
BaseWebGuiScene
Explicit methods#
Explicit eigenvalue solvers in NGSolve
include PINVIT
and LOBPCG
from ngsolve.solvers import *
help(PINVIT)
help(LOBPCG)
Help on function PINVIT in module ngsolve.eigenvalues:
PINVIT(mata, matm, pre, num=1, maxit=20, printrates=True, GramSchmidt=True)
preconditioned inverse iteration
Help on function LOBPCG in module ngsolve.eigenvalues:
LOBPCG(mata, matm, pre, num=1, maxit=20, initial=None, printrates=True, largest=False)
Knyazev's cg-like extension of PINVIT
lam,vecs = PINVIT(A,M,M.Inverse(),num=4,maxit=10000,printrates=False)
print(np.array(lam)/np.pi**2)
---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
Input In [8], in <cell line: 1>()
----> 1 lam,vecs = PINVIT(A,M,M.Inverse(),num=4,maxit=10000,printrates=False)
2 print(np.array(lam)/np.pi**2)
File ~/bin/ngsolve/lib/python3.10/dist-packages/ngsolve/eigenvalues.py:104, in PINVIT(mata, matm, pre, num, maxit, printrates, GramSchmidt)
101 asmall = InnerProduct (vecs, mata * vecs)
102 msmall = InnerProduct (vecs, matm * vecs)
--> 104 ev,evec = scipy.linalg.eigh(a=asmall, b=msmall)
105 lams = Vector(ev[0:num])
106 if printrates:
File ~/.local/lib/python3.10/site-packages/scipy/linalg/_decomp.py:576, in eigh(a, b, lower, eigvals_only, overwrite_a, overwrite_b, turbo, eigvals, type, check_finite, subset_by_index, subset_by_value, driver)
572 lwork_args = {'lwork': lw}
574 drv_args.update({'uplo': uplo, 'jobz': _job})
--> 576 w, v, *other_args, info = drv(a=a1, b=b1, **drv_args, **lwork_args)
578 # m is always the first extra argument
579 w = w[:other_args[0]] if subset else w
KeyboardInterrupt:
lam,vecs = LOBPCG(A,M,M.Inverse(),num=4,maxit=1000,printrates=False)
print(np.array(lam)/np.pi**2)
[-1.58926725e-16 1.00000000e+00 1.00000000e+00 2.00000000e+00]