{ "cells": [ { "cell_type": "markdown", "id": "d22ffe10", "metadata": { "editable": true }, "source": [ "\n", "\n", "# Demo - Mixed bases for the Helmholtz problem\n", "**Mikael Mortensen** (email: `mikaem@math.uio.no`), Department of Mathematics, University of Oslo.\n", "\n", "Date: **March 22, 2021**\n", "\n", "**Summary.** This demo shows how to solve the Helmholtz equation using different\n", "bases for test and trial spaces. The use of different bases leads for\n", "some optimal combinations to highly sparse and well-conditioned\n", "coefficient matrices." ] }, { "cell_type": "markdown", "id": "e66d8420", "metadata": { "editable": true }, "source": [ "## The Helmholtz problem\n", "\n", "We will consider Helmholtz equation with homogeneous Dirichlet boundary conditions" ] }, { "cell_type": "markdown", "id": "fc92d314", "metadata": { "editable": true }, "source": [ "\n", "
\n", "\n", "$$\n", "\\begin{equation}\n", " \\alpha u - u^{''} = f \\quad \\text{in} \\, {I}=(-1, 1), \\quad u(\\pm 1) = 0,\n", "\\label{_auto1} \\tag{1}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "id": "df92a93f", "metadata": { "editable": true }, "source": [ "where $\\alpha \\in \\mathbb{R^+}$. The relevant function space for the Dirichlet problem is" ] }, { "cell_type": "markdown", "id": "c0ee2909", "metadata": { "editable": true }, "source": [ "\n", "\n", "\n", "$$\n", "\\begin{equation}\n", " S_N=\\text{span}\\{T_k\\}_{k=0}^{N-1}, \\quad V_{N} = \\{v \\in {S}_N\\,|\\, v(\\pm 1) = 0\\},\n", "\\label{_auto2} \\tag{2}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "id": "cfb1e1a0", "metadata": { "editable": true }, "source": [ "and the Chebyshev-Galerkin (CG) method is to find $u_N \\in V_N$ such that" ] }, { "cell_type": "markdown", "id": "c0137bd8", "metadata": { "editable": true }, "source": [ "\n", "\n", "\n", "$$\n", "\\begin{equation}\n", "\\label{eq:dirGalerkin} \\tag{3}\n", " \\alpha (u_N, v)_{\\omega^{\\sigma}} -(u^{''}_N, v)_{\\omega^{\\sigma}} = (f, v)_{\\omega^{\\sigma}}, \\forall \\, v \\in V_N,\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "id": "118ec8b5", "metadata": { "editable": true }, "source": [ "where $(u,v)_{\\omega^{\\sigma}}=\\int_{{I}}uv\\omega^{\\sigma} dx$ is the scalar product in the weighted space $L^2_{\\omega^{\\sigma}}({I})$.\n", "\n", "Shenfun has implemented three different Chebyshev Dirichlet\n", "basis functions" ] }, { "cell_type": "markdown", "id": "8a6ae7d4", "metadata": { "editable": true }, "source": [ "\n", "\n", "\n", "$$\n", "\\label{eq:shen} \\tag{4}\n", "\\phi_k = T_k-T_{k+2}, \\quad k=0,1, \\ldots, N-3,\n", "$$" ] }, { "cell_type": "markdown", "id": "56b8b2b8", "metadata": { "editable": true }, "source": [ "\n", "\n", "\n", "$$\n", "\\label{eq:heinrichs} \\tag{5}\n", "\\varphi_k = (1-x^2)T_k, \\quad k=0,1, \\ldots, N-3,\n", "$$" ] }, { "cell_type": "markdown", "id": "4bb15362", "metadata": { "editable": true }, "source": [ "\n", "\n", "\n", "$$\n", "\\label{eq:dirichletU} \\tag{6}\n", "\\psi_k = U_k-\\frac{k+1}{k+3}U_{k+2}, \\quad k=0,1, \\ldots, N-3.\n", "$$" ] }, { "cell_type": "markdown", "id": "f0bf61eb", "metadata": { "editable": true }, "source": [ "These three bases are all linearly dependent and they are all bases\n", "for $V_N$." ] }, { "cell_type": "markdown", "id": "0df870c4", "metadata": { "editable": true }, "source": [ "## Implementation\n", "\n", "We can get all three function spaces as" ] }, { "cell_type": "code", "execution_count": 1, "id": "f1f9ca5d", "metadata": { "collapsed": false, "editable": true, "execution": { "iopub.execute_input": "2024-02-19T13:48:39.361760Z", "iopub.status.busy": "2024-02-19T13:48:39.361483Z", "iopub.status.idle": "2024-02-19T13:48:40.276844Z", "shell.execute_reply": "2024-02-19T13:48:40.276127Z" } }, "outputs": [], "source": [ "from shenfun import *\n", "N = 40\n", "V0 = FunctionSpace(N, 'C', basis='ShenDirichlet')\n", "V1 = FunctionSpace(N, 'C', basis='Heinrichs')\n", "V2 = FunctionSpace(N, 'U', basis='CompactDirichlet')" ] }, { "cell_type": "markdown", "id": "e31306bc", "metadata": { "editable": true }, "source": [ "where $V0 = \\text{span}\\{\\phi_k\\}_{k=0}^{N-3}$,\n", "$V1 = \\text{span}\\{\\varphi_k\\}_{k=0}^{N-3}$ and\n", "$V2 = \\text{span}\\{\\psi_k\\}_{k=0}^{N-3}$. Now, to solve the Helmholtz problem we simply need to choose\n", "test and trial bases. Shen's original method is using\n", "`V0` for both. To assemble the stiffness and mass matrices\n", "for this choice do" ] }, { "cell_type": "code", "execution_count": 2, "id": "c0a4b817", "metadata": { "collapsed": false, "editable": true, "execution": { "iopub.execute_input": "2024-02-19T13:48:40.280230Z", "iopub.status.busy": "2024-02-19T13:48:40.279834Z", "iopub.status.idle": "2024-02-19T13:48:40.285055Z", "shell.execute_reply": "2024-02-19T13:48:40.284589Z" } }, "outputs": [], "source": [ "u = TrialFunction(V0)\n", "v = TestFunction(V0)\n", "A = inner(v, div(grad(u)))\n", "B = inner(v, u)" ] }, { "cell_type": "markdown", "id": "ebc3bfe4", "metadata": { "editable": true }, "source": [ "A manufactured solution can be chosen using [Sympy](https://www.sympy.org)\n", "We choose" ] }, { "cell_type": "markdown", "id": "116f1647", "metadata": { "editable": true }, "source": [ "\n", "\n", "\n", "$$\n", "\\begin{equation}\n", "u(x) = \\sin \\left( 2 \\pi \\cos \\left( 2 \\pi x \\right) \\right)\n", "\\label{_auto3} \\tag{7}\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "id": "3ac72ba9", "metadata": { "editable": true }, "source": [ "implemented as" ] }, { "cell_type": "code", "execution_count": 3, "id": "b621fdd7", "metadata": { "collapsed": false, "editable": true, "execution": { "iopub.execute_input": "2024-02-19T13:48:40.288102Z", "iopub.status.busy": "2024-02-19T13:48:40.287531Z", "iopub.status.idle": "2024-02-19T13:48:40.294925Z", "shell.execute_reply": "2024-02-19T13:48:40.294417Z" } }, "outputs": [], "source": [ "import sympy as sp\n", "x = sp.Symbol('x', real=True)\n", "ue = sp.sin(2*sp.pi*sp.cos(2*sp.pi*x))" ] }, { "cell_type": "markdown", "id": "8ed01c45", "metadata": { "editable": true }, "source": [ "The right hand side $f$ of Helmholtz equation is" ] }, { "cell_type": "code", "execution_count": 4, "id": "b7648fc4", "metadata": { "collapsed": false, "editable": true, "execution": { "iopub.execute_input": "2024-02-19T13:48:40.297685Z", "iopub.status.busy": "2024-02-19T13:48:40.297462Z", "iopub.status.idle": "2024-02-19T13:48:40.923369Z", "shell.execute_reply": "2024-02-19T13:48:40.922562Z" } }, "outputs": [], "source": [ "alpha = 1\n", "f = sp.simplify(alpha*ue-ue.diff(x, 2))" ] }, { "cell_type": "markdown", "id": "5c02f31e", "metadata": { "editable": true }, "source": [ "To solve the problem we can do" ] }, { "cell_type": "code", "execution_count": 5, "id": "341119de", "metadata": { "collapsed": false, "editable": true, "execution": { "iopub.execute_input": "2024-02-19T13:48:40.926180Z", "iopub.status.busy": "2024-02-19T13:48:40.925963Z", "iopub.status.idle": "2024-02-19T13:48:40.941262Z", "shell.execute_reply": "2024-02-19T13:48:40.940562Z" } }, "outputs": [], "source": [ "fj = Array(V0, buffer=f) # Get f on quadrature mesh\n", "f_hat = inner(v, fj) # Compute right hand side\n", "M = alpha*B - A # Get coefficient matrix\n", "u_hat = Function(V0) # Container for the solution\n", "sol = la.Solver(M) # Solver\n", "u_hat = sol(f_hat, u_hat) # Solve" ] }, { "cell_type": "markdown", "id": "ff687ebc", "metadata": { "editable": true }, "source": [ "Compare with exact solution." ] }, { "cell_type": "code", "execution_count": 6, "id": "c29b3b52", "metadata": { "collapsed": false, "editable": true, "execution": { "iopub.execute_input": "2024-02-19T13:48:40.944831Z", "iopub.status.busy": "2024-02-19T13:48:40.944580Z", "iopub.status.idle": "2024-02-19T13:48:40.983675Z", "shell.execute_reply": "2024-02-19T13:48:40.982789Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Error = 0.41890381687693135\n" ] } ], "source": [ "uj = Array(V0, buffer=ue)\n", "error = inner(1, (u_hat.backward()-uj)**2)\n", "print('Error =', error)" ] }, { "cell_type": "markdown", "id": "d3104bb4", "metadata": { "editable": true }, "source": [ "Now that was the solution for test and trial bases from the same\n", "basis ([4](#eq:shen)). Let us create a function that takes any\n", "test and any trial basis, any manufactured solution and any $\\alpha$\n", "in the Helmholtz equation. We let the function return either\n", "the L2-error norm, the condition number of the Helmholtz\n", "coefficient matrix, or the matrix itself." ] }, { "cell_type": "code", "execution_count": 7, "id": "e7fc2aff", "metadata": { "collapsed": false, "editable": true, "execution": { "iopub.execute_input": "2024-02-19T13:48:40.986318Z", "iopub.status.busy": "2024-02-19T13:48:40.986123Z", "iopub.status.idle": "2024-02-19T13:48:41.038794Z", "shell.execute_reply": "2024-02-19T13:48:41.037921Z" } }, "outputs": [], "source": [ "def main(N, test, trial, alpha=1, method=0, ue=sp.sin(2*sp.pi*sp.cos(2*sp.pi*x))):\n", " \"\"\"Solve Helmholtz problem and return L2-error, condition number or matrix\n", "\n", " Parameters\n", " ----------\n", " N : int\n", " Number of quadrature points\n", " test, trial : int\n", " Test and trial functions.\n", " 0 = :math:`T_k-T_{k+2}`\n", " 1 = :math:`(1-x^2)T_k`\n", " 2 = :math:`U_k-\\frac{k+1}{k+3}U_{k+2}`\n", " alpha : Helmholtz parameter\n", " method : int\n", " 0 = Return L2-error norm\n", " 1 = Return condition number of matrix\n", " 2 = Return Helmholtz matrix\n", " ue : Sympy function, optional\n", " The manufactured solution with homogeneous boundary conditions.\n", "\n", " Note\n", " ----\n", " Inhomogeneous boundary conditions require a small rewrite, but is\n", " not difficult.\n", "\n", " \"\"\"\n", " bases = {0: ('C', 'ShenDirichlet'), 1: ('C', 'Heinrichs'), 2: ('U', 'CompactDirichlet')}\n", " test = FunctionSpace(N, bases[test][0], basis=bases[test][1])\n", " trial= FunctionSpace(N, bases[trial][0], basis=bases[trial][1])\n", " # Check that boundary conditions are homogeneous\n", " assert abs(ue.subs(x, -1)) < 1e-8 and abs(ue.subs(x, 1)) < 1e-8\n", " u = TrialFunction(trial)\n", " v = TestFunction(test)\n", " f = sp.simplify(alpha*ue-ue.diff(x, 2))\n", " fj = Array(test, buffer=f) # Get f on quadrature mesh\n", " f_hat = inner(v, fj) # Compute right hand side\n", " B = inner(v, u)\n", " A = inner(v, div(grad(u)))\n", " M = alpha*B-A\n", " if method == 1:\n", " return np.linalg.cond(M.diags('csr').toarray())\n", " if method == 2:\n", " return M\n", "\n", " u_hat = Function(trial)\n", " sol = la.Solver(M)\n", " u_hat = sol(f_hat, u_hat)\n", " uj = Array(trial, buffer=ue)\n", " error = np.sqrt(inner(1, (u_hat.backward()-uj)**2))\n", " return error" ] }, { "cell_type": "markdown", "id": "311641c8", "metadata": { "editable": true }, "source": [ "Let us first try basis ([4](#eq:shen)) as test function and\n", "([5](#eq:heinrichs)) as trial function. Use otherwise\n", "default parameters." ] }, { "cell_type": "code", "execution_count": 8, "id": "6a61d76e", "metadata": { "collapsed": false, "editable": true, "execution": { "iopub.execute_input": "2024-02-19T13:48:41.042436Z", "iopub.status.busy": "2024-02-19T13:48:41.042140Z", "iopub.status.idle": "2024-02-19T13:48:41.474999Z", "shell.execute_reply": "2024-02-19T13:48:41.473797Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3.8866412036007366e-07\n" ] } ], "source": [ "error = main(100, 0, 1)\n", "print(error)" ] }, { "cell_type": "markdown", "id": "19538c44", "metadata": { "editable": true }, "source": [ "So the error is small in deed. Perhaps more interesting, let's\n", "look at the sparsity pattern of the coefficient matrix" ] }, { "cell_type": "code", "execution_count": 9, "id": "21ff1456", "metadata": { "collapsed": false, "editable": true, "execution": { "iopub.execute_input": "2024-02-19T13:48:41.479228Z", "iopub.status.busy": "2024-02-19T13:48:41.478935Z", "iopub.status.idle": "2024-02-19T13:48:42.514067Z", "shell.execute_reply": "2024-02-19T13:48:42.513423Z" } }, "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}