├── .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 |
--------------------------------------------------------------------------------