@@ -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
2420This demo requires SLEPc and slepc4py to be installed. This is most easily
2521achieved by providing the optional `--slepc ` flag to either `firedrake-install `
@@ -105,133 +101,97 @@ Firedrake code
105101--------------
106102
107103Using 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
120108We specify the geometry to be a square geometry with :math: `50 ` cells
121109with 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
128116Next we define the function spaces within which our solution will
129117reside. ::
130118
131- Vcg = FunctionSpace(mesh,'CG',3)
119+ Vcg = FunctionSpace(mesh,'CG',3)
132120
133121We impose zero Dirichlet boundary conditions, in a strong sense, which
134122guarantee 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
138126The two non-dimensional parameters are the :math: `\beta ` parameter, set
139127by the sphericity of the Earth, and the Froude number, the relative
140128importance 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
149133We 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
154138To build the weak formulation of our equation we need to build two PETSc
155139matrices 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
198168We now get the real and imaginary parts of the eigenvalue and
199169eigenvector 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
219178We can now list and show plots for the eigenvalues and eigenfunctions
220179that 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
236196Below is a plot of the spatial structure of the real part of one of the eigenmodes computed above.
237197
0 commit comments