├── .gitignore ├── CahnHilliard ├── CahnHilliard.html ├── CahnHilliard.ipynb ├── CahnHilliard.py └── figs │ ├── .ipynb_checkpoints │ └── CahnHilliard_domain-checkpoint.png │ └── CahnHilliard_domain.png ├── LICENSE ├── README.md ├── elasticity ├── elasticity_clamped.html ├── elasticity_clamped.ipynb ├── elasticity_clamped.py ├── elasticity_tractions.html ├── elasticity_tractions.ipynb ├── elasticity_tractions.py └── figs │ ├── .ipynb_checkpoints │ └── elasticity_clamped_domain-checkpoint.png │ ├── elasticity_clamped_domain.png │ └── elasticity_tractions_domain.png ├── poisson ├── figs │ ├── poisson_basic_domain.png │ └── poisson_general_domain.png ├── poisson_basic.html ├── poisson_basic.ipynb ├── poisson_basic.py ├── poisson_basic2.html ├── poisson_basic2.ipynb ├── poisson_basic2.py ├── poisson_general.html ├── poisson_general.ipynb ├── poisson_general.py ├── poisson_minimization.html ├── poisson_minimization.ipynb ├── poisson_minimization.py ├── poisson_mixed.html ├── poisson_mixed.ipynb └── poisson_mixed.py └── thermoelasticity ├── figs └── thermoelasticity_domain.png ├── thermoelasticity.html ├── thermoelasticity.ipynb └── thermoelasticity.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /CahnHilliard/CahnHilliard.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Cahn-Hilliard equation\n", 8 | "\n", 9 | "This example is implemented in the Python file CahnHilliard.py and it illustrates how to:\n", 10 | "\n", 11 | "- Implement periodic boundary conditions;\n", 12 | "- Implement initial conditions with small random perturbation.\n", 13 | "\n", 14 | "Note that this example is based on the FEniCS demo: click here." 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "## Equation and problem definition" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": {}, 27 | "source": [ 28 | "The Cahn-Hilliard equation describes the process of phase separation, by which the two components of a binary fluid spontaneously separate and form domains enriched in one of the two components. The driving force for the phase separation is the tendency of the system to lower the total free energy\n", 29 | "$$G[c] = \\int_\\Omega d{\\bf x}\\, \\left[f(c) + \\frac{1}{2}\\lambda (\\nabla c)^2\\right],$$\n", 30 | "where $c$ is an order parameter and the two phases correspond to $c=0$ and $c=1$. The first term in the above equation describes the bulk free energy and the second term the interfacial energy between the two phases. The bulk free energy density is commonly described with the double well potential and in this example we use the free energy density $f(c)=100 c^2 (1-c)^2$." 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "To describe the kinetics of the phase separation process it is convenient to first introduce the chemical potential $\\mu$, which can be obtained as the functional derivative of the free energy. More precisely, we calculate the variation of the free energy $G[c+\\delta c]=G[c]+\\delta G$, where the variation is\n", 38 | "$$\\delta G= \\int_\\Omega d{\\bf x} \\ \\left[f'(c) \\delta c + \\lambda \\nabla c \\cdot \\nabla \\delta c\\right]=\\int_\\Omega d{\\bf x}\\ \\left[f'(c) - \\lambda (\\nabla^2 c) \\right] \\delta c \\equiv \\int_\\Omega d{\\bf x}\\ \\mu \\delta c,$$\n", 39 | "where we used integration by parts and defined the chemical potential\n", 40 | "$$\\mu = f'(c) - \\lambda \\nabla^2 c.$$\n", 41 | "The gradients of the chemical potential provide the driving force for the redistribution of material, and this kinetics is described with\n", 42 | "$$\\frac{\\partial c}{\\partial t} = \\nabla \\cdot (M \\nabla \\mu),$$\n", 43 | "where $M$ is the mobility." 44 | ] 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "metadata": {}, 49 | "source": [ 50 | "### Discretizaton of time \n", 51 | "\n", 52 | "In order to solve the two coupled PDEs for the concentration $c({\\bf x},t)$ and chemical potential $\\mu({\\bf x},t)$, we first discuss the time discretization. Fields are evaluated at discrete timesteps $t_n = n \\Delta t$, where $n=0,1,2,\\ldots$. We use the notation $c_{n}({\\bf x})\\equiv c({\\bf x},t_n)$ and $\\mu_{n}({\\bf x})\\equiv \\mu({\\bf x},t_n)$. The two coupled PDEs can then be written as \n", 53 | " $$\\begin{aligned}\n", 54 | " \\frac{c_{n+1}-c_{n}}{\\Delta t} &= \\nabla \\cdot (M \\nabla \\mu_{n+\\theta}),\\\\\n", 55 | " \\mu_{n+1} &= f'(c_{n+1}) - \\lambda \\nabla^2 c_{n+1},\\\\\n", 56 | " \\end{aligned}$$ \n", 57 | "where $\\mu_{n+\\theta} = (1-\\theta) \\mu_{n} + \\theta \\mu_{n+1}$. Different choices for the parameter $\\theta$ correspond to the forward Euler method ($\\theta=0$), the backward Euler method ($\\theta=1$), and the Crank–Nicolson method ($\\theta=1/2$). Check the Wikipedia article for more information about the differences between these three methods. In this example we use the Crank–Nicolson method ($\\theta=1/2$). The general procedure is to start with the initial concentrations $c_0({\\bf x})$, from which we can calculate the chemical potentials $\\mu_0({\\bf x})$ at time 0. Then we iteratively calculate the fields ($c_{n+1}$, $\\mu_{n+1}$) at time $t_{n+1}$ from the values of fields ($c_{n+1}$, $\\mu_{n+1}$) at time $t_n$." 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "metadata": {}, 63 | "source": [ 64 | "### Weak formulation of the problem\n", 65 | "\n", 66 | "The final step is to write the weak formulation of the coupled PDEs\n", 67 | " $$\\begin{aligned}\\int_\\Omega d{\\bf x} \\\n", 68 | " \\left[\\frac{c_{n+1}-c_{n}}{\\Delta t} - \\nabla \\cdot (M \\nabla \\mu_{n+\\theta})\\right] q & = 0,\\\\\n", 69 | " \\int_\\Omega d{\\bf x} \\ \\left[\\mu_{n+1} - f'(c_{n+1}) + \\lambda \\nabla^2 c_{n+1}\\right] v & = 0,\\\\\n", 70 | " \\end{aligned}$$\n", 71 | "where we introduced two *test functions* $q$ and $v$. After the integration by parts the two coupled PDEs can be rewritten as\n", 72 | " $$\\begin{aligned}\\int_\\Omega d{\\bf x} \\\n", 73 | " \\left[\\frac{c_{n+1}-c_{n}}{\\Delta t} q + M \\nabla \\mu_{n+\\theta} \\cdot \\nabla q\\right] & = 0,\\\\\n", 74 | " \\int_\\Omega d{\\bf x} \\ \\left[\\mu_{n+1} v - f'(c_{n+1})v - \\lambda \\nabla c_{n+1} \\cdot \\nabla v\\right] & = 0.\\\\\n", 75 | " \\end{aligned}$$\n", 76 | "In these example, we solve the Cahn-Hilliard equation on a unit square domain with periodic boundary conditions. The intial concentration values are chosen randomly on the interval $(0.62,0.64)$. Other values are chosen as in the FEniCS demo, i.e. $\\lambda = 10^{-2}$ and $\\Delta t = 5 \\times 10^{-6}$." 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "metadata": {}, 82 | "source": [ 83 | "## Implementation" 84 | ] 85 | }, 86 | { 87 | "cell_type": "markdown", 88 | "metadata": {}, 89 | "source": [ 90 | "As usual, in order to use solve this problem we need to import all necessary modules." 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": 1, 96 | "metadata": {}, 97 | "outputs": [], 98 | "source": [ 99 | "from __future__ import print_function\n", 100 | "import random\n", 101 | "from fenics import *\n", 102 | "from dolfin import *\n", 103 | "from mshr import *" 104 | ] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "metadata": {}, 109 | "source": [ 110 | "To implement periodic boundary conditions, we first note that the boundary of the unit square domain can be divided into two subdomains: the unique points on $\\Gamma_{inside}$ and the points on $\\Gamma_{mapped}$ that can be mapped to the $\\Gamma_{inside}$ with periodic boundary conditions. Here we choose the bottom and left boundaries to be in the set $\\Gamma_{inside}$ except for the corner points $(1,0)$ and $(0,1)$, which can be mapped to the point (0,0).\n", 111 | "
\n", 112 | " \n", 113 | "
" 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "metadata": {}, 119 | "source": [ 120 | "To impose periodic boundary conditions in FEniCS, we implement the `SubDomain` class. In this class we have to provide two functions called `inside` and `map`. The `inside` function should return `True` for the unique boundary points on $\\Gamma_{inside}$. The `map` function tells how the boundary points on $\\Gamma_{mapped}$ are mapped to the points on $\\Gamma_{inside}$. " 121 | ] 122 | }, 123 | { 124 | "cell_type": "code", 125 | "execution_count": 2, 126 | "metadata": {}, 127 | "outputs": [], 128 | "source": [ 129 | "# Sub domain for Periodic boundary condition\n", 130 | "class PeriodicBoundary(SubDomain):\n", 131 | "\n", 132 | " def inside(self, x, on_boundary):\n", 133 | " # return True if on left or bottom boundary AND NOT\n", 134 | " # on one of the two corners (0, 1) and (1, 0)\n", 135 | " return bool((near(x[0], 0) or near(x[1], 0)) and\n", 136 | " (not ((near(x[0], 0) and near(x[1], 1)) or\n", 137 | " (near(x[0], 1) and near(x[1], 0)))) and on_boundary)\n", 138 | "\n", 139 | " def map(self, x, y):\n", 140 | " if near(x[0], 1) and near(x[1], 1):\n", 141 | " y[0] = x[0] - 1.\n", 142 | " y[1] = x[1] - 1.\n", 143 | " elif near(x[0], 1):\n", 144 | " y[0] = x[0] - 1.\n", 145 | " y[1] = x[1]\n", 146 | " else: # near(x[1], 1)\n", 147 | " y[0] = x[0]\n", 148 | " y[1] = x[1] - 1." 149 | ] 150 | }, 151 | { 152 | "cell_type": "markdown", 153 | "metadata": {}, 154 | "source": [ 155 | "Next, we create a mesh for a unit square domain and define the function space, where periodic boundary conditions are imposed with the `constrained_domain` argument. Here we use mixed elements, where the first ellement corresponds to the concentration field $c$ and the second element to the chemical potential field $\\mu$. " 156 | ] 157 | }, 158 | { 159 | "cell_type": "code", 160 | "execution_count": 3, 161 | "metadata": {}, 162 | "outputs": [], 163 | "source": [ 164 | "# Create mesh and define function space with periodic boundary conditions\n", 165 | "mesh = UnitSquareMesh.create(96, 96, CellType.Type.quadrilateral)\n", 166 | "P = FiniteElement('Lagrange', mesh.ufl_cell(), 1)\n", 167 | "MFS = FunctionSpace(mesh, MixedElement([P,P]),constrained_domain=PeriodicBoundary())" 168 | ] 169 | }, 170 | { 171 | "cell_type": "markdown", 172 | "metadata": {}, 173 | "source": [ 174 | "We define functions ${\\bf u}_{new}$ and ${\\bf u}_{old}$, where we store fields $(c_{n+1},\\mu_{n+1})$ and $(c_{n},\\mu_{n})$, respectively. We also split the function into the concentration and chemical potential fields. In a similar way we introduce the test function, which is split into two test functions $q$ and $v$." 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": 4, 180 | "metadata": {}, 181 | "outputs": [], 182 | "source": [ 183 | "# Define functions\n", 184 | "u_new = Function(MFS) # solution for the next step\n", 185 | "u_old = Function(MFS) # solution from previous step\n", 186 | "u_new.rename(\"fields\",\"\")\n", 187 | "# Split mixed functions\n", 188 | "c_new, mu_new = split(u_new)\n", 189 | "c_old, mu_old = split(u_old)\n", 190 | "\n", 191 | "# Define test functions\n", 192 | "tf = TestFunction(MFS)\n", 193 | "q, v = split(tf)" 194 | ] 195 | }, 196 | { 197 | "cell_type": "markdown", 198 | "metadata": {}, 199 | "source": [ 200 | "To prescribe intitial conditions with the concentration values that are chosen randomly from the interval $(0.62,0.64)$ we implement the `UserExpression` class. In the constructor `(__init__)`, the random number generator is seeded. If the program is run in parallel, the random number generator is seeded using the rank (process number) to ensure a different sequence of numbers on each process. The function `eval` returns the values for a function at a given point $\\bf x$. The first component of the function is a randomized value of the concentration $c$ and the second value is the intial value of the chemical potential.\n", 201 | "The function `value_shape` declares that the `UseExpression` is a vector with a dimension two. " 202 | ] 203 | }, 204 | { 205 | "cell_type": "code", 206 | "execution_count": 5, 207 | "metadata": {}, 208 | "outputs": [], 209 | "source": [ 210 | "# Class representing the intial conditions\n", 211 | "class InitialConditions(UserExpression):\n", 212 | " def __init__(self, **kwargs):\n", 213 | " random.seed(2 + MPI.rank(MPI.comm_world))\n", 214 | " super().__init__(**kwargs)\n", 215 | " def eval(self, values, x):\n", 216 | " values[0] = 0.63 + 0.02*(0.5 - random.random()) # concentration\n", 217 | " values[1] = 0.0 # chemical potential\n", 218 | " def value_shape(self):\n", 219 | " return (2,)\n", 220 | " \n", 221 | "# Create intial conditions and interpolate\n", 222 | "u_init = InitialConditions(degree=1)\n", 223 | "u_new.interpolate(u_init)\n", 224 | "u_old.interpolate(u_init) " 225 | ] 226 | }, 227 | { 228 | "cell_type": "markdown", 229 | "metadata": {}, 230 | "source": [ 231 | "To calculate the contribution $f'(c)$ for the chemical potential we use automated differentiation. The first line declares that $c$ is a variable that some function can be differentiated with respect to. The next line is the function $f=100 c^2 (1-c)^2$ defined in the problem statement, and the third line performs the differentiation of $f$ with respect to the variable $c$." 232 | ] 233 | }, 234 | { 235 | "cell_type": "code", 236 | "execution_count": 6, 237 | "metadata": {}, 238 | "outputs": [], 239 | "source": [ 240 | "# Compute the chemical potential df/dc\n", 241 | "c_new = variable(c_new)\n", 242 | "f = 100*c_new**2*(1-c_new)**2\n", 243 | "dfdc = diff(f, c_new)" 244 | ] 245 | }, 246 | { 247 | "cell_type": "markdown", 248 | "metadata": {}, 249 | "source": [ 250 | "Next we implement the weak form of coupled PDEs\n", 251 | " $$\\begin{aligned}\\int_\\Omega d{\\bf x} \\\n", 252 | " \\left[\\frac{c_{n+1}-c_{n}}{\\Delta t} q + M \\nabla \\mu_{n+\\theta} \\cdot \\nabla q\\right] & = 0,\\\\\n", 253 | " \\int_\\Omega d{\\bf x} \\ \\left[\\mu_{n+1} v - f'(c_{n+1})v - \\lambda \\nabla c_{n+1} \\cdot \\nabla v\\right] & = 0.\\\\\n", 254 | " \\end{aligned}$$" 255 | ] 256 | }, 257 | { 258 | "cell_type": "code", 259 | "execution_count": 7, 260 | "metadata": {}, 261 | "outputs": [], 262 | "source": [ 263 | "lmbda = 1.0e-02 # surface parameter\n", 264 | "dt = 5.0e-06 # time step\n", 265 | "theta = 0.5 # time stepping family, e.g. theta=1 -> backward Euler, theta=0.5 -> Crank-Nicolson\n", 266 | "\n", 267 | "# mu_(n+theta)\n", 268 | "mu_mid = (1.0-theta)*mu_old + theta*mu_new\n", 269 | "\n", 270 | "# Weak statement of the equations\n", 271 | "Res_0 = (c_new - c_old)/dt*q*dx + dot(grad(mu_mid), grad(q))*dx\n", 272 | "Res_1 = mu_new*v*dx - dfdc*v*dx - lmbda*dot(grad(c_new), grad(v))*dx\n", 273 | "Res = Res_0 + Res_1" 274 | ] 275 | }, 276 | { 277 | "cell_type": "markdown", 278 | "metadata": {}, 279 | "source": [ 280 | "Prepare files, where we will store concentration and chemical potential fields for the visualization in ParaView." 281 | ] 282 | }, 283 | { 284 | "cell_type": "code", 285 | "execution_count": 8, 286 | "metadata": {}, 287 | "outputs": [], 288 | "source": [ 289 | "# Output files for concentration and chemical potential\n", 290 | "fileC = File(\"data/concentration.pvd\", \"compressed\")\n", 291 | "fileM = File(\"data/chem_potential.pvd\", \"compressed\")" 292 | ] 293 | }, 294 | { 295 | "cell_type": "markdown", 296 | "metadata": {}, 297 | "source": [ 298 | "Iteratively solve the coupled PDEs for 50 time steps. At each step we first copy the values of fields from the ${\\bf u}_{new}$ function to the ${\\bf u}_{old}$ function. Then we solve the coupled PDEs and store the new values of fields in the function ${\\bf u}_{new}$. Finally, we save the values of fields for visualization in ParaView " 299 | ] 300 | }, 301 | { 302 | "cell_type": "code", 303 | "execution_count": 9, 304 | "metadata": {}, 305 | "outputs": [ 306 | { 307 | "name": "stdout", 308 | "output_type": "stream", 309 | "text": [ 310 | "5e-06\n", 311 | "1e-05\n", 312 | "1.5000000000000002e-05\n", 313 | "2e-05\n", 314 | "2.5e-05\n", 315 | "3e-05\n", 316 | "3.5000000000000004e-05\n", 317 | "4e-05\n", 318 | "4.5e-05\n", 319 | "5e-05\n", 320 | "5.5e-05\n", 321 | "6e-05\n", 322 | "6.500000000000001e-05\n", 323 | "7.000000000000001e-05\n", 324 | "7.500000000000001e-05\n", 325 | "8e-05\n", 326 | "8.5e-05\n", 327 | "9e-05\n", 328 | "9.5e-05\n", 329 | "0.0001\n", 330 | "0.000105\n", 331 | "0.00011\n", 332 | "0.000115\n", 333 | "0.00012\n", 334 | "0.000125\n", 335 | "0.00013000000000000002\n", 336 | "0.00013500000000000003\n", 337 | "0.00014000000000000004\n", 338 | "0.00014500000000000006\n", 339 | "0.00015000000000000007\n", 340 | "0.00015500000000000008\n", 341 | "0.0001600000000000001\n", 342 | "0.0001650000000000001\n", 343 | "0.00017000000000000012\n", 344 | "0.00017500000000000013\n", 345 | "0.00018000000000000015\n", 346 | "0.00018500000000000016\n", 347 | "0.00019000000000000017\n", 348 | "0.00019500000000000019\n", 349 | "0.0002000000000000002\n", 350 | "0.0002050000000000002\n", 351 | "0.00021000000000000023\n", 352 | "0.00021500000000000024\n", 353 | "0.00022000000000000025\n", 354 | "0.00022500000000000026\n", 355 | "0.00023000000000000028\n", 356 | "0.0002350000000000003\n", 357 | "0.0002400000000000003\n", 358 | "0.0002450000000000003\n", 359 | "0.00025000000000000033\n" 360 | ] 361 | } 362 | ], 363 | "source": [ 364 | "# Step in time\n", 365 | "t = 0.0\n", 366 | "T = 50*dt\n", 367 | "while (t < T):\n", 368 | " t += dt\n", 369 | " print(t)\n", 370 | " u_old.vector()[:] = u_new.vector()\n", 371 | " solve(Res == 0, u_new)\n", 372 | " fileC << (u_new.split()[0], t)\n", 373 | " fileM << (u_new.split()[1], t)" 374 | ] 375 | }, 376 | { 377 | "cell_type": "markdown", 378 | "metadata": {}, 379 | "source": [ 380 | "## Complete code" 381 | ] 382 | }, 383 | { 384 | "cell_type": "code", 385 | "execution_count": 10, 386 | "metadata": {}, 387 | "outputs": [ 388 | { 389 | "name": "stdout", 390 | "output_type": "stream", 391 | "text": [ 392 | "5e-06\n", 393 | "1e-05\n", 394 | "1.5000000000000002e-05\n", 395 | "2e-05\n", 396 | "2.5e-05\n", 397 | "3e-05\n", 398 | "3.5000000000000004e-05\n", 399 | "4e-05\n", 400 | "4.5e-05\n", 401 | "5e-05\n", 402 | "5.5e-05\n", 403 | "6e-05\n", 404 | "6.500000000000001e-05\n", 405 | "7.000000000000001e-05\n", 406 | "7.500000000000001e-05\n", 407 | "8e-05\n", 408 | "8.5e-05\n", 409 | "9e-05\n", 410 | "9.5e-05\n", 411 | "0.0001\n", 412 | "0.000105\n", 413 | "0.00011\n", 414 | "0.000115\n", 415 | "0.00012\n", 416 | "0.000125\n", 417 | "0.00013000000000000002\n", 418 | "0.00013500000000000003\n", 419 | "0.00014000000000000004\n", 420 | "0.00014500000000000006\n", 421 | "0.00015000000000000007\n", 422 | "0.00015500000000000008\n", 423 | "0.0001600000000000001\n", 424 | "0.0001650000000000001\n", 425 | "0.00017000000000000012\n", 426 | "0.00017500000000000013\n", 427 | "0.00018000000000000015\n", 428 | "0.00018500000000000016\n", 429 | "0.00019000000000000017\n", 430 | "0.00019500000000000019\n", 431 | "0.0002000000000000002\n", 432 | "0.0002050000000000002\n", 433 | "0.00021000000000000023\n", 434 | "0.00021500000000000024\n", 435 | "0.00022000000000000025\n", 436 | "0.00022500000000000026\n", 437 | "0.00023000000000000028\n", 438 | "0.0002350000000000003\n", 439 | "0.0002400000000000003\n", 440 | "0.0002450000000000003\n", 441 | "0.00025000000000000033\n" 442 | ] 443 | } 444 | ], 445 | "source": [ 446 | "from __future__ import print_function\n", 447 | "import random\n", 448 | "from fenics import *\n", 449 | "from dolfin import *\n", 450 | "from mshr import *\n", 451 | "\n", 452 | " \n", 453 | "# Sub domain for Periodic boundary condition\n", 454 | "class PeriodicBoundary(SubDomain):\n", 455 | "\n", 456 | " def inside(self, x, on_boundary):\n", 457 | " # return True if on left or bottom boundary AND NOT\n", 458 | " # on one of the two corners (0, 1) and (1, 0)\n", 459 | " return bool((near(x[0], 0) or near(x[1], 0)) and\n", 460 | " (not ((near(x[0], 0) and near(x[1], 1)) or\n", 461 | " (near(x[0], 1) and near(x[1], 0)))) and on_boundary)\n", 462 | "\n", 463 | " def map(self, x, y):\n", 464 | " if near(x[0], 1) and near(x[1], 1):\n", 465 | " y[0] = x[0] - 1.\n", 466 | " y[1] = x[1] - 1.\n", 467 | " elif near(x[0], 1):\n", 468 | " y[0] = x[0] - 1.\n", 469 | " y[1] = x[1]\n", 470 | " else: # near(x[1], 1)\n", 471 | " y[0] = x[0]\n", 472 | " y[1] = x[1] - 1.\n", 473 | " \n", 474 | "\n", 475 | "\n", 476 | "# Create mesh and define function space with periodic boundary conditions\n", 477 | "mesh = UnitSquareMesh.create(96, 96, CellType.Type.quadrilateral)\n", 478 | "P = FiniteElement('Lagrange', mesh.ufl_cell(), 1)\n", 479 | "MFS = FunctionSpace(mesh, MixedElement([P,P]),constrained_domain=PeriodicBoundary())\n", 480 | "\n", 481 | "# Define functions\n", 482 | "u_new = Function(MFS) # solution for the next step\n", 483 | "u_old = Function(MFS) # solution from previous step\n", 484 | "u_new.rename(\"fields\",\"\")\n", 485 | "# Split mixed functions\n", 486 | "c_new, mu_new = split(u_new)\n", 487 | "c_old, mu_old = split(u_old)\n", 488 | "\n", 489 | "# Define test functions\n", 490 | "tf = TestFunction(MFS)\n", 491 | "q, v = split(tf)\n", 492 | "\n", 493 | "# Define test functions\n", 494 | "tf = TestFunction(MFS)\n", 495 | "q, v = split(tf)\n", 496 | "\n", 497 | "\n", 498 | "# Class representing the intial conditions\n", 499 | "class InitialConditions(UserExpression):\n", 500 | " def __init__(self, **kwargs):\n", 501 | " random.seed(2 + MPI.rank(MPI.comm_world))\n", 502 | " super().__init__(**kwargs)\n", 503 | " def eval(self, values, x):\n", 504 | " values[0] = 0.63 + 0.02*(0.5 - random.random()) # concentration\n", 505 | " values[1] = 0.0 # chemical potential\n", 506 | " def value_shape(self):\n", 507 | " return (2,)\n", 508 | " \n", 509 | "\n", 510 | "# Create intial conditions and interpolate\n", 511 | "u_init = InitialConditions(degree=1)\n", 512 | "u_new.interpolate(u_init)\n", 513 | "u_old.interpolate(u_init)\n", 514 | "\n", 515 | "\n", 516 | "\n", 517 | "# Compute the chemical potential df/dc\n", 518 | "c_new = variable(c_new)\n", 519 | "f = 100*c_new**2*(1-c_new)**2\n", 520 | "dfdc = diff(f, c_new)\n", 521 | "\n", 522 | "\n", 523 | "lmbda = 1.0e-02 # surface parameter\n", 524 | "dt = 5.0e-06 # time step\n", 525 | "theta = 0.5 # time stepping family, e.g. theta=1 -> backward Euler, theta=0.5 -> Crank-Nicolson\n", 526 | "\n", 527 | "# mu_(n+theta)\n", 528 | "mu_mid = (1.0-theta)*mu_old + theta*mu_new\n", 529 | "\n", 530 | "# Weak statement of the equations\n", 531 | "Res_0 = (c_new - c_old)/dt*q*dx + dot(grad(mu_mid), grad(q))*dx\n", 532 | "Res_1 = mu_new*v*dx - dfdc*v*dx - lmbda*dot(grad(c_new), grad(v))*dx\n", 533 | "Res = Res_0 + Res_1\n", 534 | "\n", 535 | "\n", 536 | "# Output files for concentration and chemical potential\n", 537 | "fileC = File(\"data/concentration.pvd\", \"compressed\")\n", 538 | "fileM = File(\"data/chem_potential.pvd\", \"compressed\")\n", 539 | "\n", 540 | "\n", 541 | "# Step in time\n", 542 | "t = 0.0\n", 543 | "T = 50*dt\n", 544 | "while (t < T):\n", 545 | " t += dt\n", 546 | " print(t)\n", 547 | " u_old.vector()[:] = u_new.vector()\n", 548 | " solve(Res == 0, u_new)\n", 549 | " fileC << (u_new.split()[0], t)\n", 550 | " fileM << (u_new.split()[1], t)\n" 551 | ] 552 | }, 553 | { 554 | "cell_type": "code", 555 | "execution_count": null, 556 | "metadata": {}, 557 | "outputs": [], 558 | "source": [] 559 | } 560 | ], 561 | "metadata": { 562 | "kernelspec": { 563 | "display_name": "Python 3", 564 | "language": "python", 565 | "name": "python3" 566 | }, 567 | "language_info": { 568 | "codemirror_mode": { 569 | "name": "ipython", 570 | "version": 3 571 | }, 572 | "file_extension": ".py", 573 | "mimetype": "text/x-python", 574 | "name": "python", 575 | "nbconvert_exporter": "python", 576 | "pygments_lexer": "ipython3", 577 | "version": "3.8.2" 578 | } 579 | }, 580 | "nbformat": 4, 581 | "nbformat_minor": 4 582 | } 583 | -------------------------------------------------------------------------------- /CahnHilliard/CahnHilliard.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import random 3 | from fenics import * 4 | from dolfin import * 5 | from mshr import * 6 | 7 | 8 | # Sub domain for Periodic boundary condition 9 | class PeriodicBoundary(SubDomain): 10 | 11 | def inside(self, x, on_boundary): 12 | # return True if on left or bottom boundary AND NOT 13 | # on one of the two corners (0, 1) and (1, 0) 14 | return bool((near(x[0], 0) or near(x[1], 0)) and 15 | (not ((near(x[0], 0) and near(x[1], 1)) or 16 | (near(x[0], 1) and near(x[1], 0)))) and on_boundary) 17 | 18 | def map(self, x, y): 19 | if near(x[0], 1) and near(x[1], 1): 20 | y[0] = x[0] - 1. 21 | y[1] = x[1] - 1. 22 | elif near(x[0], 1): 23 | y[0] = x[0] - 1. 24 | y[1] = x[1] 25 | else: # near(x[1], 1) 26 | y[0] = x[0] 27 | y[1] = x[1] - 1. 28 | 29 | 30 | 31 | # Create mesh and define function space with periodic boundary conditions 32 | mesh = UnitSquareMesh.create(96, 96, CellType.Type.quadrilateral) 33 | P = FiniteElement('Lagrange', mesh.ufl_cell(), 1) 34 | MFS = FunctionSpace(mesh, MixedElement([P,P]),constrained_domain=PeriodicBoundary()) 35 | 36 | # Define functions 37 | u_new = Function(MFS) # solution for the next step 38 | u_old = Function(MFS) # solution from previous step 39 | u_new.rename("fields","") 40 | # Split mixed functions 41 | c_new, mu_new = split(u_new) 42 | c_old, mu_old = split(u_old) 43 | 44 | # Define test functions 45 | tf = TestFunction(MFS) 46 | q, v = split(tf) 47 | 48 | # Define test functions 49 | tf = TestFunction(MFS) 50 | q, v = split(tf) 51 | 52 | 53 | # Class representing the intial conditions 54 | class InitialConditions(UserExpression): 55 | def __init__(self, **kwargs): 56 | random.seed(2 + MPI.rank(MPI.comm_world)) 57 | super().__init__(**kwargs) 58 | def eval(self, values, x): 59 | values[0] = 0.63 + 0.02*(0.5 - random.random()) # concentration 60 | values[1] = 0.0 # chemical potential 61 | def value_shape(self): 62 | return (2,) 63 | 64 | 65 | # Create intial conditions and interpolate 66 | u_init = InitialConditions(degree=1) 67 | u_new.interpolate(u_init) 68 | u_old.interpolate(u_init) 69 | 70 | 71 | 72 | # Compute the chemical potential df/dc 73 | c_new = variable(c_new) 74 | f = 100*c_new**2*(1-c_new)**2 75 | dfdc = diff(f, c_new) 76 | 77 | 78 | lmbda = 1.0e-02 # surface parameter 79 | dt = 5.0e-06 # time step 80 | theta = 0.5 # time stepping family, e.g. theta=1 -> backward Euler, theta=0.5 -> Crank-Nicolson 81 | 82 | # mu_(n+theta) 83 | mu_mid = (1.0-theta)*mu_old + theta*mu_new 84 | 85 | # Weak statement of the equations 86 | Res_0 = (c_new - c_old)/dt*q*dx + dot(grad(mu_mid), grad(q))*dx 87 | Res_1 = mu_new*v*dx - dfdc*v*dx - lmbda*dot(grad(c_new), grad(v))*dx 88 | Res = Res_0 + Res_1 89 | 90 | 91 | # Output files for concentration and chemical potential 92 | fileC = File("data/concentration.pvd", "compressed") 93 | fileM = File("data/chem_potential.pvd", "compressed") 94 | 95 | 96 | # Step in time 97 | t = 0.0 98 | T = 50*dt 99 | while (t < T): 100 | t += dt 101 | print(t) 102 | u_old.vector()[:] = u_new.vector() 103 | solve(Res == 0, u_new) 104 | fileC << (u_new.split()[0], t) 105 | fileM << (u_new.split()[1], t) 106 | -------------------------------------------------------------------------------- /CahnHilliard/figs/.ipynb_checkpoints/CahnHilliard_domain-checkpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akosmrlj/FEniCS_tutorial/0708db6a601a8e36f260819e448d59c175263bbd/CahnHilliard/figs/.ipynb_checkpoints/CahnHilliard_domain-checkpoint.png -------------------------------------------------------------------------------- /CahnHilliard/figs/CahnHilliard_domain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akosmrlj/FEniCS_tutorial/0708db6a601a8e36f260819e448d59c175263bbd/CahnHilliard/figs/CahnHilliard_domain.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Andrej Košmrlj 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tutorial on using FEniCS for solving PDEs 2 | 3 | This is the GitHub repository for a tutorial on using the [FEniCS](https://fenicsproject.org/) package to solve PDEs. This tutorial was prepared for the [KITP workshop Symmetry, Thermodynamics and Topology in Active Matter](https://www.kitp.ucsb.edu/activities/active20) and the recorded video is available [here](http://online.kitp.ucsb.edu/online/active20/tutorial4/). 4 | 5 | ## Layout 6 | 7 | This tutorial is organized as follows: 8 | 9 | - poisson - several examples of solving the Poisson's equation in 2d 10 | - elasticity - two examples of solving 2d linear elasticity problems 11 | - thermoelasticity - one examples of solving 2d thermoelasticity problem 12 | - CahnHilliard - solving the Cahn-Hilliard equation in 2d 13 | 14 | ## Getting started 15 | 16 | Cloning the repository 17 | 18 | Open a terminal and clone the repository with 19 | 20 | `git clone https://github.com/akosmrlj/FEniCS_tutorial.git` 21 | 22 | This command will create a local copy of the repository on your computer. It will be stored in a directory called *FEniCS_tutorial*. 23 | 24 | ## Creating conda environment 25 | 26 | We recommend using [Anaconda](https://anaconda.org/) and creating a *fenicsproject* environment. This can be done by typing in the terminal: 27 | 28 | `conda create -n fenicsproject -c conda-forge fenics mshr matplotlib jupyterlab` 29 | 30 | This should install all packages needed to run this tutorial. 31 | 32 | ## Running examples 33 | 34 | Each example comes with three files: 35 | 36 | - the standalone python code 37 | - Jupyter notebook with explanations 38 | - HTML copy of the Jupyter notebook 39 | 40 | To run examples you first need to activate the *fenicsproject* environment: 41 | 42 | `conda activate fenicsproject` 43 | 44 | Afterward, you can either run the python code directly from the terminal, e.g. 45 | 46 | `python poisson_basic.py` 47 | 48 | or you can start the Jupyter lab as 49 | 50 | `jupyter lab` 51 | 52 | where you can open the Jupyer notebooks containing the code and explanations. 53 | 54 | ## Additional software 55 | 56 | We recommend installing [Paraview](https://www.paraview.org/) for visualization. 57 | 58 | Note: It is also possible to install Paraview via the conda-forge channel. However, this has not been tested and one may encounter a number of package dependency issues if trying to install Paraview in the fenicsproject conda environment provided above. 59 | 60 | ## Additional resources 61 | 62 | There is a very detailed [FEniCS tutorial eBook](https://fenicsproject.org/tutorial/). Please note that this eBook was written in 2017 and some of the functions have slightly changed. Here are a few demos with explanations that are up to date: 63 | https://fenicsproject.org/docs/dolfin/latest/python/demos.html 64 | 65 | ## Lecturer 66 | 67 | Andrej Košmrlj, Princeton University 68 | [website](http://www.princeton.edu/~akosmrlj/) 69 | 70 | -------------------------------------------------------------------------------- /elasticity/elasticity_clamped.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from fenics import * 3 | from mshr import * 4 | import matplotlib.pyplot as plt 5 | 6 | 7 | # Create rectangular mesh with circular hole 8 | N = 50 9 | L = 1 10 | R = 0.2 11 | domain = Rectangle(Point(-L/2,-L/2),Point(L/2,L/2)) - Circle(Point(0.,0.), R) 12 | mesh = generate_mesh(domain, N) 13 | d = mesh.topology().dim() # dimensionality of the problem 14 | print("d = ",d) 15 | plot(mesh,linewidth=0.3) 16 | plt.show() 17 | 18 | # elastic constants 19 | E = 1 20 | nu = 0.4 21 | mu = E/2/(1+nu) 22 | Lambda = E*nu/(1-nu*nu) 23 | 24 | # displacement of the clamped ends 25 | Delta = 0.2*L 26 | 27 | 28 | #define vector function space, function u, and test function v 29 | degreeElements = 1 30 | VFS = VectorFunctionSpace(mesh, 'Lagrange', degreeElements) 31 | u = Function(VFS) 32 | v = TestFunction(VFS) 33 | 34 | 35 | #impose clamped boundary conditions 36 | def left_boundary(x, on_boundary): 37 | return on_boundary and near(x[0],-L/2); 38 | def right_boundary(x, on_boundary): 39 | return on_boundary and near(x[0],L/2); 40 | 41 | bc_left_X = DirichletBC(VFS.sub(0), Constant(-Delta/2), left_boundary) 42 | bc_right_X = DirichletBC(VFS.sub(0), Constant(+Delta/2), right_boundary) 43 | bc_left_Y = DirichletBC(VFS.sub(1), Constant(0.), left_boundary) 44 | bc_right_Y = DirichletBC(VFS.sub(1), Constant(0.), right_boundary) 45 | 46 | bc = [bc_left_X, bc_right_X, bc_left_Y, bc_right_Y] 47 | 48 | 49 | # define strain and stress 50 | def epsilon(u): 51 | return sym(grad(u)) 52 | def sigma(u): 53 | return 2*mu*epsilon(u) + Lambda*tr(epsilon(u))*Identity(d) 54 | 55 | # elastic energy 56 | Energy = 1/2*inner(sigma(u),epsilon(u))*dx 57 | 58 | # minimize elastic energy 59 | Res = derivative(Energy, u, v) 60 | solve(Res == 0, u, bc) 61 | 62 | # calculate elastic energy 63 | print("Energy = ", assemble(Energy)) 64 | 65 | # export displacements 66 | u.rename("displacements","") 67 | fileD = File("data/clamped_displacement.pvd"); 68 | fileD << u; 69 | 70 | # calculate and export von Mises stress 71 | FS = FunctionSpace(mesh, 'Lagrange', 1) 72 | devStress = sigma(u) - (1./d)*tr(sigma(u))*Identity(d) # deviatoric stress 73 | von_Mises = project(sqrt(3/2*inner(devStress, devStress)), FS) 74 | von_Mises.rename("von Mises","") 75 | fileS = File("data/clamped_vonMises_stress.pvd"); 76 | fileS << von_Mises; 77 | 78 | # calculate and export stress component sigma_xx 79 | sigma_xx = project(sigma(u)[0,0], FS) 80 | sigma_xx.rename("sigma_xx","") 81 | fileS = File("data/clamped_sigma_xx.pvd"); 82 | fileS << sigma_xx; 83 | 84 | # calculate and export stress component sigma_yy 85 | sigma_yy = project(sigma(u)[1,1], FS) 86 | sigma_yy.rename("sigma_yy","") 87 | fileS = File("data/clamped_sigma_yy.pvd"); 88 | fileS << sigma_yy; 89 | 90 | # calculate and export stress component sigma_xy 91 | sigma_xy = project(sigma(u)[0,1], FS) 92 | sigma_xy.rename("sigma_xy","") 93 | fileS = File("data/clamped_sigma_xy.pvd"); 94 | fileS << sigma_xy; 95 | -------------------------------------------------------------------------------- /elasticity/elasticity_tractions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Linear deformation of elastic material with two circular inclusions with prescribed tractions\n", 8 | "\n", 9 | "This example is implemented in the Python file elasticity_tractions. and it illustrates how to:\n", 10 | "\n", 11 | "- Use subdamins;\n", 12 | "- Use Lagrange multipliers;\n", 13 | "- Use the `UserExpression` class;\n", 14 | "- Extract normal vector to the physical boundary;" 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "## Equation and problem definition" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": {}, 27 | "source": [ 28 | "We consider linear deformation of a 2D elastic material ($\\Omega_1$) with two circular inclusions ($\\Omega_2$, $\\Omega_3$) by prescribing tractions on the left ($\\Gamma_1$) and right ($\\Gamma_2$) boundaries, while the other other boundaries are traction-free ($\\Gamma_3$, $\\Gamma_4$).\n", 29 | "
\n", 30 | " \n", 31 | "
" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": {}, 37 | "source": [ 38 | "This problem can be solved via minimization of the free energy\n", 39 | "$$F[u_i]=E_{el}[u_i] - W[u_i]=\\int_\\Omega d{\\bf x} \\ \\frac{1}{2} \\sigma_{ij} \\epsilon_{ij} - \\oint_\\Gamma ds\\, u_i t_i,$$\n", 40 | "with respect to displacements $u_i$. The first term describes the stored elastic energy and the second term describes the work of external tractions ($t_i = \\sigma_{ij}^0 n_j$ with $n_j$ being the unit normal vector to the boundary). The constitutive equations for all materials are \n", 41 | "$$\\sigma_{ij} = 2 \\mu \\epsilon_{ij} + \\lambda \\epsilon_{kk} \\delta_{ij},$$\n", 42 | "$$\\epsilon_{ij} = \\frac{1}{2} \\left(\\partial_i u_j + \\partial_j u_i\\right),$$\n", 43 | "\n", 44 | "where $\\lambda$ and $\\mu$ are 2D Lamé constants that can be expressed in terms of the 2D Young's modulus $E$ and the Poisson's ratio $\\nu$ as $\\mu=E/[2(1+\\nu)]$ and $\\lambda = E \\nu/(1-\\nu^2)$. In this example we use values: $E_1=1$, $E_1=1$, $E_2=10$, $E_3=0.1$, $\\nu_1=0.3$, $\\nu_2=0.2$, and $\\nu_1=0.1$. " 45 | ] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "metadata": {}, 50 | "source": [ 51 | "Note that the minimization of the total free energy automatically satisfies the boundary conditions at different materials, which are the continuity of displacements and tractions. In order to prevent rigid body motions (2 translations, 1 rotation), which do not cost any energy, we use Lagrange multipliers ${\\bf c}_{trans}$ and $c_{rot}$ and minimize the total free energy \n", 52 | "$$F[u_i]=\\int_\\Omega d{\\bf x} \\, \\frac{1}{2} \\sigma_{ij} \\epsilon_{ij} - \\oint_\\Gamma ds\\, u_i t_i +\\int_\\Omega d{\\bf x}\\, c_{trans,i} u_i + \\int_\\Omega d{\\bf x}\\ c_{rot} (x u_y -y u_x)$$" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "## Implementation" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "As usual, in order to use solve this problem we need to import all necessary modules." 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 1, 72 | "metadata": {}, 73 | "outputs": [], 74 | "source": [ 75 | "from __future__ import print_function\n", 76 | "from fenics import *\n", 77 | "from dolfin import *\n", 78 | "from mshr import *\n", 79 | "import matplotlib.pyplot as plt" 80 | ] 81 | }, 82 | { 83 | "cell_type": "markdown", 84 | "metadata": {}, 85 | "source": [ 86 | "We create a rectangular box with two circular subdomains. For each subdomain we can prescribe the identifier marker numbers via the `set_subdomain` function. Here we first specified the marker value 1 for the entire rectangular domain and then we specified marker values 2 and 3 for the two circular domains. Note that if subdomains overlap, then the last specified marker is used. Thus the marker value 1 only describes the rectangular region without the two circular disks. Note also that the marker values of 0 cannot be used, because they are reserved to specify the whole domain. To access the values of markers for each cell in the mesh, we use the `MeshFunction`, where `d` is the dimensionality of the cell. " 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": 2, 92 | "metadata": {}, 93 | "outputs": [ 94 | { 95 | "name": "stdout", 96 | "output_type": "stream", 97 | "text": [ 98 | "d = 2\n" 99 | ] 100 | } 101 | ], 102 | "source": [ 103 | "# Create rectangular mesh with two circular inclusions\n", 104 | "N = 100\n", 105 | "L = 1\n", 106 | "R_2 = 0.05\n", 107 | "R_3 = 0.08\n", 108 | "domain = Rectangle(Point(-L/2,-L/2),Point(L/2,L/2))\n", 109 | "# mark subdomains with markers 1, 2, 3\n", 110 | "domain.set_subdomain(1, Rectangle(Point(-L/2,-L/2),Point(L/2,L/2)))\n", 111 | "domain.set_subdomain(2, Circle(Point(0.,0.43), R_2))\n", 112 | "domain.set_subdomain(3, Circle(Point(-0.15,0.35), R_3))\n", 113 | "mesh = generate_mesh(domain, N)\n", 114 | "d = mesh.topology().dim() # dimensionality of the problem\n", 115 | "print(\"d = \",d)\n", 116 | "markers = MeshFunction(\"size_t\", mesh, d , mesh.domains())" 117 | ] 118 | }, 119 | { 120 | "cell_type": "markdown", 121 | "metadata": {}, 122 | "source": [ 123 | "To define boundary subdomains we use the `SubDomain` class. Note that we use the `near` function to test whether the mesh points belong to the boundary. This is because using `x[0]==-L/2` to check if two floating point numbers are equal is unreliable due to rounding errors. To mark the values of markers for facets on each boundary subdomain, we again use the `MeshFunction` class, which now has dimensionality `d-1`. Note again that the marker values of 0 cannot be used, because they are reserved to specify the entire boundary domain." 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": 3, 129 | "metadata": {}, 130 | "outputs": [], 131 | "source": [ 132 | "# define boundary subdomains\n", 133 | "class Left(SubDomain):\n", 134 | " def inside(self, x, on_boundary):\n", 135 | " return near(x[0], -L/2)\n", 136 | "\n", 137 | "class Right(SubDomain):\n", 138 | " def inside(self, x, on_boundary):\n", 139 | " return near(x[0], +L/2)\n", 140 | "\n", 141 | "class Top(SubDomain):\n", 142 | " def inside(self, x, on_boundary):\n", 143 | " return near(x[1], L/2)\n", 144 | "\n", 145 | "class Bottom(SubDomain):\n", 146 | " def inside(self, x, on_boundary):\n", 147 | " return near(x[1], -L/2)\n", 148 | "\n", 149 | "left = Left()\n", 150 | "right = Right()\n", 151 | "top = Top()\n", 152 | "bottom = Bottom()\n", 153 | "\n", 154 | "# mark boundary subdomains with markers 1, 2, 3, 4\n", 155 | "boundaries = MeshFunction(\"size_t\", mesh, d-1, 0)\n", 156 | "boundaries.set_all(0)\n", 157 | "left.mark(boundaries, 1)\n", 158 | "right.mark(boundaries, 2)\n", 159 | "top.mark(boundaries, 3)\n", 160 | "bottom.mark(boundaries, 4)" 161 | ] 162 | }, 163 | { 164 | "cell_type": "markdown", 165 | "metadata": {}, 166 | "source": [ 167 | "The `UserExpression` class can be used to specify elastic constants on each of the three subdomains $\\Omega_1$, $\\Omega_2$, $\\Omega_3$ with the help of cell markers that were defined above." 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": 4, 173 | "metadata": {}, 174 | "outputs": [], 175 | "source": [ 176 | "# elastic constants of the matrix and two circular inclusions\n", 177 | "E_1 = 1\n", 178 | "E_2 = 10\n", 179 | "E_3 = 0.1\n", 180 | "nu_1 = 0.3\n", 181 | "nu_2 = 0.2\n", 182 | "nu_3 = 0.1\n", 183 | "\n", 184 | "# define class for calculating the Young's modulus over the whole domain\n", 185 | "class E_class(UserExpression):\n", 186 | " def __init__(self, **kwargs):\n", 187 | " self.markers = markers\n", 188 | " super().__init__(**kwargs)\n", 189 | " def eval_cell(self, value, x, ufc_cell):\n", 190 | " if markers[ufc_cell.index] == 1:\n", 191 | " value[0] = E_1\n", 192 | " elif markers[ufc_cell.index] == 2:\n", 193 | " value[0] = E_2\n", 194 | " else:\n", 195 | " value[0] = E_3\n", 196 | " def value_shape(self):\n", 197 | " return ()\n", 198 | "\n", 199 | "# define class for calculating the Poisson's ratio over the whole domain\n", 200 | "class nu_class(UserExpression):\n", 201 | " def __init__(self, **kwargs):\n", 202 | " self.markers = markers\n", 203 | " super().__init__(**kwargs)\n", 204 | " def eval_cell(self, value, x, ufc_cell):\n", 205 | " if markers[ufc_cell.index] == 1:\n", 206 | " value[0] = nu_1\n", 207 | " elif markers[ufc_cell.index] == 2:\n", 208 | " value[0] = nu_2\n", 209 | " else:\n", 210 | " value[0] = nu_3\n", 211 | " def value_shape(self):\n", 212 | " return ()\n", 213 | "\n", 214 | "# functions of elastic constants on the whole domain\n", 215 | "E = E_class(degree=1)\n", 216 | "nu = nu_class(degree=1)\n", 217 | "mu = E/2/(1+nu)\n", 218 | "Lambda = E*nu/(1-nu*nu)" 219 | ] 220 | }, 221 | { 222 | "cell_type": "markdown", 223 | "metadata": {}, 224 | "source": [ 225 | "Use `MixedElement` to specify the function space for displacement vectors (linear Lagrange elements) and for 3 Lagrange multipliers (real numbers). " 226 | ] 227 | }, 228 | { 229 | "cell_type": "code", 230 | "execution_count": 5, 231 | "metadata": {}, 232 | "outputs": [], 233 | "source": [ 234 | "#define function space with mixed finite elements (displacements + 3 Lagrange multipliers)\n", 235 | "degreeElements = 1\n", 236 | "P1 = FiniteElement('Lagrange', mesh.ufl_cell(), degreeElements)\n", 237 | "R = FiniteElement('Real', mesh.ufl_cell(), 0)\n", 238 | "MFS = FunctionSpace(mesh, MixedElement([(P1*P1),(R*R),R]))" 239 | ] 240 | }, 241 | { 242 | "cell_type": "markdown", 243 | "metadata": {}, 244 | "source": [ 245 | "The function on this `FunctionSpace` can be defined as usual. However, it is convenient to `split` these function into functions that correspond to subspaces for displacements $u$ and Lagrange multipliers ${\\bf c}_{trans}$ and $c_{rot}$." 246 | ] 247 | }, 248 | { 249 | "cell_type": "code", 250 | "execution_count": 6, 251 | "metadata": {}, 252 | "outputs": [], 253 | "source": [ 254 | "#define function and split it into displacements u and Lagrange multipliers\n", 255 | "f = Function(MFS)\n", 256 | "u, c_trans, c_rot = split(f)\n", 257 | "#define test function\n", 258 | "tf = TestFunction(MFS)" 259 | ] 260 | }, 261 | { 262 | "cell_type": "markdown", 263 | "metadata": {}, 264 | "source": [ 265 | "The strain and stress tensor are defined as discussed in the elasticity example with clamped-free boundary conditions (Jupyter notebook, HTML)." 266 | ] 267 | }, 268 | { 269 | "cell_type": "code", 270 | "execution_count": 7, 271 | "metadata": {}, 272 | "outputs": [], 273 | "source": [ 274 | "# define strain and stress\n", 275 | "def epsilon(u):\n", 276 | " return sym(grad(u))\n", 277 | "def sigma(u):\n", 278 | " return 2*mu*epsilon(u) + Lambda*tr(epsilon(u))*Identity(d)" 279 | ] 280 | }, 281 | { 282 | "cell_type": "markdown", 283 | "metadata": {}, 284 | "source": [ 285 | "To prescribe tractions at the boundary $t_i=\\sigma_{ij}^0 n_j$, we use the `FacetNormal` function to obtain the unit normal vector $n_j$ to the boundary." 286 | ] 287 | }, 288 | { 289 | "cell_type": "code", 290 | "execution_count": 8, 291 | "metadata": {}, 292 | "outputs": [], 293 | "source": [ 294 | "#external load\n", 295 | "sigma_xx = 0.2*E_1\n", 296 | "sigma_xy = 0\n", 297 | "sigma_yy = 0\n", 298 | "sigma_0 = Constant(((sigma_xx,sigma_xy),(sigma_xy,sigma_yy)))\n", 299 | "\n", 300 | "#unit normal vector to the boundary\n", 301 | "n = FacetNormal(mesh)\n", 302 | " \n", 303 | "#tractions on boundaries\n", 304 | "t = sigma_0 * n" 305 | ] 306 | }, 307 | { 308 | "cell_type": "markdown", 309 | "metadata": {}, 310 | "source": [ 311 | "The toal free energy can be decomposed to the elastic energy\n", 312 | "$$E_{el}[u_i]=\\int_\\Omega d {\\bf x} \\, \\frac{1}{2} \\sigma_{ij} \\epsilon_{ij},$$\n", 313 | "the work of external forces\n", 314 | "$$W[u_i]=\\oint_\\Gamma ds\\, u_i t_i$$\n", 315 | "and constraints with Lagrange multipliers\n", 316 | "$$\\int_\\Omega d{\\bf x}\\, c_{trans,i} u_i + \\int_\\Omega d{\\bf x}\\ c_{rot} (x u_y -y u_x)$$" 317 | ] 318 | }, 319 | { 320 | "cell_type": "code", 321 | "execution_count": 9, 322 | "metadata": {}, 323 | "outputs": [], 324 | "source": [ 325 | "#calculate elastic energy\n", 326 | "elastic_energy = 1/2*inner(sigma(u),epsilon(u))*dx\n", 327 | "#calculate work of external tractions\n", 328 | "work = dot(t,u)*ds\n", 329 | "#Lagrange multipliers to prevent rigid body motions\n", 330 | "r=Expression(('x[0]','x[1]'),degree=1)\n", 331 | "constraints = dot(c_trans,u)*dx + c_rot*(r[0]*u[1]-r[1]*u[0])*dx\n", 332 | "#total free energy\n", 333 | "free_energy = elastic_energy - work + constraints" 334 | ] 335 | }, 336 | { 337 | "cell_type": "markdown", 338 | "metadata": {}, 339 | "source": [ 340 | "
\n", 341 | "Note that if the 3 materials had different constitutive laws we coud have evaluated the total elastic energy by integrating separately over domains $\\Omega_1$, $\\Omega_2$, and $\\Omega_3$.
\n", 342 | "
\n", 343 | "dx = Measure('dx', domain=mesh, subdomain_data=markers)\n", 344 | "elastic_energy = energy_density1 * dx(1) + energy_density2 * dx(2) + energy_density3 * dx(3) \n", 345 | "
\n", 346 | "Similarly, if we had different values of tractions on different boundary domains we could have evaluated the work by integrating over different domains.\n", 347 | "
\n", 348 | "
ds = Measure('ds', domain=mesh, subdomain_data=boundaries)\n", 349 | "work = dot(tractions1,u) * ds(1) + dot(tractions2,u) * ds(2) + dot(tractions3,u) * ds(3) + dot(tractions4,u) * ds(4)\n", 350 | "
\n", 351 | "
" 352 | ] 353 | }, 354 | { 355 | "cell_type": "markdown", 356 | "metadata": {}, 357 | "source": [ 358 | "Minimize the total free energy and calculate the contributions from the elastic energy, the work of external loads and from constraints." 359 | ] 360 | }, 361 | { 362 | "cell_type": "code", 363 | "execution_count": 10, 364 | "metadata": {}, 365 | "outputs": [ 366 | { 367 | "name": "stdout", 368 | "output_type": "stream", 369 | "text": [ 370 | "Tot Free Energy = -0.020811012183109497\n", 371 | "Elastic Energy = 0.02081101218310997\n", 372 | "Work = 0.0416220243662194\n", 373 | "Constraints = -2.1385721704735844e-31\n" 374 | ] 375 | } 376 | ], 377 | "source": [ 378 | "#minimize total free energy\n", 379 | "Res = derivative(free_energy, f, tf)\n", 380 | "solve(Res == 0, f)\n", 381 | "\n", 382 | "#calculate total free energy\n", 383 | "print(\"Tot Free Energy = \",assemble(free_energy))\n", 384 | "print(\"Elastic Energy = \",assemble(elastic_energy))\n", 385 | "print(\"Work = \",assemble(work))\n", 386 | "print(\"Constraints = \",assemble(constraints))" 387 | ] 388 | }, 389 | { 390 | "cell_type": "markdown", 391 | "metadata": {}, 392 | "source": [ 393 | "We can export displacements and stress for the visualization in ParaView as was done in the elasticity example with clamped-free boundary conditions (Jupyter notebook, HTML)." 394 | ] 395 | }, 396 | { 397 | "cell_type": "code", 398 | "execution_count": 11, 399 | "metadata": {}, 400 | "outputs": [], 401 | "source": [ 402 | "# export displacements\n", 403 | "VFS = VectorFunctionSpace(mesh, 'Lagrange', 1)\n", 404 | "disp=project(u, VFS)\n", 405 | "disp.rename(\"displacements\",\"\")\n", 406 | "fileD = File(\"data/tractions_displacement.pvd\");\n", 407 | "fileD << disp;\n", 408 | "\n", 409 | "# calculate and export von Mises stress\n", 410 | "FS = FunctionSpace(mesh, 'Lagrange', 1)\n", 411 | "devStress = sigma(u) - (1./d)*tr(sigma(u))*Identity(d) # deviatoric stress\n", 412 | "von_Mises = project(sqrt(3./2*inner(devStress, devStress)), FS)\n", 413 | "von_Mises.rename(\"von Mises\",\"\")\n", 414 | "fileS = File(\"data/tractions_vonMises_stress.pvd\");\n", 415 | "fileS << von_Mises;\n", 416 | "\n", 417 | "# calculate and export stress component sigma_xx\n", 418 | "sigma_xx = project(sigma(u)[0,0], FS)\n", 419 | "sigma_xx.rename(\"sigma_xx\",\"\")\n", 420 | "fileS = File(\"data/tractions_sigma_xx.pvd\");\n", 421 | "fileS << sigma_xx;\n", 422 | "\n", 423 | "# calculate and export stress component sigma_yy\n", 424 | "sigma_yy = project(sigma(u)[1,1], FS)\n", 425 | "sigma_yy.rename(\"sigma_yy\",\"\")\n", 426 | "fileS = File(\"data/tractions_sigma_yy.pvd\");\n", 427 | "fileS << sigma_yy;\n", 428 | "\n", 429 | "# calculate and export stress component sigma_xy\n", 430 | "sigma_xy = project(sigma(u)[0,1], FS)\n", 431 | "sigma_xy.rename(\"sigma_xy\",\"\")\n", 432 | "fileS = File(\"data/tractions_sigma_xy.pvd\");\n", 433 | "fileS << sigma_xy;\n", 434 | "\n", 435 | "# export Young's modulus\n", 436 | "young = project(E, FS)\n", 437 | "young.rename(\"Young's modulus\",\"\")\n", 438 | "fileS = File(\"data/tractions_young.pvd\");\n", 439 | "fileS << young;\n" 440 | ] 441 | }, 442 | { 443 | "cell_type": "markdown", 444 | "metadata": {}, 445 | "source": [ 446 | "## Complete code" 447 | ] 448 | }, 449 | { 450 | "cell_type": "code", 451 | "execution_count": 12, 452 | "metadata": {}, 453 | "outputs": [ 454 | { 455 | "name": "stdout", 456 | "output_type": "stream", 457 | "text": [ 458 | "Tot Free Energy = -0.020811012183109497\n", 459 | "Elastic Energy = 0.02081101218310997\n", 460 | "Work = 0.0416220243662194\n", 461 | "Constraints = -2.1385721704735844e-31\n" 462 | ] 463 | } 464 | ], 465 | "source": [ 466 | "from __future__ import print_function\n", 467 | "from fenics import *\n", 468 | "from dolfin import *\n", 469 | "from mshr import *\n", 470 | "import matplotlib.pyplot as plt\n", 471 | "\n", 472 | "# Create rectangular mesh with two circular inclusions\n", 473 | "N = 100\n", 474 | "L = 1\n", 475 | "R_2 = 0.05\n", 476 | "R_3 = 0.08\n", 477 | "domain = Rectangle(Point(-L/2,-L/2),Point(L/2,L/2))\n", 478 | "# mark subdomains with markers 1, 2, 3\n", 479 | "domain.set_subdomain(1, Rectangle(Point(-L/2,-L/2),Point(L/2,L/2)))\n", 480 | "domain.set_subdomain(2, Circle(Point(0.,0.43), R_2))\n", 481 | "domain.set_subdomain(3, Circle(Point(-0.15,0.35), R_3))\n", 482 | "mesh = generate_mesh(domain, N)\n", 483 | "d = mesh.topology().dim() # dimensionality of the problem\n", 484 | "markers = MeshFunction(\"size_t\", mesh, d , mesh.domains())\n", 485 | "\n", 486 | "\n", 487 | "# define boundary subdomains\n", 488 | "class Left(SubDomain):\n", 489 | " def inside(self, x, on_boundary):\n", 490 | " return near(x[0], -L/2)\n", 491 | "\n", 492 | "class Right(SubDomain):\n", 493 | " def inside(self, x, on_boundary):\n", 494 | " return near(x[0], +L/2)\n", 495 | "\n", 496 | "class Top(SubDomain):\n", 497 | " def inside(self, x, on_boundary):\n", 498 | " return near(x[1], L/2)\n", 499 | "\n", 500 | "class Bottom(SubDomain):\n", 501 | " def inside(self, x, on_boundary):\n", 502 | " return near(x[1], -L/2)\n", 503 | "\n", 504 | "left = Left()\n", 505 | "right = Right()\n", 506 | "top = Top()\n", 507 | "bottom = Bottom()\n", 508 | "\n", 509 | "# mark boundary subdomains with markers 1, 2, 3, 4\n", 510 | "boundaries = MeshFunction(\"size_t\", mesh, d-1, 0)\n", 511 | "boundaries.set_all(0)\n", 512 | "left.mark(boundaries, 1)\n", 513 | "right.mark(boundaries, 2)\n", 514 | "top.mark(boundaries, 3)\n", 515 | "bottom.mark(boundaries, 4)\n", 516 | "\n", 517 | "\n", 518 | "# elastic constants of the matrix and two circular inclusions\n", 519 | "E_1 = 1\n", 520 | "E_2 = 10\n", 521 | "E_3 = 0.1\n", 522 | "nu_1 = 0.3\n", 523 | "nu_2 = 0.2\n", 524 | "nu_3 = 0.1\n", 525 | "\n", 526 | "\n", 527 | "# define class for calculating the Young's modulus over the whole domain\n", 528 | "class E_class(UserExpression):\n", 529 | " def __init__(self, **kwargs):\n", 530 | " self.markers = markers\n", 531 | " super().__init__(**kwargs)\n", 532 | " def eval_cell(self, value, x, ufc_cell):\n", 533 | " if markers[ufc_cell.index] == 1:\n", 534 | " value[0] = E_1\n", 535 | " elif markers[ufc_cell.index] == 2:\n", 536 | " value[0] = E_2\n", 537 | " else:\n", 538 | " value[0] = E_3\n", 539 | " def value_shape(self):\n", 540 | " return ()\n", 541 | "\n", 542 | "# define class for calculating the Poisson's ratio over the whole domain\n", 543 | "class nu_class(UserExpression):\n", 544 | " def __init__(self, **kwargs):\n", 545 | " self.markers = markers\n", 546 | " super().__init__(**kwargs)\n", 547 | " def eval_cell(self, value, x, ufc_cell):\n", 548 | " if markers[ufc_cell.index] == 1:\n", 549 | " value[0] = nu_1\n", 550 | " elif markers[ufc_cell.index] == 2:\n", 551 | " value[0] = nu_2\n", 552 | " else:\n", 553 | " value[0] = nu_3\n", 554 | " def value_shape(self):\n", 555 | " return ()\n", 556 | "\n", 557 | "# functions of elastic constants on the whole domain\n", 558 | "E = E_class(degree=1)\n", 559 | "nu = nu_class(degree=1)\n", 560 | "mu = E/2/(1+nu)\n", 561 | "Lambda = E*nu/(1-nu*nu)\n", 562 | "\n", 563 | "\n", 564 | "#define function space with mixed finite elements (displacements + 3 Lagrange multipliers)\n", 565 | "degreeElements = 1\n", 566 | "P1 = FiniteElement('Lagrange', mesh.ufl_cell(), degreeElements)\n", 567 | "R = FiniteElement('Real', mesh.ufl_cell(), 0)\n", 568 | "MFS = FunctionSpace(mesh, MixedElement([(P1*P1),(R*R),R]))\n", 569 | "\n", 570 | "#define function and split it into displacements u and Lagrange multipliers\n", 571 | "f = Function(MFS)\n", 572 | "u, c_trans, c_rot = split(f)\n", 573 | "#define test function\n", 574 | "tf = TestFunction(MFS)\n", 575 | "\n", 576 | "\n", 577 | "# define strain and stress\n", 578 | "def epsilon(u):\n", 579 | " return sym(grad(u))\n", 580 | "def sigma(u):\n", 581 | " return 2*mu*epsilon(u) + Lambda*tr(epsilon(u))*Identity(d)\n", 582 | "\n", 583 | "#external load\n", 584 | "sigma_xx = 0.2*E_1\n", 585 | "sigma_xy = 0\n", 586 | "sigma_yy = 0\n", 587 | "sigma_0 = Constant(((sigma_xx,sigma_xy),(sigma_xy,sigma_yy)))\n", 588 | "\n", 589 | "#unit normal vector to the boundary\n", 590 | "n = FacetNormal(mesh)\n", 591 | " \n", 592 | "#tractions on boundaries\n", 593 | "t = sigma_0 * n\n", 594 | "\n", 595 | "#calculate elastic energy\n", 596 | "elastic_energy = 1/2*inner(sigma(u),epsilon(u))*dx\n", 597 | "#calculate work of external tractions\n", 598 | "work = dot(t,u)*ds\n", 599 | "#Lagrange multipliers to prevent rigid body motions\n", 600 | "r=Expression(('x[0]','x[1]'),degree=1)\n", 601 | "constraints = dot(c_trans,u)*dx + c_rot*(r[0]*u[1]-r[1]*u[0])*dx\n", 602 | "#total free energy\n", 603 | "free_energy = elastic_energy - work + constraints\n", 604 | "\n", 605 | "\n", 606 | "#minimize total free energy\n", 607 | "Res = derivative(free_energy, f, tf)\n", 608 | "solve(Res == 0, f)\n", 609 | "\n", 610 | "#calculate total free energy\n", 611 | "print(\"Tot Free Energy = \",assemble(free_energy))\n", 612 | "print(\"Elastic Energy = \",assemble(elastic_energy))\n", 613 | "print(\"Work = \",assemble(work))\n", 614 | "print(\"Constraints = \",assemble(constraints))\n", 615 | "\n", 616 | "\n", 617 | "# export displacements\n", 618 | "VFS = VectorFunctionSpace(mesh, 'Lagrange', 1)\n", 619 | "disp=project(u, VFS)\n", 620 | "disp.rename(\"displacements\",\"\")\n", 621 | "fileD = File(\"data/tractions_displacement.pvd\");\n", 622 | "fileD << disp;\n", 623 | "\n", 624 | "# calculate and export von Mises stress\n", 625 | "FS = FunctionSpace(mesh, 'Lagrange', 1)\n", 626 | "devStress = sigma(u) - (1./d)*tr(sigma(u))*Identity(d) # deviatoric stress\n", 627 | "von_Mises = project(sqrt(3./2*inner(devStress, devStress)), FS)\n", 628 | "von_Mises.rename(\"von Mises\",\"\")\n", 629 | "fileS = File(\"data/tractions_vonMises_stress.pvd\");\n", 630 | "fileS << von_Mises;\n", 631 | "\n", 632 | "# calculate and export stress component sigma_xx\n", 633 | "sigma_xx = project(sigma(u)[0,0], FS)\n", 634 | "sigma_xx.rename(\"sigma_xx\",\"\")\n", 635 | "fileS = File(\"data/tractions_sigma_xx.pvd\");\n", 636 | "fileS << sigma_xx;\n", 637 | "\n", 638 | "# calculate and export stress component sigma_yy\n", 639 | "sigma_yy = project(sigma(u)[1,1], FS)\n", 640 | "sigma_yy.rename(\"sigma_yy\",\"\")\n", 641 | "fileS = File(\"data/tractions_sigma_yy.pvd\");\n", 642 | "fileS << sigma_yy;\n", 643 | "\n", 644 | "# calculate and export stress component sigma_xy\n", 645 | "sigma_xy = project(sigma(u)[0,1], FS)\n", 646 | "sigma_xy.rename(\"sigma_xy\",\"\")\n", 647 | "fileS = File(\"data/tractions_sigma_xy.pvd\");\n", 648 | "fileS << sigma_xy;\n", 649 | "\n", 650 | "# export Young's modulus\n", 651 | "young = project(E, FS)\n", 652 | "young.rename(\"Young's modulus\",\"\")\n", 653 | "fileS = File(\"data/tractions_young.pvd\");\n", 654 | "fileS << young;" 655 | ] 656 | }, 657 | { 658 | "cell_type": "code", 659 | "execution_count": null, 660 | "metadata": {}, 661 | "outputs": [], 662 | "source": [] 663 | } 664 | ], 665 | "metadata": { 666 | "kernelspec": { 667 | "display_name": "Python 3", 668 | "language": "python", 669 | "name": "python3" 670 | }, 671 | "language_info": { 672 | "codemirror_mode": { 673 | "name": "ipython", 674 | "version": 3 675 | }, 676 | "file_extension": ".py", 677 | "mimetype": "text/x-python", 678 | "name": "python", 679 | "nbconvert_exporter": "python", 680 | "pygments_lexer": "ipython3", 681 | "version": "3.8.2" 682 | } 683 | }, 684 | "nbformat": 4, 685 | "nbformat_minor": 4 686 | } 687 | -------------------------------------------------------------------------------- /elasticity/elasticity_tractions.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from fenics import * 3 | from dolfin import * 4 | from mshr import * 5 | import matplotlib.pyplot as plt 6 | 7 | # Create rectangular mesh with two circular inclusions 8 | N = 100 9 | L = 1 10 | R_2 = 0.05 11 | R_3 = 0.08 12 | domain = Rectangle(Point(-L/2,-L/2),Point(L/2,L/2)) 13 | # mark subdomains with markers 1, 2, 3 14 | domain.set_subdomain(1, Rectangle(Point(-L/2,-L/2),Point(L/2,L/2))) 15 | domain.set_subdomain(2, Circle(Point(0.,0.43), R_2)) 16 | domain.set_subdomain(3, Circle(Point(-0.15,0.35), R_3)) 17 | mesh = generate_mesh(domain, N) 18 | d = mesh.topology().dim() # dimensionality of the problem 19 | markers = MeshFunction("size_t", mesh, d , mesh.domains()) 20 | 21 | 22 | # define boundary subdomains 23 | class Left(SubDomain): 24 | def inside(self, x, on_boundary): 25 | return near(x[0], -L/2) 26 | 27 | class Right(SubDomain): 28 | def inside(self, x, on_boundary): 29 | return near(x[0], +L/2) 30 | 31 | class Top(SubDomain): 32 | def inside(self, x, on_boundary): 33 | return near(x[1], L/2) 34 | 35 | class Bottom(SubDomain): 36 | def inside(self, x, on_boundary): 37 | return near(x[1], -L/2) 38 | 39 | left = Left() 40 | right = Right() 41 | top = Top() 42 | bottom = Bottom() 43 | 44 | # mark boundary subdomains with markers 1, 2, 3, 4 45 | boundaries = MeshFunction("size_t", mesh, d-1, 0) 46 | boundaries.set_all(0) 47 | left.mark(boundaries, 1) 48 | right.mark(boundaries, 2) 49 | top.mark(boundaries, 3) 50 | bottom.mark(boundaries, 4) 51 | 52 | 53 | # elastic constants of the matrix and two circular inclusions 54 | E_1 = 1 55 | E_2 = 10 56 | E_3 = 0.1 57 | nu_1 = 0.3 58 | nu_2 = 0.2 59 | nu_3 = 0.1 60 | 61 | 62 | # define class for calculating the Young's modulus over the whole domain 63 | class E_class(UserExpression): 64 | def __init__(self, **kwargs): 65 | self.markers = markers 66 | super().__init__(**kwargs) 67 | def eval_cell(self, value, x, ufc_cell): 68 | if markers[ufc_cell.index] == 1: 69 | value[0] = E_1 70 | elif markers[ufc_cell.index] == 2: 71 | value[0] = E_2 72 | else: 73 | value[0] = E_3 74 | def value_shape(self): 75 | return () 76 | 77 | # define class for calculating the Poisson's ratio over the whole domain 78 | class nu_class(UserExpression): 79 | def __init__(self, **kwargs): 80 | self.markers = markers 81 | super().__init__(**kwargs) 82 | def eval_cell(self, value, x, ufc_cell): 83 | if markers[ufc_cell.index] == 1: 84 | value[0] = nu_1 85 | elif markers[ufc_cell.index] == 2: 86 | value[0] = nu_2 87 | else: 88 | value[0] = nu_3 89 | def value_shape(self): 90 | return () 91 | 92 | # functions of elastic constants on the whole domain 93 | E = E_class(degree=1) 94 | nu = nu_class(degree=1) 95 | mu = E/2/(1+nu) 96 | Lambda = E*nu/(1-nu*nu) 97 | 98 | 99 | 100 | 101 | #define function space with mixed finite elements (displacements + 3 Lagrange multipliers) 102 | degreeElements = 1 103 | P1 = FiniteElement('Lagrange', mesh.ufl_cell(), degreeElements) 104 | R = FiniteElement('Real', mesh.ufl_cell(), 0) 105 | MFS = FunctionSpace(mesh, MixedElement([(P1*P1),(R*R),R])) 106 | 107 | #define function and split it into displacements u and Lagrange multipliers 108 | f = Function(MFS) 109 | u, c_trans, c_rot = split(f) 110 | #define test function 111 | tf = TestFunction(MFS) 112 | 113 | 114 | # define strain and stress 115 | def epsilon(u): 116 | return sym(grad(u)) 117 | def sigma(u): 118 | return 2*mu*epsilon(u) + Lambda*tr(epsilon(u))*Identity(d) 119 | 120 | #external load 121 | sigma_xx = 0.2*E_1 122 | sigma_xy = 0 123 | sigma_yy = 0 124 | sigma_0 = Constant(((sigma_xx,sigma_xy),(sigma_xy,sigma_yy))) 125 | 126 | #unit normal vector to the boundary 127 | n = FacetNormal(mesh) 128 | 129 | #tractions on boundaries 130 | t = sigma_0 * n 131 | 132 | #calculate elastic energy 133 | elastic_energy = 1/2*inner(sigma(u),epsilon(u))*dx 134 | #calculate work of external tractions 135 | work = dot(t,u)*ds 136 | #Lagrange multipliers to prevent rigid body motions 137 | r=Expression(('x[0]','x[1]'),degree=1) 138 | constraints = dot(c_trans,u)*dx + c_rot*(r[0]*u[1]-r[1]*u[0])*dx 139 | #total free energy 140 | free_energy = elastic_energy - work + constraints 141 | 142 | 143 | #minimize total free energy 144 | Res = derivative(free_energy, f, tf) 145 | solve(Res == 0, f) 146 | 147 | #calculate total free energy 148 | print("Tot Free Energy = ",assemble(free_energy)) 149 | print("Elastic Energy = ",assemble(elastic_energy)) 150 | print("Work = ",assemble(work)) 151 | print("Constraints = ",assemble(constraints)) 152 | 153 | 154 | # export displacements 155 | VFS = VectorFunctionSpace(mesh, 'Lagrange', 1) 156 | disp=project(u, VFS) 157 | disp.rename("displacements","") 158 | fileD = File("data/tractions_displacement.pvd"); 159 | fileD << disp; 160 | 161 | # calculate and export von Mises stress 162 | FS = FunctionSpace(mesh, 'Lagrange', 1) 163 | devStress = sigma(u) - (1./d)*tr(sigma(u))*Identity(d) # deviatoric stress 164 | von_Mises = project(sqrt(3./2*inner(devStress, devStress)), FS) 165 | von_Mises.rename("von Mises","") 166 | fileS = File("data/tractions_vonMises_stress.pvd"); 167 | fileS << von_Mises; 168 | 169 | # calculate and export stress component sigma_xx 170 | sigma_xx = project(sigma(u)[0,0], FS) 171 | sigma_xx.rename("sigma_xx","") 172 | fileS = File("data/tractions_sigma_xx.pvd"); 173 | fileS << sigma_xx; 174 | 175 | # calculate and export stress component sigma_yy 176 | sigma_yy = project(sigma(u)[1,1], FS) 177 | sigma_yy.rename("sigma_yy","") 178 | fileS = File("data/tractions_sigma_yy.pvd"); 179 | fileS << sigma_yy; 180 | 181 | # calculate and export stress component sigma_xy 182 | sigma_xy = project(sigma(u)[0,1], FS) 183 | sigma_xy.rename("sigma_xy","") 184 | fileS = File("data/tractions_sigma_xy.pvd"); 185 | fileS << sigma_xy; 186 | 187 | # export Young's modulus 188 | young = project(E, FS) 189 | young.rename("Young's modulus","") 190 | fileS = File("data/tractions_young.pvd"); 191 | fileS << young; 192 | -------------------------------------------------------------------------------- /elasticity/figs/.ipynb_checkpoints/elasticity_clamped_domain-checkpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akosmrlj/FEniCS_tutorial/0708db6a601a8e36f260819e448d59c175263bbd/elasticity/figs/.ipynb_checkpoints/elasticity_clamped_domain-checkpoint.png -------------------------------------------------------------------------------- /elasticity/figs/elasticity_clamped_domain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akosmrlj/FEniCS_tutorial/0708db6a601a8e36f260819e448d59c175263bbd/elasticity/figs/elasticity_clamped_domain.png -------------------------------------------------------------------------------- /elasticity/figs/elasticity_tractions_domain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akosmrlj/FEniCS_tutorial/0708db6a601a8e36f260819e448d59c175263bbd/elasticity/figs/elasticity_tractions_domain.png -------------------------------------------------------------------------------- /poisson/figs/poisson_basic_domain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akosmrlj/FEniCS_tutorial/0708db6a601a8e36f260819e448d59c175263bbd/poisson/figs/poisson_basic_domain.png -------------------------------------------------------------------------------- /poisson/figs/poisson_general_domain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akosmrlj/FEniCS_tutorial/0708db6a601a8e36f260819e448d59c175263bbd/poisson/figs/poisson_general_domain.png -------------------------------------------------------------------------------- /poisson/poisson_basic.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from fenics import * 3 | from mshr import * 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | 7 | # Create mesh 8 | R = 1. # radius 9 | N = 20 # mesh resolution 10 | domain = Circle(Point(0., 0.), R) 11 | mesh = generate_mesh(domain, N) 12 | plot(mesh,linewidth=0.3) 13 | plt.show() 14 | 15 | #define function space 16 | degreeElements = 1 17 | FS = FunctionSpace(mesh, 'Lagrange', degreeElements) 18 | 19 | #impose Dirichlet boundary conditions 20 | def boundary(x, on_boundary): 21 | return on_boundary 22 | 23 | bc = DirichletBC(FS, Constant(0.), boundary) 24 | 25 | #define function u and test function v 26 | u = Function(FS) 27 | v = TestFunction(FS) 28 | 29 | # weak formulation of the problem 30 | Res = -dot(grad(u), grad(v))*dx + v*dx 31 | 32 | # solve the problem 33 | solve(Res == 0, u, bc) 34 | 35 | # plot solution 36 | c = plot(u,mode='color',title='$u$') 37 | plt.colorbar(c) 38 | plot(mesh,linewidth=0.3) 39 | plt.show() 40 | 41 | # exact solution 42 | uExact=Expression('(1-x[0]*x[0]-x[1]*x[1])/4',degree=2) 43 | 44 | # Compute error (L2 norm) 45 | error_L2 = errornorm(uExact, u, 'L2') 46 | print("Error = ",error_L2) 47 | 48 | # evaluate function at several points 49 | print("u(0,0) = ",u(0,0)) 50 | print("u(0.5,0.5) = ",u(0.5,0.5)) 51 | #print("u(2,0) = ",u(2,0)) #point outside domain 52 | print("u(1,0) = ",u(1,0)) 53 | # print("u(0,1) = ",u(0,1)) #point outside the discretized domain 54 | 55 | 56 | # plot solution 57 | tol=1e-2; 58 | x=np.linspace(-1+tol,1-tol,40) 59 | points = [(x_, 0) for x_ in x] 60 | u_line = np.array([u(point) for point in points]) 61 | plt.plot(x,u_line,'k-') 62 | plt.plot(x,(1-x*x)/4,'r--') 63 | plt.xlabel('$x$') 64 | plt.ylabel('$u(x,0)$') 65 | plt.legend(['FEM','exact']) 66 | plt.show() 67 | -------------------------------------------------------------------------------- /poisson/poisson_basic2.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from fenics import * 3 | from mshr import * 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | 7 | # Create mesh 8 | R = 1. # radius 9 | N = 20 # mesh resolution 10 | domain = Circle(Point(0., 0.), R) 11 | mesh = generate_mesh(domain, N) 12 | 13 | #define function space 14 | degreeElements = 1 15 | FS = FunctionSpace(mesh, 'Lagrange', degreeElements) 16 | 17 | #impose Dirichlet boundary conditions 18 | def boundary(x, on_boundary): 19 | return on_boundary 20 | 21 | bc = DirichletBC(FS, Constant(0.), boundary) 22 | 23 | #define function u and test function v 24 | u = Function(FS) 25 | v = TestFunction(FS) 26 | 27 | # weak formulation of the problem 28 | f = Expression('t', degree=1, t=0.) 29 | #f = Constant(0.) 30 | Res = -dot(grad(u), grad(v))*dx + f*v*dx 31 | 32 | for t in range(0,3): 33 | # update the value of the source term 34 | f.t=t 35 | # f.assign(Constant((t))) 36 | 37 | # solve the problem 38 | solve(Res == 0, u, bc) 39 | 40 | # plot solution 41 | c = plot(u,mode='color',title='t = '+str(t),vmin=0,vmax=0.5) 42 | plt.colorbar(c) 43 | plot(mesh,linewidth=0.3) 44 | plt.show() 45 | -------------------------------------------------------------------------------- /poisson/poisson_general.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from fenics import * 3 | from mshr import * 4 | import matplotlib.pyplot as plt 5 | 6 | # Create mesh 7 | R = 1. # radius 8 | N = 20 # mesh density 9 | domain = Circle(Point(0., 0.), R) 10 | mesh = generate_mesh(domain, N) 11 | 12 | #define function space 13 | degreeElements = 1 14 | FS = FunctionSpace(mesh, 'Lagrange', degreeElements) 15 | 16 | #define function u and test function v 17 | u = Function(FS) 18 | v = TestFunction(FS) 19 | 20 | 21 | #impose Dirichlet boundary conditions 22 | def right_boundary(x, on_boundary): 23 | return on_boundary and x[0]>=0 24 | 25 | uD = Expression('x[0]',degree=degreeElements) 26 | bc = DirichletBC(FS, uD, right_boundary) 27 | 28 | 29 | # define functions a and f 30 | a = Expression('1-0.5*(x[0]*x[0]+x[1]*x[1])', degree=degreeElements) 31 | f = 1 32 | 33 | # define function g 34 | class G(UserExpression): 35 | def __init__(self, **kwargs): 36 | super().__init__(**kwargs) 37 | def eval_cell(self, value, x, ufc_cell): 38 | value[0]=x[1] 39 | def value_shape(self): 40 | return () 41 | 42 | g = G(degree=degreeElements) 43 | 44 | 45 | # weak formulation of the problem 46 | Res = a*dot(grad(u), grad(v))*dx - f*v*dx - g*v*ds 47 | 48 | # solve the problem 49 | solve(Res == 0, u, bc) 50 | 51 | # plot solution 52 | c = plot(u,mode='color',title='$u$') 53 | plt.colorbar(c) 54 | plot(mesh,linewidth=0.3) 55 | plt.show() 56 | 57 | # plot a*grad(u) 58 | VFS = VectorFunctionSpace(mesh, 'Lagrange', degreeElements) 59 | sigma=project(a*grad(u),VFS) 60 | c=plot(sigma,title='$a \\nabla u$',width=.008) 61 | plt.colorbar(c) 62 | plot(mesh,linewidth=0.3) 63 | plt.show() 64 | 65 | # plot solution for div(a*grad(u)) 66 | divSigma = project(div(sigma), FS) 67 | c = plot(divSigma,mode='color',title='$\\nabla \cdot (a \\nabla u)$',vmin=-1.1,vmax=-0.9) 68 | plt.colorbar(c) 69 | plot(mesh,linewidth=0.3) 70 | plt.show() 71 | -------------------------------------------------------------------------------- /poisson/poisson_minimization.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from fenics import * 3 | from mshr import * 4 | import matplotlib.pyplot as plt 5 | 6 | # Create mesh 7 | R = 1. # radius 8 | N = 20 # mesh density 9 | domain = Circle(Point(0., 0.), R) 10 | mesh = generate_mesh(domain, N) 11 | 12 | 13 | #define function space, function u, and test function v 14 | degreeElements = 1 15 | FS = FunctionSpace(mesh, 'Lagrange', degreeElements) 16 | u = Function(FS) 17 | v = TestFunction(FS) 18 | 19 | # define functions a and f 20 | a = Expression('1-0.5*(x[0]*x[0]+x[1]*x[1])', degree=degreeElements) 21 | f = 1 22 | 23 | # define function g 24 | class G(UserExpression): 25 | def __init__(self, **kwargs): 26 | super().__init__(**kwargs) 27 | def eval_cell(self, value, x, ufc_cell): 28 | value[0]=x[1] 29 | def value_shape(self): 30 | return () 31 | 32 | g = G(degree=degreeElements) 33 | 34 | 35 | #impose Dirichlet boundary conditions 36 | def right_boundary(x, on_boundary): 37 | return on_boundary and x[0]>=0 38 | 39 | uD = Expression('x[0]',degree=degreeElements) 40 | bc = DirichletBC(FS, uD, right_boundary) 41 | 42 | 43 | # functional 44 | E = (1/2*a*dot(grad(u),grad(u))-f*u)*dx - g*u*ds 45 | 46 | # solve the problem 47 | Res = derivative(E, u, v) 48 | solve(Res == 0, u, bc) 49 | 50 | # plot solution 51 | c = plot(u,mode='color',title='$u$') 52 | plt.colorbar(c) 53 | plot(mesh,linewidth=0.3) 54 | plt.show() 55 | 56 | # plot a*grad(u) 57 | VFS = VectorFunctionSpace(mesh, 'Lagrange', degreeElements) 58 | sigma=project(a*grad(u),VFS) 59 | c=plot(sigma,title='$a \\nabla u$',width=.008) 60 | plt.colorbar(c) 61 | plot(mesh,linewidth=0.3) 62 | plt.show() 63 | 64 | # plot solution for div(a*grad(u)) 65 | divSigma = project(div(sigma), FS) 66 | c=plot(divSigma,mode='color',title='$\\nabla \cdot (a \\nabla u)$',vmin=-1.1,vmax=-0.9) 67 | plt.colorbar(c) 68 | plot(mesh,linewidth=0.3) 69 | plt.show() 70 | -------------------------------------------------------------------------------- /poisson/poisson_mixed.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from fenics import * 3 | from mshr import * 4 | import matplotlib.pyplot as plt 5 | 6 | # Create mesh 7 | R = 1. # radius 8 | N = 20 # mesh density 9 | domain = Circle(Point(0., 0.), R) 10 | mesh = generate_mesh(domain, N) 11 | #plot(mesh,linewidth=0.3) 12 | 13 | 14 | #define function space with mixed finite elements 15 | degreeElements = 1 16 | FE_u = FiniteElement('Lagrange', mesh.ufl_cell(), degreeElements) 17 | FE_sigma = FiniteElement('Lagrange', mesh.ufl_cell(), degreeElements) 18 | FS = FunctionSpace(mesh, MixedElement([FE_u, FE_sigma*FE_sigma])) 19 | 20 | # define function and split it into u and sigma 21 | F = Function(FS) 22 | u,sigma = split(F) 23 | 24 | # define test function and split it into v and tau 25 | TF = TestFunction(FS) 26 | v,tau = split(TF) 27 | 28 | 29 | # define functions a and f 30 | a = Expression('1-0.5*(x[0]*x[0]+x[1]*x[1])', degree=degreeElements) 31 | f = 1 32 | 33 | # define function g 34 | class G(UserExpression): 35 | def __init__(self, **kwargs): 36 | super().__init__(**kwargs) 37 | def eval_cell(self, value, x, ufc_cell): 38 | value[0]=x[1] 39 | def value_shape(self): 40 | return () 41 | 42 | g = G(degree=degreeElements) 43 | 44 | 45 | #impose Dirichlet boundary conditions for u 46 | def right_boundary(x, on_boundary): 47 | return on_boundary and x[0]>=0 48 | 49 | uD = Expression('x[0]',degree=degreeElements) 50 | bc = DirichletBC(FS.sub(0), uD, right_boundary) 51 | 52 | #weak formulation of the problem 53 | Res_1 = (dot(sigma,grad(v)) - f*v)*dx - v*g*ds 54 | Res_2 = (dot(sigma, tau) - dot(a*grad(u), tau))*dx 55 | Res = Res_1 + Res_2 56 | 57 | # solve the problem and store solution in F 58 | solve(Res == 0, F, bc) 59 | 60 | # plot solution for u 61 | c = plot(u,mode='color',title='$u$') 62 | plt.colorbar(c) 63 | plot(mesh,linewidth=0.3) 64 | plt.show() 65 | 66 | # plot solution for sigma 67 | c=plot(sigma,title='$\sigma=a \\nabla u$',width=.008) 68 | plt.colorbar(c) 69 | plot(mesh,linewidth=0.3) 70 | plt.show() 71 | 72 | # plot solution for div(sigma)=div(a*grad(u)) 73 | V = FunctionSpace(mesh, 'Lagrange', degreeElements) 74 | divSigma = project(div(sigma), V) 75 | c=plot(divSigma,mode='color',title='$\\nabla \cdot \sigma=\\nabla \cdot (a \\nabla u)$',vmin=-1.1,vmax=-0.9) 76 | plt.colorbar(c) 77 | plot(mesh,linewidth=0.3) 78 | plt.show() 79 | 80 | -------------------------------------------------------------------------------- /thermoelasticity/figs/thermoelasticity_domain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akosmrlj/FEniCS_tutorial/0708db6a601a8e36f260819e448d59c175263bbd/thermoelasticity/figs/thermoelasticity_domain.png -------------------------------------------------------------------------------- /thermoelasticity/thermoelasticity.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from fenics import * 3 | from mshr import * 4 | import matplotlib.pyplot as plt 5 | 6 | 7 | # create square mesh 8 | N = 30 9 | L = 1 10 | domain = Rectangle(Point(0.,0.),Point(L,L)) 11 | mesh = generate_mesh(domain, N) 12 | d = mesh.topology().dim() # dimensionality of the problem 13 | print("d = ",d) 14 | plot(mesh,linewidth=0.3) 15 | plt.show() 16 | 17 | # elastic constants 18 | E = 1 19 | nu = 0.4 20 | mu = E/2/(1+nu) 21 | Lambda = E*nu/(1-nu*nu) 22 | 23 | # thermal expansion coefficient 24 | alpha = 0.1 25 | # material conductivity 26 | k = 1 27 | 28 | 29 | #define function space, temperature function T, and test function dT 30 | degreeElements = 1 31 | FS = FunctionSpace(mesh, 'Lagrange', degreeElements) 32 | T = Function(FS) 33 | delta_T = TestFunction(FS) 34 | 35 | 36 | # temperatures at the boundaries 37 | T0 = 0 38 | T1 = 1 39 | 40 | #define left, right, top, bottom boundaries 41 | def left_boundary(x, on_boundary): 42 | return on_boundary and near(x[0],0); 43 | def right_boundary(x, on_boundary): 44 | return on_boundary and near(x[0],L); 45 | def top_boundary(x, on_boundary): 46 | return on_boundary and near(x[1],L); 47 | def bottom_boundary(x, on_boundary): 48 | return on_boundary and near(x[1],0); 49 | 50 | # impose Dirichlet boundary conditions for temperature 51 | bc_T_left = DirichletBC(FS, Constant(T0), left_boundary) 52 | bc_T_right = DirichletBC(FS, Constant(T0), right_boundary) 53 | bc_T_top = DirichletBC(FS, Constant(T1), top_boundary) 54 | bc_T_bottom = DirichletBC(FS, Constant(T1), bottom_boundary) 55 | 56 | bc_T = [bc_T_left, bc_T_right, bc_T_top, bc_T_bottom] 57 | 58 | 59 | # solve for the temperature field 60 | Res_T = k*dot(grad(T),grad(delta_T))*dx 61 | solve(Res_T == 0, T, bc_T) 62 | 63 | # plot temperature field 64 | c = plot(T,mode='color',title='$T$') 65 | plt.colorbar(c) 66 | plot(mesh,linewidth=0.3) 67 | plt.show() 68 | 69 | # save temperature field 70 | T.rename("temperature","") 71 | fileT = File("data/temperature.pvd"); 72 | fileT << T; 73 | 74 | #define vector function space, displacements function u, and test function v 75 | VFS = VectorFunctionSpace(mesh, 'Lagrange', degreeElements) 76 | u = Function(VFS) 77 | v = TestFunction(VFS) 78 | 79 | 80 | #clamped boundary conditions on the left and right boundaries 81 | bc_u_left = DirichletBC(VFS, Constant((0.,0.)), left_boundary) 82 | bc_u_right = DirichletBC(VFS, Constant((0.,0.)), right_boundary) 83 | 84 | bc_u = [bc_u_left, bc_u_right] 85 | 86 | # define total strain 87 | def epsilon_tot(u): 88 | return sym(grad(u)) 89 | # define elastic strain 90 | def epsilon_elastic(u,T): 91 | return epsilon_tot(u) - alpha*T*Identity(d) 92 | # define stress 93 | def sigma(u,T): 94 | return 2*mu*epsilon_elastic(u,T) + Lambda*tr(epsilon_elastic(u,T))*Identity(d) 95 | 96 | # elastic energy functional 97 | Energy = 1/2*inner(sigma(u,T),epsilon_elastic(u,T))*dx 98 | 99 | # solve for the displacement field 100 | Res_u = derivative(Energy, u, v) 101 | solve(Res_u == 0, u, bc_u) 102 | 103 | # calculate elastic energy 104 | print("Energy = ",assemble(Energy)) 105 | 106 | # export displacements 107 | u.rename("displacements","") 108 | fileD = File("data/displacements.pvd"); 109 | fileD << u; 110 | 111 | # calculate and export von Mises stress 112 | devStress = sigma(u,T) - (1./d)*tr(sigma(u,T))*Identity(d) # deviatoric stress 113 | von_Mises = project(sqrt(3./2*inner(devStress, devStress)), FS) 114 | von_Mises.rename("von Mises","") 115 | fileS = File("data/vonMises_stress.pvd"); 116 | fileS << von_Mises; 117 | --------------------------------------------------------------------------------