{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2.5 Mixed formulation for second order equations\n",
    "\n",
    "Motivation:\n",
    "* exact flux conservation\n",
    "* useful for a posteriori error estimates\n",
    "* model problem for $4^{th}$ order problems, Stokes, ...\n",
    "\n",
    "\n",
    "We consider the diffusion equation\n",
    "$$\n",
    "\\DeclareMathOperator{\\Div}{div}\n",
    "\\begin{array}{rcll}\n",
    "-\\Div \\lambda \\nabla u & = & f & \\text{ in } \\Omega \\\\\n",
    "u & = & u_D & \\text{ on } \\Gamma_D \\\\\n",
    "\\lambda \\frac{\\partial u}{\\partial n} & = & g & \\text{ on } \\Gamma_N\n",
    "\\end{array}\n",
    "$$\n",
    "\n",
    "### Primal variational formulation\n",
    "\n",
    "Find $u \\in H^1,  u = u_D$ on $\\Gamma_D$ such that\n",
    "\n",
    "$$\n",
    "\\int_\\Omega \\lambda \\nabla u \\nabla v = \\int_\\Omega f v + \\int_{\\Gamma_N} g v\n",
    "\\quad \\forall v \\in H^1, v = 0 \\text{ on } \\Gamma_D\n",
    "$$\n",
    "\n",
    "### First order system\n",
    "\n",
    "Find scalar $u$ and the flux $\\sigma$ such that\n",
    "\n",
    "$$\n",
    "\\lambda^{-1} \\sigma = \\nabla u, \\quad \\Div \\sigma = -f\n",
    "$$\n",
    "\n",
    "with boundary conditions\n",
    "\n",
    "$$\n",
    "\\sigma \\cdot n = g \\text{ on } \\Gamma_N, \\quad \\text{ and } \\quad\n",
    "u = u_D \\text{ on } \\Gamma_D \n",
    "$$\n",
    "\n",
    "\n",
    "### Mixed variational formulation\n",
    "\n",
    "Find $\\sigma \\in H(\\Div)$ and $u \\in L_2$ such that $\\sigma_n = g$ on $\\Gamma_N$ and\n",
    "\n",
    "\\begin{eqnarray*}\n",
    "\\int_\\Omega \\lambda^{-1} \\sigma \\tau + \\int_\\Omega \\Div \\tau u & = & 0 \\\\\n",
    "\\int_\\Omega \\Div \\sigma v  & = &  -\\int_\\Omega f v + \\int_{\\Gamma_D} u_D \\tau_n\n",
    "\\end{eqnarray*}\n",
    "\n",
    "for all test-functions $\\tau \\in H(\\Div)$ and $v \\in L_2$ with $\\tau_n = 0$.\n",
    "\n",
    "Here $\\sigma_n$ is the normal trace operator $\\sigma \\cdot n |_{\\Gamma_N}$, which is meaningful in $H(\\Div)$.\n",
    "\n",
    "\n",
    "A Compact notation is the single-liner \n",
    "\n",
    "Find $(\\sigma, u) \\in H(\\Div) \\times L_2$ such that $\\sigma_n = g$ on $\\Gamma_N$ and\n",
    "\n",
    "$$\n",
    "\\int_\\Omega \\lambda^{-1} \\sigma \\tau + \\Div \\sigma v + \\Div \\tau u = \n",
    "-\\int_\\Omega f v + \\int_{\\Gamma_D} u_D \\tau_n\n",
    "$$\n",
    "\n",
    "for all test-functions $(\\tau, v) \\in H(\\Div) \\times L_2$ with $\\tau_n = 0$.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ngsolve import *\n",
    "from ngsolve.webgui import Draw\n",
    "\n",
    "mesh = Mesh(unit_square.GenerateMesh(maxh=0.1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "source = sin(3.14*x)\n",
    "ud = CF(5)\n",
    "g = mesh.BoundaryCF( {\"left\" : y*(1-y)}, default=0)\n",
    "lam = 10"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Setup and solve primal problem:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fesp = H1(mesh, order=4, dirichlet=\"bottom\")\n",
    "up, vp = fesp.TnT()\n",
    "\n",
    "ap = BilinearForm(lam*grad(up)*grad(vp)*dx).Assemble()\n",
    "fp = LinearForm(source*vp*dx + g*vp * ds).Assemble()\n",
    "\n",
    "gfup = GridFunction(fesp)\n",
    "gfup.Set(ud, BND)\n",
    "\n",
    "r = fp.vec - ap.mat * gfup.vec\n",
    "gfup.vec.data += ap.mat.Inverse(freedofs=fesp.FreeDofs()) * r\n",
    "Draw (gfup)\n",
    "Draw (lam * grad(gfup), mesh, \"flux-primal\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Solving the mixed problem\n",
    "Define spaces for mixed problem. Note that essential boundary conditions  are set to the $H(\\Div)$-component on the opposite boundary. Creating a space from a list of spaces generates a product space:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "order_flux=1\n",
    "V = HDiv(mesh, order=order_flux, dirichlet=\"right|top|left\")\n",
    "Q = L2(mesh, order=order_flux-1)\n",
    "fesm = V*Q"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The space provides now a list of trial and test-functions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sigma, u = fesm.TrialFunction()\n",
    "tau, v = fesm.TestFunction()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The normal vector is provided as a *special* coefficient function (which may be used only at the boundary). The orientation depends on the definition of the geometry. In 2D, it is the tangential vector rotated to the right, and is the outer vector in our case. Since every CoefficientFunction must know its vector-dimension, we have to provide the spatial dimension:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "normal = specialcf.normal(mesh.dim)\n",
    "print (normal)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Define the forms on the product space. For the boundary term, we have to use the Trace operator, which provides the projection to normal direction. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "am = BilinearForm((1/lam*sigma*tau + div(sigma)*v + div(tau)*u)*dx).Assemble()\n",
    "fm = LinearForm(-source*v*dx + ud*(tau.Trace()*normal)*ds).Assemble()\n",
    "\n",
    "gfm = GridFunction(fesm)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The proxy-functions used for the forms know to which component of the product space they belong to. To access components of the solution, we have to unpack its components. They don't have own coefficient vectors, but refer to ranges of the big coefficient vector."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gfsigma, gfu = gfm.components"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Just to try something:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gfsigma.Set(CF( (sin(10*x), sin(10*y)) ))\n",
    "gfu.Set (x)\n",
    "Draw (gfsigma, mesh, \"flux-mixed\")\n",
    "Draw (gfu, mesh, \"u-mixed\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now set the essential boundary conditions for the flux part:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gfsigma.Set(g*normal, BND)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "res = fm.vec.data - am.mat * gfm.vec\n",
    "gfm.vec.data += am.mat.Inverse(freedofs=fesm.FreeDofs(), inverse=\"umfpack\") * res\n",
    "# solvers.BVP(bf=am, lf=fm, gf=gfm)\n",
    "Draw (gfsigma, mesh, \"flux-mixed\")\n",
    "Draw (gfu, mesh, \"u-mixed\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Calculate the difference:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print (\"err-u:   \", sqrt(Integrate( (gfup-gfu)**2, mesh)))\n",
    "errflux = lam * grad(gfup) - gfsigma\n",
    "print (\"err-flux:\", sqrt(Integrate(errflux*errflux, mesh)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Post-processing for the scalar variable\n",
    "\n",
    "The scalar variable is approximated one order lower than the vector variable, what is its gradient. Knowing the gradient of a function more accurate, and knowing its mean value, one can recover the function itself. For this post-processing trick we refer to [Arnold+Brezzi 85]\n",
    "\n",
    "\n",
    "find $\\widehat u \\in P^{k+1, dc}$ and $\\widehat \\lambda \\in P^{0, dc}$ such that\n",
    "\n",
    "$$\n",
    "\\begin{array}{ccccl}\n",
    "\\int \\lambda \\nabla \\widehat u \\nabla \\widehat v & + & \\int \\widehat \\lambda \\widehat v & = & \\int \\sigma \\nabla \\widehat v & \\forall \\, \\widehat v\\\\\n",
    "\\int \\widehat u \\widehat \\mu & & & = & \\int u \\widehat \\mu & \\forall \\, \\widehat \\mu\n",
    "\\end{array}\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fespost_u = L2(mesh, order=order_flux+1)\n",
    "fespost_lam = L2(mesh, order=0)\n",
    "fes_post = fespost_u*fespost_lam\n",
    "\n",
    "(u,la),(v,mu) = fes_post.TnT()\n",
    "\n",
    "a = BilinearForm( (lam*grad(u)*grad(v)+la*v+mu*u)*dx).Assemble()\n",
    "f = LinearForm((gfsigma*grad(v)+gfu*mu)*dx).Assemble()\n",
    "\n",
    "gfpost = GridFunction(fes_post)\n",
    "gfpost.vec.data = a.mat.Inverse() * f.vec\n",
    "\n",
    "Draw (gfpost.components[0], mesh, \"upost\")\n",
    "print (\"err-upost:   \", sqrt(Integrate( (gfup-gfpost.components[0])**2, mesh)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
