Skip to content

Commit 582851b

Browse files
committed
Add some notebooks
1 parent 95ec76e commit 582851b

File tree

4 files changed

+1036
-2
lines changed

4 files changed

+1036
-2
lines changed

README.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Shenfun enables fast development of efficient and accurate PDE solvers (spectral
1919

2020
The demo folder contains several examples for the Poisson, Helmholtz and Biharmonic equations. For extended documentation and installation instructions see `ReadTheDocs <http://shenfun.readthedocs.org>`_. For interactive demos, see the `jupyter book <https://mikaem.github.io/shenfun-demos>`_. Note that shenfun currently comes with the possibility to use two non-periodic directions (see `biharmonic demo <https://github.com/spectralDNS/shenfun/blob/master/demo/biharmonic2D_2nonperiodic.py>`_), and equations may be solved coupled and implicit (see `MixedPoisson.py <https://github.com/spectralDNS/shenfun/blob/master/demo/MixedPoisson.py>`_).
2121

22-
Note that shenfun works with curvilinear coordinates. For example, it is possible to solve equations on a `sphere <https://github.com/spectralDNS/shenfun/blob/master/demo/sphere_helmholtz.py>`_ (using spherical coordinates), on the surface of a `torus <https://github.com/spectralDNS/shenfun/blob/master/binder/Torus.ipynb>`_, on a `Möbius strip <https://mikaem.github.io/shenfun-demos/content/moebius.html>`_ or along any `curved line in 2D/3D <https://github.com/spectralDNS/shenfun/blob/master/demo/curvilinear_poisson1D.py>`_. Actually, any new coordinates may be defined by the user as long as the coordinates lead to a system of equations with separable coefficients. After defining new coordinates, operators like div, grad and curl work automatically with the new curvilinear coordinates. See also `this notebook on the sphere <https://github.com/spectralDNS/shenfun/blob/master/binder/sphere-helmholtz.ipynb>`_ or an illustration of the `vector Laplacian <https://github.com/spectralDNS/shenfun/blob/master/binder/vector-laplacian.ipynb>`_.
22+
Note that shenfun works with curvilinear coordinates. For example, it is possible to solve equations on a `sphere <https://github.com/spectralDNS/shenfun/blob/master/demo/sphere_helmholtz.py>`_ (using spherical coordinates), on the surface of a `torus <https://github.com/spectralDNS/shenfun/blob/master/docs/notebooks/Torus.ipynb>`_, on a `Möbius strip <https://mikaem.github.io/shenfun-demos/content/moebius.html>`_ or along any `curved line in 2D/3D <https://github.com/spectralDNS/shenfun/blob/master/demo/curvilinear_poisson1D.py>`_. Actually, any new coordinates may be defined by the user as long as the coordinates lead to a system of equations with separable coefficients. After defining new coordinates, operators like div, grad and curl work automatically with the new curvilinear coordinates. See also `this notebook on the sphere <https://github.com/spectralDNS/shenfun/blob/master/docs/notebooks/sphere-helmholtz.ipynb>`_ or an illustration of the `vector Laplacian <https://github.com/spectralDNS/shenfun/blob/master/docs/notebooks/vector-laplacian.ipynb>`_.
2323

2424
.. image:: https://cdn.jsdelivr.net/gh/spectralDNS/spectralutilities@master/figures/moebius8_trans.png
2525
:target: https://mikaem.github.io/shenfun-demos/content/moebius.html
@@ -30,7 +30,7 @@ Note that shenfun works with curvilinear coordinates. For example, it is possibl
3030
:target: https://mikaem.github.io/shenfun-demos/content/sphericalhelmholtz.html
3131
:alt: Solution of Poisson's equation on a spherical shell
3232
.. image:: https://cdn.jsdelivr.net/gh/spectralDNS/spectralutilities@master/figures/torus2.png
33-
:target: https://github.com/spectralDNS/shenfun/blob/master/binder/Torus.ipynb
33+
:target: https://github.com/spectralDNS/shenfun/blob/master/docs/notebooks/Torus.ipynb
3434
:alt: Solution of Poisson's equation on the surface of a torus
3535

3636

docs/notebooks/Torus.ipynb

+255
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# The Helmholtz equation on the Torus\n",
8+
"\n",
9+
"Helmholtz equation is given as\n",
10+
"\n",
11+
"$$\n",
12+
"\\nabla^2 u + \\alpha u = f.\n",
13+
"$$\n",
14+
"\n",
15+
"In this notebook we will solve this equation (without boundary conditions) on the surface of a torus, using curvilinear coordinates. The surface of the torus is parametrized by \n",
16+
"\n",
17+
"$$\n",
18+
"\\begin{align}\n",
19+
"x(\\theta, \\phi) &= (R + r \\cos \\theta) \\cos \\phi \\\\\n",
20+
"y(\\theta, \\phi) &= (R + r \\cos \\theta) \\sin \\phi \\\\\n",
21+
"z(\\theta, \\phi) &= r \\sin \\theta\n",
22+
"\\end{align}\n",
23+
"$$\n",
24+
"\n",
25+
"where $\\theta, \\phi$ are angles which make a full circle, so that their values start and end at the same point, $R$ is the distance from the center of the tube to the center of the torus,\n",
26+
"$r$ is the radius of the tube. Note that $\\theta$ is the angle in the small circle (around its center), whereas $\\phi$ is the angle of the large circle, around origo.\n",
27+
"\n",
28+
"We start the implementation by importing necessary functionality from shenfun and sympy and then defining the coordinates of the surface of the torus. Note that `rv` represents the position vector spanning the surface."
29+
]
30+
},
31+
{
32+
"cell_type": "code",
33+
"execution_count": null,
34+
"metadata": {},
35+
"outputs": [],
36+
"source": [
37+
"from shenfun import *\n",
38+
"from shenfun.la import SolverGeneric1ND\n",
39+
"import sympy as sp\n",
40+
"from IPython.display import Math\n",
41+
"\n",
42+
"N = 96\n",
43+
"R = 3\n",
44+
"r = 1\n",
45+
"theta, phi = psi = sp.symbols('x,y', real=True, positive=True)\n",
46+
"rv = ((R + r*sp.cos(theta))*sp.cos(phi), (R + r*sp.cos(theta))*sp.sin(phi), r*sp.sin(theta)) "
47+
]
48+
},
49+
{
50+
"cell_type": "markdown",
51+
"metadata": {},
52+
"source": [
53+
"Now create necessary bases and function spaces. Due to the geometry of the problem, the solution will be periodic in both $\\theta$ and $\\phi$ directions, and we choose Fourier basis functions. The basis for the $\\phi$-direction can be either real to complex or complex to complex, depending on the type of the solution. Here we assume a real solution"
54+
]
55+
},
56+
{
57+
"cell_type": "code",
58+
"execution_count": null,
59+
"metadata": {},
60+
"outputs": [],
61+
"source": [
62+
"B1 = FunctionSpace(N, 'F', dtype='D', domain=(0, 2*np.pi))\n",
63+
"B2 = FunctionSpace(N, 'F', dtype='d', domain=(0, 2*np.pi))\n",
64+
"T = TensorProductSpace(comm, (B1, B2), coordinates=(psi, rv, sp.Q.positive(r*sp.cos(theta)+R)))\n",
65+
"V = VectorSpace(T)\n",
66+
"u = TrialFunction(T)\n",
67+
"v = TestFunction(T)\n",
68+
"T.coors.sg"
69+
]
70+
},
71+
{
72+
"cell_type": "markdown",
73+
"metadata": {},
74+
"source": [
75+
"Note the assumption `sp.Q.positive(r*sp.cos(theta)+R))`, which is there to help sympy when computing basis vectors and scaling factors. It is not completely necessary, but try to omit it and look at what happens to the expanded Helmholtz equation below."
76+
]
77+
},
78+
{
79+
"cell_type": "code",
80+
"execution_count": null,
81+
"metadata": {},
82+
"outputs": [],
83+
"source": [
84+
"alpha = 0\n",
85+
"du = div(grad(u))+alpha*u\n",
86+
"g = sp.Symbol('g', real=True, positive=True) # The Jacobian**2 (T.coors.sg**2)\n",
87+
"#replace = [(3*sp.cos(theta)+5, g/2), (sp.cos(theta)**2+6*sp.cos(theta)+10, sp.cos(theta)**2+g), (3*sp.cos(theta)+10, g/2+5)] # to simplify the look\n",
88+
"replace = [(sp.cos(theta)+3, g)]\n",
89+
"Math(du.tolatex(symbol_names={r: 'r', theta: '\\\\theta', phi: '\\\\phi'}, replace=replace))"
90+
]
91+
},
92+
{
93+
"cell_type": "markdown",
94+
"metadata": {},
95+
"source": [
96+
"We now create a manufactured solution that satisfies periodicity and compute the right hand side $f$."
97+
]
98+
},
99+
{
100+
"cell_type": "code",
101+
"execution_count": null,
102+
"metadata": {},
103+
"outputs": [],
104+
"source": [
105+
"ue = sp.sin(sp.cos(theta*4))*sp.cos(4*phi)\n",
106+
"f = (div(grad(u))+alpha*u).tosympy(basis=ue, psi=psi)\n",
107+
"fj = Array(T, buffer=f*T.sg)\n",
108+
"f_hat = Function(T)\n",
109+
"f_hat = inner(v, fj, output_array=f_hat)"
110+
]
111+
},
112+
{
113+
"cell_type": "markdown",
114+
"metadata": {},
115+
"source": [
116+
"Assemble coefficient matrix and solve problem. Note that the tensorproduct matrices along axis 0 can be non-diagonal due to the measure $\\cos \\theta + 3$. The matrices along the second axis will all be diagonal, so we can choose to use `SolverGeneric1ND`"
117+
]
118+
},
119+
{
120+
"cell_type": "code",
121+
"execution_count": null,
122+
"metadata": {},
123+
"outputs": [],
124+
"source": [
125+
"mats = inner(v*T.sg, (div(grad(u))+alpha*u))\n",
126+
"#mats = inner(grad(v*T.sg), -grad(u)) # + inner(v, alpha*u)\n",
127+
"u_hat = Function(T)\n",
128+
"sol = SolverGeneric1ND(mats)\n",
129+
"u_hat = sol(f_hat, u_hat, constraints=((0, 0),))\n",
130+
"uj = u_hat.backward()\n",
131+
"uq = Array(T, buffer=ue)\n",
132+
"print('Error =', np.sqrt(inner(1, (uj-uq)**2)))"
133+
]
134+
},
135+
{
136+
"cell_type": "code",
137+
"execution_count": null,
138+
"metadata": {},
139+
"outputs": [],
140+
"source": [
141+
"mats = inner(v*T.sg, u)"
142+
]
143+
},
144+
{
145+
"cell_type": "markdown",
146+
"metadata": {},
147+
"source": [
148+
"Finally, just plot the solution using plotly."
149+
]
150+
},
151+
{
152+
"cell_type": "code",
153+
"execution_count": null,
154+
"metadata": {},
155+
"outputs": [],
156+
"source": [
157+
"surf3D(u_hat, wrapaxes=[0, 1])"
158+
]
159+
},
160+
{
161+
"cell_type": "markdown",
162+
"metadata": {},
163+
"source": [
164+
"Inspect sparsity pattern at a given Fourier wavenumber "
165+
]
166+
},
167+
{
168+
"cell_type": "code",
169+
"execution_count": null,
170+
"metadata": {},
171+
"outputs": [],
172+
"source": [
173+
"import matplotlib.pyplot as plt\n",
174+
"%matplotlib notebook\n",
175+
"plt.figure(figsize=(6,4))\n",
176+
"plt.spy(sol.solvers1D[6].mat, markersize=0.1)"
177+
]
178+
},
179+
{
180+
"cell_type": "markdown",
181+
"metadata": {},
182+
"source": [
183+
"Inspect the vector Laplacian"
184+
]
185+
},
186+
{
187+
"cell_type": "code",
188+
"execution_count": null,
189+
"metadata": {},
190+
"outputs": [],
191+
"source": [
192+
"uv = TrialFunction(V)\n",
193+
"vv = TestFunction(V)\n",
194+
"du = div(grad(uv))\n",
195+
"Math(du.tolatex(symbol_names={r: 'r', theta: '\\\\theta', phi: '\\\\phi'}, replace=replace))"
196+
]
197+
},
198+
{
199+
"cell_type": "code",
200+
"execution_count": null,
201+
"metadata": {},
202+
"outputs": [],
203+
"source": [
204+
"Math(T.coors.latex_basis_vectors(symbol_names={r: 'r', theta: '\\\\theta', phi: '\\\\phi'}, replace=replace))"
205+
]
206+
},
207+
{
208+
"cell_type": "code",
209+
"execution_count": null,
210+
"metadata": {},
211+
"outputs": [],
212+
"source": [
213+
"%matplotlib notebook\n",
214+
"M = inner(vv*T.sg, du)\n",
215+
"B = BlockMatrix(M)\n",
216+
"plt.figure(figsize=(6,4))\n",
217+
"plt.spy(B.diags(it=(4,)), markersize=0.2)"
218+
]
219+
}
220+
],
221+
"metadata": {
222+
"kernelspec": {
223+
"display_name": "shenfun",
224+
"language": "python",
225+
"name": "shenfun"
226+
},
227+
"language_info": {
228+
"codemirror_mode": {
229+
"name": "ipython",
230+
"version": 3
231+
},
232+
"file_extension": ".py",
233+
"mimetype": "text/x-python",
234+
"name": "python",
235+
"nbconvert_exporter": "python",
236+
"pygments_lexer": "ipython3",
237+
"version": "3.10.2"
238+
},
239+
"toc": {
240+
"base_numbering": 1,
241+
"nav_menu": {},
242+
"number_sections": false,
243+
"sideBar": true,
244+
"skip_h1_title": false,
245+
"title_cell": "Table of Contents",
246+
"title_sidebar": "Contents",
247+
"toc_cell": false,
248+
"toc_position": {},
249+
"toc_section_display": true,
250+
"toc_window_display": false
251+
}
252+
},
253+
"nbformat": 4,
254+
"nbformat_minor": 4
255+
}

0 commit comments

Comments
 (0)