{ "cells": [ { "cell_type": "markdown", "id": "03e210dc", "metadata": { "editable": true }, "source": [ "\n", "\n", "# Demo - The Tau method\n", "**Mikael Mortensen** (email: `mikaem@math.uio.no`), Department of Mathematics, University of Oslo.\n", "\n", "Date: **June 15, 2021**\n", "\n", "**Summary.** Shenfun has primarily been developed for the spectral Galerkin method.\n", "However, there are other methods out there that make use of global basis\n", "functions and variational principles. One such method, which has a lot in\n", "common with the spectral Galerkin method, is the Tau method. The\n", "principle difference between a Tau method and a spectral Galerkin method\n", "is in the choice of basis functions. The spectral Galerkin method is\n", "usually defined through function spaces where the boundary conditions\n", "of the problem are already built in. The tau-method, on the other hand,\n", "usually considers\n", "only the orthogonal basis, like pure Chebyshev or Legendre polynomials,\n", "and derives differentiation matrices for these bases. The boundary conditions\n", "are then usually fixed through manipulation of a couple of rows of the\n", "differentiation matrix. In this demo we will show how the original\n", "tau-method can be easily implemented using [shenfun](https://github.com/spectralDNS/shenfun)." ] }, { "cell_type": "markdown", "id": "4032d1d0", "metadata": { "editable": true }, "source": [ "## The tau method for Poisson's equation in 1D\n", "\n", "Poisson's equation is given on a domain $\\Omega = (-1, 1)$ as" ] }, { "cell_type": "markdown", "id": "6860396a", "metadata": { "editable": true }, "source": [ "\n", "
\n", "\n", "$$\n", "\\begin{equation}\n", "\\nabla^2 u(x) = f(x) \\quad \\text{for }\\, x \\in \\Omega, \\label{eq:poisson} \\tag{1}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "id": "04929b21", "metadata": { "editable": true }, "source": [ "\n", "\n", "\n", "$$\n", "\\begin{equation} \n", "u(-1)=a, u(1)=b, \\notag\n", "\\label{_auto1} \\tag{2}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "id": "b722d3fb", "metadata": { "editable": true }, "source": [ "where $u(x)$ is the solution, $f(x)$ is a function and $a, b$ are two possibly\n", "non-zero constants. To solve Eq. ([1](#eq:poisson)) with the tau method we choose either\n", "Legendre of Chebyshev basis functions $\\phi_k$, and then look for\n", "solutions" ] }, { "cell_type": "markdown", "id": "c9f32970", "metadata": { "editable": true }, "source": [ "\n", "\n", "\n", "$$\n", "\\begin{equation}\n", "u(x) = \\sum_{k=0}^{N-1} \\hat{u}_k \\phi_k(x), \\label{eq:u} \\tag{3}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "id": "f10d071a", "metadata": { "editable": true }, "source": [ "where $N$ is the size of the discretized problem and\n", "$\\hat{\\mathbf{u}} = \\{\\hat{u}_k\\}_{k=0}^{N-1}$ are the unknown expansion\n", "coefficients. For this function to satisfy the given boundary conditions, it is necessary\n", "that" ] }, { "cell_type": "markdown", "id": "fe13714e", "metadata": { "editable": true }, "source": [ "\n", "\n", "\n", "$$\n", "\\begin{equation}\n", "u(-1) = \\sum_{k=0}^{N-1} \\hat{u}_k \\phi_k(-1) = \\sum_{k=0}^{N-1}\\hat{u}_{k}(-1)^k = a,\n", "\\label{eq:dirichleta} \\tag{4} \n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "id": "e0a0c7d7", "metadata": { "editable": true }, "source": [ "\n", "\n", "\n", "$$\n", "\\begin{equation} \n", "u(+1) = \\sum_{k=0}^{N-1} \\hat{u}_k \\phi_k(+1) = \\sum_{k=0}^{N-1} \\hat{u}_{k} = b,\n", "\\label{eq:dirichletb} \\tag{5}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "id": "c7ad046f", "metadata": { "editable": true }, "source": [ "where we have use that $\\phi_k(1) = 1$ and $\\phi_k(-1)=(-1)^k$ for $k=0,1, \\ldots, N-1$,\n", "for either Chebyshev or Legendre polynomials $\\phi_k$. This gives two equations\n", "for the $N$ unknowns in $\\{\\hat{u}_k\\}_{k=0}^{N-1}$.\n", "We will now use variational principles, like in the Galerkin method, in order to\n", "derive equations that can be used to close the remaining $N-2$ unknowns.\n", "To this end we multiply Poisson's equation by a\n", "test function $v$, a weight $w$, and integrate over the domain" ] }, { "cell_type": "markdown", "id": "389e6324", "metadata": { "editable": true }, "source": [ "\n", "\n", "\n", "$$\n", "\\begin{equation}\n", "\\int_{-1}^1 \\nabla^2 u \\, v \\, w\\, dx = \\int_{-1}^1 f \\, v\\, w\\, dx. \\label{eq:varform} \\tag{6}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "id": "29046d76", "metadata": { "editable": true }, "source": [ "The weight function depends on the choice of basis functions. For Chebyshev\n", "it will be $1/\\sqrt{1-x^2}$, whereas it is unity for Legendre.\n", "\n", "Finally, we insert the trial function $u=\\sum_{j=0}^{N-1}\\hat{u}_j \\phi_j$ and\n", "the test function $v=\\phi_k$, to get" ] }, { "cell_type": "markdown", "id": "c390a448", "metadata": { "editable": true }, "source": [ "\n", "\n", "\n", "$$\n", "\\begin{equation}\n", "\\int_{-1}^1 \\nabla^2 \\sum_{j=0}^{N-1} \\phi_j \\, \\, \\phi_k \\, w\\, dx \\hat{u}_j = \\int_{-1}^1 f \\, \\phi_k\\, w\\, dx. \\label{eq:varform2} \\tag{7}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "id": "9d4a27c7", "metadata": { "editable": true }, "source": [ "This problem can be reformulated as a linear algebra problem," ] }, { "cell_type": "markdown", "id": "1dd2760c", "metadata": { "editable": true }, "source": [ "\n", "\n", "\n", "$$\n", "\\begin{equation}\n", "a_{kj} \\hat{u}_j = \\tilde{f}_k, \n", "\\label{_auto2} \\tag{8}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "id": "f79f7562", "metadata": { "editable": true }, "source": [ "\n", "\n", "\n", "$$\n", "\\begin{equation} \n", "A \\hat{\\mathbf{{u}}} = \\tilde{\\mathbf{{f}}}.\n", "\\label{_auto3} \\tag{9}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "id": "3949a751", "metadata": { "editable": true }, "source": [ "However, the matrix $A\\in \\mathbb{R}^{N \\times N}$ is singular because it\n", "contains two zero rows. These two rows are used to implement the two boundary\n", "conditions. Setting $a_{N-2, j}=1$ and\n", "$a_{N-1, j}= (-1)^j$ for $j=0, 1, \\ldots, N-1$, and also fixing\n", "the right hand sides $\\tilde{f}_{N-2}=a$ and $\\tilde{f}_{N-1}=b$, the\n", "two boundary conditions will be satisfied." ] }, { "cell_type": "markdown", "id": "fc08f897", "metadata": { "editable": true }, "source": [ "## Implementation" ] }, { "cell_type": "markdown", "id": "6f3913f0", "metadata": { "editable": true }, "source": [ "### Preamble\n", "\n", "We will solve Poisson's equation using the [shenfun](https://github.com/spectralDNS/shenfun) Python module. The first thing needed\n", "is then to import some of this module's functionality\n", "plus some other helper modules, like [Numpy](https://numpy.org) and [Sympy](https://sympy.org), and the [scipy.sparse](https://docs.scipy.org/doc/scipy/reference/sparse.html)\n", "for handeling sparse matrices:" ] }, { "cell_type": "code", "execution_count": 1, "id": "d2f50904", "metadata": { "collapsed": false, "editable": true, "execution": { "iopub.execute_input": "2024-02-19T13:48:49.483400Z", "iopub.status.busy": "2024-02-19T13:48:49.483112Z", "iopub.status.idle": "2024-02-19T13:48:50.330161Z", "shell.execute_reply": "2024-02-19T13:48:50.329453Z" } }, "outputs": [], "source": [ "from shenfun import inner, div, grad, TestFunction, TrialFunction, Function, \\\n", " project, Dx, Array, FunctionSpace, dx\n", "import numpy as np\n", "import scipy.sparse as scp\n", "from sympy import symbols, cos, sin, exp, lambdify" ] }, { "cell_type": "markdown", "id": "40fcaf01", "metadata": { "editable": true }, "source": [ "We use `Sympy` for a manufactured solution and `Numpy` for testing.\n", "The exact manufactured solution $u_e(x)$ and the right hand side\n", "$f_e(x)$ are created using `Sympy` as follows" ] }, { "cell_type": "code", "execution_count": 2, "id": "b7f66a59", "metadata": { "collapsed": false, "editable": true, "execution": { "iopub.execute_input": "2024-02-19T13:48:50.333410Z", "iopub.status.busy": "2024-02-19T13:48:50.332992Z", "iopub.status.idle": "2024-02-19T13:48:50.343012Z", "shell.execute_reply": "2024-02-19T13:48:50.342398Z" } }, "outputs": [], "source": [ "x = symbols(\"x\")\n", "ue = sin(4*np.pi*x)\n", "fe = ue.diff(x, 2)" ] }, { "cell_type": "markdown", "id": "739884be", "metadata": { "editable": true }, "source": [ "Note that we compute the right hand side function `fe` that corresponds to\n", "the manufactured solution `ue`." ] }, { "cell_type": "markdown", "id": "377d957f", "metadata": { "editable": true }, "source": [ "### Discretization\n", "\n", "We create a basis with a given number of basis functions," ] }, { "cell_type": "code", "execution_count": 3, "id": "12458aa3", "metadata": { "collapsed": false, "editable": true, "execution": { "iopub.execute_input": "2024-02-19T13:48:50.345766Z", "iopub.status.busy": "2024-02-19T13:48:50.345545Z", "iopub.status.idle": "2024-02-19T13:48:50.355966Z", "shell.execute_reply": "2024-02-19T13:48:50.354823Z" } }, "outputs": [], "source": [ "N = 32\n", "T = FunctionSpace(N, 'Chebyshev')\n", "#T = FunctionSpace(N, 'Legendre')" ] }, { "cell_type": "markdown", "id": "736218b2", "metadata": { "editable": true }, "source": [ "Note that we can either choose a Legendre or a Chebyshev basis. The\n", "remaining code will work either way." ] }, { "cell_type": "markdown", "id": "37f9d1b5", "metadata": { "editable": true }, "source": [ "### Variational formulation\n", "\n", "The variational problem ([6](#eq:varform)) can be assembled using `shenfun`'s\n", "[TrialFunction](https://shenfun.readthedocs.io/en/latest/shenfun.forms.html#shenfun.forms.arguments.TrialFunction), [TestFunction](https://shenfun.readthedocs.io/en/latest/shenfun.forms.html#shenfun.forms.arguments.TestFunction) and [inner()](https://shenfun.readthedocs.io/en/latest/shenfun.forms.html#shenfun.forms.inner.inner) functions." ] }, { "cell_type": "code", "execution_count": 4, "id": "fa34ca27", "metadata": { "collapsed": false, "editable": true, "execution": { "iopub.execute_input": "2024-02-19T13:48:50.359251Z", "iopub.status.busy": "2024-02-19T13:48:50.359054Z", "iopub.status.idle": "2024-02-19T13:48:50.370371Z", "shell.execute_reply": "2024-02-19T13:48:50.369768Z" } }, "outputs": [], "source": [ "u = TrialFunction(T)\n", "v = TestFunction(T)\n", "# Assemble differentiation matrix\n", "A = inner(v, div(grad(u)))\n", "# Assemble right hand side\n", "fj = Array(T, buffer=fe)\n", "f_hat = Function(T)\n", "f_hat = inner(v, fj, output_array=f_hat)" ] }, { "cell_type": "markdown", "id": "a10787e4", "metadata": { "editable": true }, "source": [ "Note that the `sympy` function `fe` is be used to initialize the [Array](https://shenfun.readthedocs.io/en/latest/shenfun.forms.html#shenfun.forms.arguments.Array)\n", "`fj`, because an Array\n", "is required as input to the [inner()](https://shenfun.readthedocs.io/en/latest/shenfun.forms.html#shenfun.forms.inner.inner) function. An\n", "[Array](https://shenfun.readthedocs.io/en/latest/shenfun.forms.html#shenfun.forms.arguments.Array) contains the solution evaluated on the\n", "quadrature mesh. A [Function](https://shenfun.readthedocs.io/en/latest/shenfun.forms.html#shenfun.forms.arguments.Function) represents a global\n", "expansion, like Eq. ([3](#eq:u)), and its values are the\n", "expansion coefficients $\\{\\hat{u}_{k}\\}_{k=0}^{N-1}$." ] }, { "cell_type": "markdown", "id": "4a47d951", "metadata": { "editable": true }, "source": [ "### Fix boundary conditions\n", "\n", "We fix two rows of the differentiation matrix in order to satisfy\n", "Eqs. ([4](#eq:dirichleta)) and ([5](#eq:dirichletb))." ] }, { "cell_type": "code", "execution_count": 5, "id": "7da80e99", "metadata": { "collapsed": false, "editable": true, "execution": { "iopub.execute_input": "2024-02-19T13:48:50.373092Z", "iopub.status.busy": "2024-02-19T13:48:50.372887Z", "iopub.status.idle": "2024-02-19T13:48:50.382392Z", "shell.execute_reply": "2024-02-19T13:48:50.381841Z" } }, "outputs": [], "source": [ "A = A.diags('lil')\n", "A[-2] = (-1)**np.arange(N)\n", "A[-1] = np.ones(N)\n", "A = A.tocsc()\n", "f_hat[-2] = ue.subs(x, T.domain[0])\n", "f_hat[-1] = ue.subs(x, T.domain[1])" ] }, { "cell_type": "markdown", "id": "0e3b7fe8", "metadata": { "editable": true }, "source": [ "Note that the last two lines uses evaluation of the sympy function\n", "`ue` at the borders of the domain. Implemented like this it is\n", "easy to change to a nonstandard domain size.\n", "The sparsity pattern of the matrix A is now modified\n", "with the typical tau-lines that we can visualize using [plotly](https://plotly.com/)" ] }, { "cell_type": "code", "execution_count": 6, "id": "454473fc", "metadata": { "collapsed": false, "editable": true, "execution": { "iopub.execute_input": "2024-02-19T13:48:50.385039Z", "iopub.status.busy": "2024-02-19T13:48:50.384822Z", "iopub.status.idle": "2024-02-19T13:48:51.080843Z", "shell.execute_reply": "2024-02-19T13:48:51.080389Z" } }, "outputs": [ { "data": { "text/html": [ " \n", " " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.plotly.v1+json": { "config": { "plotlyServerURL": "https://plot.ly" }, "data": [ { "hovertemplate": "x: %{x}