Skip to content

Commit de4e975

Browse files
yianzengdhamksagiyam
authored
Eigensolver (#2985)
Introduce LinearEigenproblem and LinearEigensolver classes. Also add tests and a demo. --------- Co-authored-by: David Ham <[email protected]> Co-authored-by: ksagiyam <[email protected]>
1 parent 8856d90 commit de4e975

File tree

6 files changed

+336
-97
lines changed

6 files changed

+336
-97
lines changed

demos/eigenvalues_QG_basinmodes/qgbasinmodes.py.rst

Lines changed: 57 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,16 @@ Oceanic Basin Modes: Quasi-Geostrophic approach
66
This tutorial was contributed by Christine Kaufhold and `Francis
77
Poulin <mailto:[email protected]>`__.
88

9-
As a continuation of the Quasi-Geostrophic (QG) model described in the
10-
other tutorial, we will now see how we can use Firedrake to compute
11-
the spatial structure and frequencies of the freely evolving modes in this system,
12-
what are referred to as basin modes.
13-
Oceanic basin modes are low frequency structures that propagate
14-
zonally in the oceans that alter the dynamics of Western Boundary Currents,
15-
such as the Gulf Stream. In this particular tutorial we will show how to
16-
solve the QG eigenvalue problem with no basic state and no dissipative
17-
forces.
18-
Unlike the other demo that integrated the equations forward in time, in this
19-
problem it is necessary to compute the eigenvalues and eigenfunctions
20-
for a particular differential operator. This requires using
21-
`PETSc <http://www.mcs.anl.gov/petsc/>`__ matrices
22-
and eigenvalue solvers in `SLEPc <http://slepc.upv.es>`__.
9+
As a continuation of the Quasi-Geostrophic (QG) model described in the other
10+
tutorial, we will now see how we can use Firedrake to compute the spatial
11+
structure and frequencies of the freely evolving modes in this system, what are
12+
referred to as basin modes. Oceanic basin modes are low frequency structures
13+
that propagate zonally in the oceans that alter the dynamics of Western
14+
Boundary Currents, such as the Gulf Stream. In this particular tutorial we will
15+
show how to solve the QG eigenvalue problem with no basic state and no
16+
dissipative forces. Unlike the other demo that integrated the equations forward
17+
in time, in this problem it is necessary to compute the eigenvalues and
18+
eigenfunctions for a particular differential operator.
2319

2420
This demo requires SLEPc and slepc4py to be installed. This is most easily
2521
achieved by providing the optional `--slepc` flag to either `firedrake-install`
@@ -105,133 +101,97 @@ Firedrake code
105101
--------------
106102

107103
Using this form, we can now implement this eigenvalue problem in
108-
Firedrake. We import the Firedrake, PETSc, and SLEPc libraries. ::
109-
110-
from firedrake import *
111-
from firedrake.petsc import PETSc
112-
try:
113-
from slepc4py import SLEPc
114-
except ImportError:
115-
import sys
116-
warning("Unable to import SLEPc, eigenvalue computation not possible (try firedrake-update --slepc)")
117-
sys.exit(0)
104+
Firedrake. We start by importing Firedrake. ::
118105

106+
from firedrake import *
119107

120108
We specify the geometry to be a square geometry with :math:`50` cells
121109
with length :math:`1`. ::
122110

123-
Lx = 1.
124-
Ly = 1.
125-
n0 = 50
126-
mesh = RectangleMesh(n0, n0, Lx, Ly, reorder=None)
111+
Lx = 1.
112+
Ly = 1.
113+
n0 = 50
114+
mesh = RectangleMesh(n0, n0, Lx, Ly, reorder=None)
127115

128116
Next we define the function spaces within which our solution will
129117
reside. ::
130118

131-
Vcg = FunctionSpace(mesh,'CG',3)
119+
Vcg = FunctionSpace(mesh,'CG',3)
132120

133121
We impose zero Dirichlet boundary conditions, in a strong sense, which
134122
guarantee that we have no-normal flow at the boundary walls. ::
135123

136-
bc = DirichletBC(Vcg, 0.0, "on_boundary")
124+
bc = DirichletBC(Vcg, 0.0, "on_boundary")
137125

138126
The two non-dimensional parameters are the :math:`\beta` parameter, set
139127
by the sphericity of the Earth, and the Froude number, the relative
140128
importance of rotation to stratification. ::
141129

142-
beta = Constant('1.0')
143-
F = Constant('1.0')
144-
145-
Additionally, we can create some Functions to store the eigenmodes. ::
146-
147-
eigenmodes_real, eigenmodes_imag = Function(Vcg), Function(Vcg)
130+
beta = Constant('1.0')
131+
F = Constant('1.0')
148132

149133
We define the Test Function :math:`\phi` and the Trial Function
150134
:math:`\psi` in our function space. ::
151135

152-
phi, psi = TestFunction(Vcg), TrialFunction(Vcg)
136+
phi, psi = TestFunction(Vcg), TrialFunction(Vcg)
153137

154138
To build the weak formulation of our equation we need to build two PETSc
155139
matrices in the form of a generalized eigenvalue problem,
156-
:math:`A\psi = \lambda M\psi`. We impose the boundary conditions on the
157-
mass matrix :math:`M`, since that is where we used integration by parts. ::
158-
159-
a = beta*phi*psi.dx(0)*dx
160-
m = -inner(grad(psi), grad(phi))*dx - F*psi*phi*dx
161-
petsc_a = assemble(a).M.handle
162-
petsc_m = assemble(m, bcs=bc).M.handle
140+
:math:`A\psi = \lambda M\psi` ::
163141

164-
We can declare how many eigenpairs, eigenfunctions and eigenvalues, we
165-
want to find ::
142+
eigenproblem = LinearEigenproblem(
143+
A=beta*phi*psi.dx(0)*dx,
144+
M=-inner(grad(psi), grad(phi))*dx - F*psi*phi*dx,
145+
bcs=bc)
166146

167-
num_eigenvalues = 1
147+
Next we program our eigenvalue solver through the PETSc options system. The
148+
first is specifying that we have an generalized eigenvalue problem that is
149+
nonhermitian. Then, we ask for the eigenvalues with the largest imaginary
150+
part. Finally we set the spectral transform to shift with no target::
168151

169-
Next we will impose parameters onto our eigenvalue solver. The first is
170-
specifying that we have an generalized eigenvalue problem that is
171-
nonhermitian. The second specifies the spectral transform shift factor
172-
to be non-zero. The third requires we use a Krylov-Schur method,
173-
which is the default so this is not strictly necessary. Then, we ask for
174-
the eigenvalues with the largest imaginary part. Finally, we specify the
175-
tolerance. ::
152+
opts = {"eps_gen_non_hermitian": None,
153+
"eps_largest_imaginary": None,
154+
"st_type": "shift",
155+
"eps_target": None,
156+
"st_pc_factor_shift_type": "NONZERO"}
176157

177-
opts = PETSc.Options()
178-
opts.setValue("eps_gen_non_hermitian", None)
179-
opts.setValue("st_pc_factor_shift_type", "NONZERO")
180-
opts.setValue("eps_type", "krylovschur")
181-
opts.setValue("eps_largest_imaginary", None)
182-
opts.setValue("eps_tol", 1e-10)
158+
Finally, we build our eigenvalue solver, specifying in this case that we just
159+
want to see the first eigenvalue, eigenvector pair::
183160

184-
Finally, we build our eigenvalue solver using SLEPc. We add our PETSc
185-
matrices into the solver as operators and use setFromOptions() to call
186-
the PETSc parameters we previously declared. ::
161+
eigensolver = LinearEigensolver(eigenproblem, n_evals=1,
162+
solver_parameters=opts)
187163

188-
es = SLEPc.EPS().create(comm=COMM_WORLD)
189-
es.setDimensions(num_eigenvalues)
190-
es.setOperators(petsc_a, petsc_m)
191-
es.setFromOptions()
192-
es.solve()
164+
Now solve the system. This returns the number of converged eigenvalues. ::
193165

194-
Additionally we can find the number of converged eigenvalues. ::
195-
196-
nconv = es.getConverged()
166+
nconv = eigensolver.solve()
197167

198168
We now get the real and imaginary parts of the eigenvalue and
199169
eigenvector for the leading eigenpair (that with the largest in
200-
magnitude imaginary part). First we check if we actually managed to
201-
converge any eigenvalues at all. ::
202-
203-
if nconv == 0:
204-
import sys
205-
warning("Did not converge any eigenvalues")
206-
sys.exit(0)
207-
208-
If we did, we go ahead and extract them from the SLEPc eigenvalue
209-
solver::
210-
211-
vr, vi = petsc_a.getVecs()
170+
magnitude imaginary part). ::
212171

