{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Level-set method\n", "***" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Introduction\n", "The level-set method is a method designed for modeling material interaction. The main idea is based on a function $l$, which satisfies the following.\n", "\n", "$$ l(x) \\begin{cases}\n", " > 0 \\text{ if } x \\in \\Omega_1 \\\\\n", " < 0 \\text{ in } x \\in \\Omega_2\n", " \\end{cases}. $$\n", "\n", "We will focus only on the interaction between two incompressible Navier-Stokes equations with different\n", "constant parameters $\\rho_i, \\mu_i \\in \\Omega_i$ for $i \\in \\{ 1, 2 \\}$. The function $l$ allows us to define\n", "viscosity $\\mu$ and density $\\rho$ in the whole domain $\\Omega$ as a single\n", "function\n", "\\begin{equation*}\n", " \\mu(l) = \\frac{1}{2}(\\text{sign}(l) + 1)\\mu_1 + \\frac{1}{2}(\\text{sign}(l) - 1) \\mu_2\n", "\\end{equation*}\n", "and\n", "\\begin{equation*}\n", " \\rho(l) = \\frac{1}{2}(\\text{sign}(l) + 1)\\rho_1 + \\frac{1}{2}(\\text{sign}(l) - 1) \\rho_2.\n", "\\end{equation*}\n", "Because we would like to solve the evolution problem, the $\\Omega_i$ and the function $l$\n", "have to depend on time. We want $l$ to be constant along streamlines for the prescribed velocity field $v$.\n", "This condition is satisfied if\n", "\n", "\\begin{equation*}\n", "\\partial_t l(x, t) + \\text{div}(l(x, t) v(x, t)) = 0.\n", "\\end{equation*}\n", "\n", "We can now formulate the whole system, firstly in the strong sense.\n", "$$ \\rho(l) \\left( \\partial_t v + (v \\cdot \\nabla) v \\right) = \\text{div}\\left(\\mathbb{T}(\\mu(l), \\nabla v)\\right) + \\rho(l)g,$$\n", "\n", "$$ \\mathbb{T}(\\mu(l), \\nabla v) = \\mu(l)\\left( \\nabla v + (\\nabla v)^T \\right) - p\\mathbb{I}, $$\n", "\n", "$$ \\partial_t l + \\text{div}(l v) = 0, $$\n", "\n", "$$ \\text{div}(v) = 0. $$" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Rayleigh-Taylor Example\n", "Let us assume a rectangular domain with two fluids as it is desctibed in the figure below. We will cosidere Navier-Stokes fluid for both of them.\n", "The fluids will stick on the boundary of the domain, so $v = 0$ at $\\partial \\Omega$.\n", "
\n", "\n", "
" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Implementation" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import dolfin as df\n", "import matplotlib.pyplot as plt" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Then we define the mesh." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "mesh = df.RectangleMesh(\n", " df.Point(0.0, -0.5), df.Point(0.25, 0.5), 20, 80, 'crossed'\n", ")\n", "df.plot(mesh)\n", "plt.show()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "We define the initial conditions for velocity and levelset. " ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "class InitialCondition(df.UserExpression):\n", "\n", " def __init__(self, **kwargs):\n", " self.center = [0.125, 0.25]\n", " self.radius = df.sqrt(0.125**2 + 0.25**2)\n", " super().__init__(**kwargs)\n", " \n", " def r(self, x: list) -> float:\n", " return df.sqrt((x[0] - self.center[0])**2 + (x[1] - self.center[1])**2)\n", "\n", " def eval(self, values, x):\n", " values[0] = 0.0 # v_x\n", " values[1] = 0.0 # v_y\n", " values[2] = 0.0 # p\n", " values[3] = self.r(x) - self.radius # l\n", "\n", " def value_shape(self):\n", " return (4, )\n", "# create instance of the class.\n", "# initial_conditions = InitialCondition()\n", "\n", "\n", "class InitialConditionSigmoid(df.UserExpression):\n", "\n", " def __init__(self, **kwargs):\n", " self.center = [0.125, 0.25]\n", " self.radius = df.sqrt(0.125**2 + 0.25**2)\n", " self.eps = 0.001 \n", " super().__init__(**kwargs)\n", " \n", " def r(self, x: list) -> float:\n", " return df.sqrt((x[0] - self.center[0])**2 + (x[1] - self.center[1])**2)\n", "\n", " def eval(self, values, x):\n", " values[0] = 0.0 # v_x\n", " values[1] = 0.0 # v_y\n", " values[2] = 0.0 # p\n", " values[3] = 1 / (1 + df.exp(min((self.radius - self.r(x) ) / self.eps, 10)))\n", " # values[3] = 1 / (df.exp( (self.r(x) - self.radius) / self.eps ))\n", "\n", " def value_shape(self):\n", " return (4, )\n", "\n", "initial_conditions = InitialConditionSigmoid()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Then we define approximation of the signum function as\n", "\\begin{equation}\n", " \\text{sign}(l) = \\frac{l}{\\sqrt(l^2 + \\varepsilon^2\\nabla l \\cdot \\nabla l)} \n", "\\end{equation}" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "# def sign(q: df.Function, eps: float):\n", "# return q / df.sqrt(q * q + eps * eps * df.inner(df.grad(q), df.grad(q)))\n", "\n", "def sign(q: df.Function, eps: float):\n", " return 2 * (q - 0.5) " ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Further we create the function spaces. We use \"CG\" for each subspace." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "elements = [\n", " df.VectorElement(\"CG\", mesh.ufl_cell(), 2), # velocity\n", " df.FiniteElement(\"CG\", mesh.ufl_cell(), 1), # pressure\n", " df.FiniteElement(\"CG\", mesh.ufl_cell(), 1) # levelset\n", "]\n", "\n", "function_space = df.FunctionSpace(\n", " mesh, df.MixedElement(elements)\n", ")" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Now we define all the constants. " ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "material_params = {\n", " \"mu1\": 1.0,\n", " \"mu2\": 1.0,\n", " \"rho1\": 500,\n", " \"rho2\": 1000,\n", "}\n", "eps = 1e-4\n", "dt = 0.02\n", "t_start = 0.0\n", "t_end = 1.0\n", "g = df.Constant((0.0, -10.0)) # gravity field" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Further, we create the boundary conditions for velocity. In particular we \n", "require that $v = 0$ on the whole boundary of the domain." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "bcs = [\n", " df.DirichletBC(\n", " function_space.sub(0), # v\n", " df.Constant((0.0, 0.0)),\n", " \"on_boundary\"\n", " )\n", "]" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Now we define the funcions on our space and interpolate the initial condition on the space." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "w = df.Function(function_space) # unknown\n", "w0 = df.Function(function_space) # from previous step\n", "phi = df.TestFunction(function_space) # test function\n", "w0.assign(df.interpolate(initial_conditions, function_space))\n", "w.assign(w0)\n", "\n", "# Split functions\n", "v, p, l = df.split(w)\n", "v0, p0, l0 = df.split(w0)\n", "phi_v, phi_p, phi_l = df.split(phi)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "We will need to formulate the functions $\\rho$ and $\\mu$. " ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "def rho(params: dict, l: df.Function, eps: float, sign):\n", " return (\n", " params[\"rho1\"] * 0.5* (1.0 + sign(l, eps))\n", " + params[\"rho2\"] * 0.5 * (1.0 - sign(l, eps))\n", " )\n", "\n", "\n", "def mu(params: dict, l: df.Function, eps: float, sign):\n", " return (\n", " params[\"mu1\"] * 0.5 * (1.0 + sign(l, eps))\n", " + params[\"mu2\"] * 0.5 * (1.0 - sign(l, eps))\n", " )" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "It is time to write down the equations.\n", "\\begin{equation*}\n", " \\mathbb{T} = \\mu (\\nabla v + (\\nabla v)^T) - p \\mathbb{I} \n", "\\end{equation*}\n", "\n", "\\begin{equation*}\n", " \\int_{\\Omega} \\rho \\partial_t v \\cdot \\varphi_{v} + \\rho ((\\nabla v) v) \\cdot \\varphi_v \\; dx + \\int_{\\Omega} \\mathbb{T} \\cdot \\nabla \\varphi_{v}\\; dx = 0\n", "\\end{equation*}\n", "\n", "\\begin{equation*}\n", " \\int_{\\Omega} \\text{div}(v)\\varphi_p \\; dx = 0\n", "\\end{equation*}\n", "\n", "\\begin{equation*}\n", " \\int_{\\Omega} (\\partial_t l) \\varphi_l + \\text{div}(l v) \\varphi_l \\;dx = 0 \n", "\\end{equation*}" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "n = df.FacetNormal(mesh)\n", "h = df.CellDiameter(mesh)\n", "h_avg = (h('+') + h('-')) / 2.0\n", "alpha = df.Constant(0.1)\n", "\n", "cauchy_green = (\n", " mu(material_params, l, eps, sign) * (df.grad(v) + df.grad(v).T)\n", " - p * df.Identity(mesh.topology().dim())\n", ")\n", "\n", "material_detivative = (\n", " (1 / dt) * df.inner(v - v0, phi_v) # partial time derivative\n", " + df.inner(df.grad(v) * v, phi_v) # convective therm\n", ")\n", "\n", "momentum = (\n", " rho(material_params, l, eps, sign) * material_detivative*df.dx\n", " + df.inner(cauchy_green, df.grad(phi_v))*df.dx\n", " - rho(material_params, l, eps, sign)\n", " * df.inner(g, phi_v) * df.dx\n", ")\n", "\n", "mass = (\n", " df.div(v) * phi_p*df.dx\n", ")\n", "\n", "levelset_convection = (\n", " (1 / dt) * df.inner(l - l0, phi_l) * df.dx\n", " + df.div(l * v) * phi_l * df.dx\n", ")\n", "\n", "stabilization = (\n", " alpha('+') * (h_avg**2)\n", " * df.inner(df.jump(df.grad(l), n), df.jump(df.grad(phi_l), n)) * df.dS\n", ")\n", "\n", "pde_form = momentum + mass + levelset_convection + stabilization" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Our system is not linear, thus we need to solve it with Newton solver. Let's define it now!" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "# Set Newton-solver\n", "# form compiler parameter\n", "ffc_options = {\n", " \"quadrature_degree\": 4,\n", " \"optimize\": True,\n", " \"eliminate_zeros\": True\n", "}\n", "\n", "J = df.derivative(pde_form, w)\n", "problem = df.NonlinearVariationalProblem(pde_form, w, bcs, J, ffc_options)\n", "solver = df.NonlinearVariationalSolver(problem)\n", "\n", "prm = solver.parameters\n", "prm['nonlinear_solver'] = 'newton'\n", "prm['newton_solver']['linear_solver'] = 'mumps'\n", "prm['newton_solver']['lu_solver']['report'] = False\n", "prm['newton_solver']['absolute_tolerance'] = 1E-10\n", "prm['newton_solver']['relative_tolerance'] = 1E-10\n", "prm['newton_solver']['maximum_iterations'] = 20\n", "prm['newton_solver']['report'] = True" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "We create the XDMF files for storing the results." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "# Initialize the files for writing the results\n", "files = []\n", "for name in ['v', 'p', 'l']:\n", " with df.XDMFFile(df.MPI.comm_world, f\"result/{name}.xdmf\") as xdmf:\n", " xdmf.parameters[\"flush_output\"] = True\n", " files.append(xdmf)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "It remains to solve it!!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t = t_start\n", "while t < t_end:\n", " df.info(f\"t = {t}\")\n", " solver.solve()\n", " w0.assign(w)\n", " t += dt\n", " # write the time-step into the file\n", " for func, name, xdmf in zip(w.split(True), ['v', 'p', 'l'], files):\n", " func.rename(name, name)\n", " xdmf.write(func, t)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Complete Code" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import dolfin as df\n", "import matplotlib.pyplot as plt\n", "\n", "mesh = df.RectangleMesh(\n", " df.Point(0.0, -0.5), df.Point(0.25, 0.5), 20, 80, 'crossed'\n", ")\n", "df.plot(mesh)\n", "plt.show()\n", "\n", "class InitialCondition(df.UserExpression):\n", "\n", " def __init__(self, **kwargs):\n", " self.center = [0.125, 0.25]\n", " self.radius = df.sqrt(0.125**2 + 0.25**2)\n", " super().__init__(**kwargs)\n", " \n", " def r(self, x: list) -> float:\n", " return df.sqrt((x[0] - self.center[0])**2 + (x[1] - self.center[1])**2)\n", "\n", " def eval(self, values, x):\n", " values[0] = 0.0 # v_x\n", " values[1] = 0.0 # v_y\n", " values[2] = 0.0 # p\n", " values[3] = self.r(x) - self.radius # l\n", "\n", " def value_shape(self):\n", " return (4, )\n", "# create instance of the class.\n", "# initial_conditions = InitialCondition()\n", "\n", "\n", "class InitialConditionSigmoid(df.UserExpression):\n", "\n", " def __init__(self, **kwargs):\n", " self.center = [0.125, 0.25]\n", " self.radius = df.sqrt(0.125**2 + 0.25**2)\n", " self.eps = 0.001 \n", " super().__init__(**kwargs)\n", " \n", " def r(self, x: list) -> float:\n", " return df.sqrt((x[0] - self.center[0])**2 + (x[1] - self.center[1])**2)\n", "\n", " def eval(self, values, x):\n", " values[0] = 0.0 # v_x\n", " values[1] = 0.0 # v_y\n", " values[2] = 0.0 # p\n", " values[3] = 1 / (1 + df.exp(min((self.radius - self.r(x) ) / self.eps, 10)))\n", " # values[3] = 1 / (df.exp( (self.r(x) - self.radius) / self.eps ))\n", "\n", " def value_shape(self):\n", " return (4, )\n", "\n", "initial_conditions = InitialConditionSigmoid()\n", "\n", "# def sign(q: df.Function, eps: float):\n", "# return q / df.sqrt(q * q + eps * eps * df.inner(df.grad(q), df.grad(q)))\n", "\n", "def sign(q: df.Function, eps: float):\n", " return 2 * (q - 0.5) \n", "\n", "elements = [\n", " df.VectorElement(\"CG\", mesh.ufl_cell(), 2), # velocity\n", " df.FiniteElement(\"CG\", mesh.ufl_cell(), 1), # pressure\n", " df.FiniteElement(\"CG\", mesh.ufl_cell(), 1) # levelset\n", "]\n", "\n", "function_space = df.FunctionSpace(\n", " mesh, df.MixedElement(elements)\n", ")\n", "\n", "material_params = {\n", " \"mu1\": 1.0,\n", " \"mu2\": 1.0,\n", " \"rho1\": 500,\n", " \"rho2\": 1000,\n", "}\n", "eps = 1e-4\n", "dt = 0.02\n", "t_start = 0.0\n", "t_end = 1.0\n", "g = df.Constant((0.0, -10.0)) # gravity field\n", "\n", "bcs = [\n", " df.DirichletBC(\n", " function_space.sub(0), # v\n", " df.Constant((0.0, 0.0)),\n", " \"on_boundary\"\n", " )\n", "]\n", "\n", "w = df.Function(function_space) # unknown\n", "w0 = df.Function(function_space) # from previous step\n", "phi = df.TestFunction(function_space) # test function\n", "w0.assign(df.interpolate(initial_conditions, function_space))\n", "w.assign(w0)\n", "\n", "# Split functions\n", "v, p, l = df.split(w)\n", "v0, p0, l0 = df.split(w0)\n", "phi_v, phi_p, phi_l = df.split(phi)\n", "\n", "def rho(params: dict, l: df.Function, eps: float, sign):\n", " return (\n", " params[\"rho1\"] * 0.5* (1.0 + sign(l, eps))\n", " + params[\"rho2\"] * 0.5 * (1.0 - sign(l, eps))\n", " )\n", "\n", "\n", "def mu(params: dict, l: df.Function, eps: float, sign):\n", " return (\n", " params[\"mu1\"] * 0.5 * (1.0 + sign(l, eps))\n", " + params[\"mu2\"] * 0.5 * (1.0 - sign(l, eps))\n", " )\n", "\n", "n = df.FacetNormal(mesh)\n", "h = df.CellDiameter(mesh)\n", "h_avg = (h('+') + h('-')) / 2.0\n", "alpha = df.Constant(0.1)\n", "\n", "cauchy_green = (\n", " mu(material_params, l, eps, sign) * (df.grad(v) + df.grad(v).T)\n", " - p * df.Identity(mesh.topology().dim())\n", ")\n", "\n", "material_detivative = (\n", " (1 / dt) * df.inner(v - v0, phi_v) # partial time derivative\n", " + df.inner(df.grad(v) * v, phi_v) # convective therm\n", ")\n", "\n", "momentum = (\n", " rho(material_params, l, eps, sign) * material_detivative*df.dx\n", " + df.inner(cauchy_green, df.grad(phi_v))*df.dx\n", " - rho(material_params, l, eps, sign)\n", " * df.inner(g, phi_v) * df.dx\n", ")\n", "\n", "mass = (\n", " df.div(v) * phi_p*df.dx\n", ")\n", "\n", "levelset_convection = (\n", " (1 / dt) * df.inner(l - l0, phi_l) * df.dx\n", " + df.div(l * v) * phi_l * df.dx\n", ")\n", "\n", "stabilization = (\n", " alpha('+') * (h_avg**2)\n", " * df.inner(df.jump(df.grad(l), n), df.jump(df.grad(phi_l), n)) * df.dS\n", ")\n", "\n", "pde_form = momentum + mass + levelset_convection + stabilization\n", "\n", "# Set Newton-solver\n", "# form compiler parameter\n", "ffc_options = {\n", " \"quadrature_degree\": 4,\n", " \"optimize\": True,\n", " \"eliminate_zeros\": True\n", "}\n", "\n", "J = df.derivative(pde_form, w)\n", "problem = df.NonlinearVariationalProblem(pde_form, w, bcs, J, ffc_options)\n", "solver = df.NonlinearVariationalSolver(problem)\n", "\n", "prm = solver.parameters\n", "prm['nonlinear_solver'] = 'newton'\n", "prm['newton_solver']['linear_solver'] = 'mumps'\n", "prm['newton_solver']['lu_solver']['report'] = False\n", "prm['newton_solver']['absolute_tolerance'] = 1E-10\n", "prm['newton_solver']['relative_tolerance'] = 1E-10\n", "prm['newton_solver']['maximum_iterations'] = 20\n", "prm['newton_solver']['report'] = True\n", "\n", "# Initialize the files for writing the results\n", "files = []\n", "for name in ['v', 'p', 'l']:\n", " with df.XDMFFile(df.MPI.comm_world, f\"result/{name}.xdmf\") as xdmf:\n", " xdmf.parameters[\"flush_output\"] = True\n", " files.append(xdmf)\n", "\n", "t = t_start\n", "while t < t_end:\n", " df.info(f\"t = {t}\")\n", " solver.solve()\n", " w0.assign(w)\n", " t += dt\n", " # write the time-step into the file\n", " for func, name, xdmf in zip(w.split(True), ['v', 'p', 'l'], files):\n", " func.rename(name, name)\n", " xdmf.write(func, t)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.8.10" }, "orig_nbformat": 4, "vscode": { "interpreter": { "hash": "767d51c1340bd893661ea55ea3124f6de3c7a262a8b4abca0554b478b1e2ff90" } } }, "nbformat": 4, "nbformat_minor": 2 }