-
Notifications
You must be signed in to change notification settings - Fork 0
/
helmholtz.py
142 lines (118 loc) · 4.44 KB
/
helmholtz.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# Simple Helmholtz equation
# =========================
#
# Let's start by considering the modified Helmholtz equation on a unit square,
# :math:`\Omega`, with boundary :math:`\Gamma`:
#
# .. math::
#
# -\nabla^2 u + u &= f
#
# \nabla u \cdot \vec{n} &= 0 \quad \textrm{on}\ \Gamma
#
# for some known function :math:`f`. The solution to this equation will
# be some function :math:`u\in V`, for some suitable function space
# :math:`V`, that satisfies these equations. Note that this is the
# Helmholtz equation that appears in meteorology, rather than the
# indefinite Helmholtz equation :math:`\nabla^2 u + u = f` that arises
# in wave problems.
#
# We transform the equation into weak form by multiplying by an arbitrary
# test function in :math:`V`, integrating over the domain and then
# integrating by parts. The variational problem so derived reads: find
# :math:`u \in V` such that:
#
# .. math::
#
# \require{cancel}
# \int_\Omega \nabla u\cdot\nabla v + uv\ \mathrm{d}x = \int_\Omega
# vf\ \mathrm{d}x + \cancel{\int_\Gamma v \nabla u \cdot \vec{n} \mathrm{d}s}
#
# Note that the boundary condition has been enforced weakly by removing
# the surface term resulting from the integration by parts.
#
# We can choose the function :math:`f`, so we take:
#
# .. math::
#
# f = (1.0 + 8.0\pi^2)\cos(2\pi x)\cos(2\pi y)
#
# which conveniently yields the analytic solution:
#
# .. math::
#
# u = \cos(2\pi x)\cos(2\pi y)
#
# However we wish to employ this as an example for the finite element
# method, so lets go ahead and produce a numerical solution.
#
# First, we always need a mesh. Let's have a :math:`10\times10` element unit square::
from firedrake import *
mesh = UnitSquareMesh(10, 10)
# We need to decide on the function space in which we'd like to solve the
# problem. Let's use piecewise linear functions continuous between
# elements::
V = FunctionSpace(mesh, "CG", 1)
# We'll also need the test and trial functions corresponding to this
# function space::
u = TrialFunction(V)
v = TestFunction(V)
# We declare a function over our function space and give it the
# value of our right hand side function::
f = Function(V)
x, y = SpatialCoordinate(mesh)
f.interpolate((1+8*pi*pi)*cos(x*pi*2)*cos(y*pi*2))
# We can now define the bilinear and linear forms for the left and right
# hand sides of our equation respectively::
a = (inner(grad(u), grad(v)) + inner(u, v)) * dx
L = inner(f, v) * dx
# Finally we solve the equation. We redefine `u` to be a function
# holding the solution::
u = Function(V)
# Since we know that the Helmholtz equation is
# symmetric, we instruct PETSc to employ the conjugate gradient method
# and do not worry about preconditioning for the purposes of this demo ::
solve(a == L, u, solver_parameters={'ksp_type': 'cg', 'pc_type': 'none'})
# For more details on how to specify solver parameters, see the section
# of the manual on :doc:`solving PDEs <../solving-interface>`.
#
# Next, we might want to look at the result, so we output our solution
# to a file::
projected = VTKFile("proj_output.pvd", project_output=True)
projected.write(u)
VTKFile("helmholtz.pvd").write(u)
# This file can be visualised using `paraview <http://www.paraview.org/>`__.
#
# We could use the built-in plotting functions of firedrake by calling
# :func:`tripcolor <firedrake.pyplot.tripcolor>` to make a pseudo-color plot.
# Before that, matplotlib.pyplot should be installed and imported::
try:
import matplotlib.pyplot as plt
except:
warning("Matplotlib not imported")
try:
from firedrake.pyplot import tripcolor, tricontour
fig, axes = plt.subplots()
colors = tripcolor(u, axes=axes)
fig.colorbar(colors)
except Exception as e:
warning("Cannot plot figure. Error msg: '%s'" % e)
# The plotting functions in Firedrake mimic those of matplotlib; to produce a
# contour plot instead of a pseudocolor plot, we can call
# :func:`tricontour <firedrake.pyplot.tricontour>` instead::
try:
fig, axes = plt.subplots()
contours = tricontour(u, axes=axes)
fig.colorbar(contours)
except Exception as e:
warning("Cannot plot figure. Error msg: '%s'" % e)
# Don't forget to show the image::
try:
plt.show()
except Exception as e:
warning("Cannot show figure. Error msg: '%s'" % e)
# Alternatively, since we have an analytic solution, we can check the
# :math:`L_2` norm of the error in the solution::
f.interpolate(cos(x*pi*2)*cos(y*pi*2))
print(sqrt(assemble(dot(u - f, u - f) * dx)))
# A python script version of this demo can be found :demo:`here <helmholtz.py>`.