213-
lam = es.getEigenpair(0, vr, vi)
172+
lam = eigensolver.eigenvalue(0)
214173

215-
and we gather the final eigenfunctions ::
174+
and we gather the corresponding eigenfunctions ::
216175

217-
eigenmodes_real.vector()[:], eigenmodes_imag.vector()[:] = vr, vi
176+
eigenmode_real, eigenmode_imag = eigensolver.eigenfunction(0)
218177

219178
We can now list and show plots for the eigenvalues and eigenfunctions
220179
that were found. ::
221180

222-
print("Leading eigenvalue is:", lam)
181+
print("Leading eigenvalue is:", lam)
223182

224-
try:
225-
import matplotlib.pyplot as plt
226-
fig, axes = plt.subplots()
227-
colors = tripcolor(eigenmodes_real, axes=axes)
228-
fig.colorbar(colors)
183+
try:
184+
import matplotlib.pyplot as plt
185+
fig, axes = plt.subplots()
186+
colors = tripcolor(eigenmode_real, axes=axes)
187+
fig.colorbar(colors)
229188

230-
fig, axes = plt.subplots()
231-
colors = tripcolor(eigenmodes_imag, axes=axes)
232-
fig.colorbar(colors)
233-
except ImportError:
234-
warning("Matplotlib not available, not plotting eigemodes")
189+
fig, axes = plt.subplots()
190+
colors = tripcolor(eigenmode_imag, axes=axes)
191+
fig.colorbar(colors)
192+
plt.show()
193+
except ImportError:
194+
warning("Matplotlib not available, not plotting eigemodes")
235195

236196
Below is a plot of the spatial structure of the real part of one of the eigenmodes computed above.
237197

docs/source/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@
262262
\sphinxDUC{2218}{$\circ$}
263263
\sphinxDUC{22C5}{$\cdot$}
264264
\sphinxDUC{25A3}{$\boxdot$}
265+
\sphinxDUC{03BB}{$\lambda$}
265266
% Sphinx equivalent of
266267
% \DeclareUnicodeCharacter{}{}
267268

docs/source/team.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,5 @@ Ben Sepanski: https://bensepanski.github.io
111111
Jemma Shipton:
112112
Joseph G. Wallwork: https://www.imperial.ac.uk/people/j.wallwork16
113113
Florian Wechsung: https://florianwechsung.github.io
114+
Yian Zeng:
114115
Fangyi Zhou:

firedrake/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
from firedrake.ufl_expr import *
9999
from firedrake.utility_meshes import *
100100
from firedrake.variational_solver import *
101+
from firedrake.eigensolver import *
101102
from firedrake.vector import *
102103
from firedrake.version import __version__ as ver, __version_info__, check # noqa: F401
103104
from firedrake.ensemble import *

0 commit comments

Comments
 (0)