├── .gitattributes
├── .gitignore
├── Boettinger-Figure-1.png
├── Boettinger-Figure-2.png
├── Cahn-Hilliard.mp4
├── Composite-Cropped.png
├── Enthalpy-Nonzero.png
├── Mixing.png
├── Practical Phase Field With Examples.ipynb
├── README.md
├── t-vs-g.png
└── t-vs-phi.png
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | MANIFEST
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 | .pytest_cache/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 | db.sqlite3
58 |
59 | # Flask stuff:
60 | instance/
61 | .webassets-cache
62 |
63 | # Scrapy stuff:
64 | .scrapy
65 |
66 | # Sphinx documentation
67 | docs/_build/
68 |
69 | # PyBuilder
70 | target/
71 |
72 | # Jupyter Notebook
73 | .ipynb_checkpoints
74 |
75 | # pyenv
76 | .python-version
77 |
78 | # celery beat schedule file
79 | celerybeat-schedule
80 |
81 | # SageMath parsed files
82 | *.sage.py
83 |
84 | # Environments
85 | .env
86 | .venv
87 | env/
88 | venv/
89 | ENV/
90 | env.bak/
91 | venv.bak/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 |
--------------------------------------------------------------------------------
/Boettinger-Figure-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucentdan/practical-phase-field-with-examples/2803d34d2f3c4beee913e84cff379757342c726b/Boettinger-Figure-1.png
--------------------------------------------------------------------------------
/Boettinger-Figure-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucentdan/practical-phase-field-with-examples/2803d34d2f3c4beee913e84cff379757342c726b/Boettinger-Figure-2.png
--------------------------------------------------------------------------------
/Cahn-Hilliard.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucentdan/practical-phase-field-with-examples/2803d34d2f3c4beee913e84cff379757342c726b/Cahn-Hilliard.mp4
--------------------------------------------------------------------------------
/Composite-Cropped.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucentdan/practical-phase-field-with-examples/2803d34d2f3c4beee913e84cff379757342c726b/Composite-Cropped.png
--------------------------------------------------------------------------------
/Enthalpy-Nonzero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucentdan/practical-phase-field-with-examples/2803d34d2f3c4beee913e84cff379757342c726b/Enthalpy-Nonzero.png
--------------------------------------------------------------------------------
/Mixing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucentdan/practical-phase-field-with-examples/2803d34d2f3c4beee913e84cff379757342c726b/Mixing.png
--------------------------------------------------------------------------------
/Practical Phase Field With Examples.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {
6 | "slideshow": {
7 | "slide_type": "slide"
8 | }
9 | },
10 | "source": [
11 | "## Practical Phase Field With Examples\n",
12 | "\n",
13 | "Dan Lewis \n",
14 | "Rensselaer Polytechnic Institute \n",
15 | "lewisd2@rpi.edu"
16 | ]
17 | },
18 | {
19 | "cell_type": "markdown",
20 | "metadata": {
21 | "slideshow": {
22 | "slide_type": "slide"
23 | }
24 | },
25 | "source": [
26 | "## Outline\n",
27 | "\n",
28 | "This presentation reviews three essential models that provide the basis for phase field modeling reports in the literature:\n",
29 | "\n",
30 | "1. A Pure Material\n",
31 | "1. Spinodal Decomposition (and Order Disorder Transformations)\n",
32 | "1. Multi-Order Parameter Models"
33 | ]
34 | },
35 | {
36 | "cell_type": "markdown",
37 | "metadata": {
38 | "slideshow": {
39 | "slide_type": "skip"
40 | }
41 | },
42 | "source": [
43 | "While many other models are used and published, I feel that these three will help readers better understand the literature."
44 | ]
45 | },
46 | {
47 | "cell_type": "markdown",
48 | "metadata": {
49 | "slideshow": {
50 | "slide_type": "slide"
51 | }
52 | },
53 | "source": [
54 | "## Getting Started\n",
55 | "\n",
56 | "To use these notes you will need:\n",
57 | "\n",
58 | "* A Python installation (such as Anaconda)\n",
59 | "* The FiPy PDE solving package (installed via Python commands)\n",
60 | "* The ability to start and use basic functionality of the Jupyter Notebook \n",
61 | "* An understanding of basic numerical methods"
62 | ]
63 | },
64 | {
65 | "cell_type": "markdown",
66 | "metadata": {
67 | "slideshow": {
68 | "slide_type": "skip"
69 | }
70 | },
71 | "source": [
72 | "## References\n",
73 | "\n",
74 | "* W. J. Boettinger, J. A. Warren, et al., \"Phase Field Simulation of Solidification\", Annual Review of Materials Research, v32, p163-194 (2002)\n",
75 | "* Free Energy of a Nonuniform System. I. Interfacial Free Energy, Journal of chemical Physics, v28, n2, p258-267 (1958) \n",
76 | "* R. DeHoff, \"Thermodynamics in Materials Science\", CRC Press, 2006\n",
77 | "* FiPy at http://www.nist.gov"
78 | ]
79 | },
80 | {
81 | "cell_type": "markdown",
82 | "metadata": {
83 | "slideshow": {
84 | "slide_type": "slide"
85 | }
86 | },
87 | "source": [
88 | "## Introduction\n",
89 | "----\n",
90 | "\n",
91 | "* The phase field method makes possible the study of complex microstructural morphologies such as dendritic and eutectic solidification as well as polycrystalline growth. \n",
92 | "* The major contribution of the method is the introduction of an order parameter used to delineate phases such as solid/liquid, $\\alpha~/~\\beta$, etc. The concept of an order parameter is not new. However, smoothly varying this order parameter through an interphase interface frees us from tracking the interface position and applying boundary conditions at interfaces having complex morphologies.\n",
93 | "* Three \"building block\" models are important for study, 1) the pure material, 2) a chemical solution model, and 3) a multiphase model. These three models are the basis for other more complex models."
94 | ]
95 | },
96 | {
97 | "cell_type": "markdown",
98 | "metadata": {
99 | "slideshow": {
100 | "slide_type": "skip"
101 | }
102 | },
103 | "source": [
104 | "A small excerpt from Boettinger's review paper (cited above):\n",
105 | "\n",
106 | ">The method employs a phase-field variable, e.g., $\\phi$, which is a function of position and time, to describe whether the material is liquid or solid. The behavior of this variable is governed by an equation that is coupled to equations for heat\n",
107 | "and solute transport. Interfaces between liquid and solid are described by smooth but highly localized changes of this variable between fixed values that represent solid and liquid, (in this review, 0 and 1, respectively).\n",
108 | "\n",
109 | "Using the order parameter in this way makes it easier to calculate solidification microstructures - we no longer have to track the interface. The shape (while changing from 0 to 1) of this interface is a balance between two energies. The first energy contribution is an increase in the bulk free energy when the order parameter lies between 0 and 1. The bulk energy contribution is smallest when the material has an infinitely sharp interface and no intermediate states. The second contribution is the interfacial cost that scales with the gradient of the order parameter. \n",
110 | "\n",
111 | "One of these energies seeks to make the interface sharper (the bulk energy) and the other seeks to make the interface more diffuse (the gradient energy). The balance between these two represents an interfacial equilbrium. The model for these energies is discussed next."
112 | ]
113 | },
114 | {
115 | "cell_type": "markdown",
116 | "metadata": {
117 | "slideshow": {
118 | "slide_type": "slide"
119 | }
120 | },
121 | "source": [
122 | "## The Free Energy of a Non-Uniform System\n",
123 | "----\n",
124 | "\n",
125 | "Cahn and Hilliard identified the lowest order correction to the free energy density that arises from the presence of an interface. Inclusion of this term forms the energetic basis for the phase-field method.\n",
126 | "\n",
127 | "If the temperature and pressure are our process variables, then we can use the Gibbs free energy. The total energy of the system is found by integrating the Gibbs free energy density over the volume of the system. Furthermore we assume that the order parameter (and/or composition) and powers of the derivatives of order parameter all contribute to the free energy and are independent:\n",
128 | "\n",
129 | "$$\n",
130 | "\\mathscr{F} = \\int_V f_v(\\phi, \\nabla \\phi, \\nabla^2 \\phi, ...) \\delta V\n",
131 | "$$"
132 | ]
133 | },
134 | {
135 | "cell_type": "markdown",
136 | "metadata": {
137 | "slideshow": {
138 | "slide_type": "slide"
139 | }
140 | },
141 | "source": [
142 | "It is possible to expand the integrand explicitly in powers of the independent parameters (a multivariate Taylor series), in a shorthand notation we write an equivalent statement:\n",
143 | "\n",
144 | "$$\n",
145 | "f_v = f_v^0 + L \\nabla \\phi + K_1 \\nabla^2 \\phi + K_2 (\\nabla \\phi)^2 + \\; ...\n",
146 | "$$\n",
147 | "\n",
148 | "with\n",
149 | "\n",
150 | "$$\n",
151 | "L = \\left( \\frac{\\partial f_v}{\\partial (\\nabla \\phi)} \\right)\n",
152 | "$$\n",
153 | "\n",
154 | "and **other similar terms** as per the Taylor's Series expansion above treating $\\phi$ and all higher order derivatives as independent parameters in the free energy space. These extra terms can be viewed as \"correction\" terms in the approximation of the free energy density near equilibrium.\n",
155 | "\n",
156 | "Other terms are argued to be zero due to symmetry considerations."
157 | ]
158 | },
159 | {
160 | "cell_type": "markdown",
161 | "metadata": {
162 | "slideshow": {
163 | "slide_type": "slide"
164 | }
165 | },
166 | "source": [
167 | "We keep the lowest order, nonzero correction term in the gradient of the order parameter. These assumptions can be relaxed for different applications. Once the bulk free energy, $f_v$, is identified it is possible to find the function $\\phi(x)$ that makes the integral an extreme value. The functional is therefore composed of two parts, a bulk energy and a gradient energy:\n",
168 | "\n",
169 | "$$\n",
170 | "\\mathscr{L} = \\int_V \\mathscr{F}(\\phi, \\nabla \\phi)\\; \\delta V = \\int_V f_v(\\phi, T) + K (\\nabla \\phi)^2 \\; \\delta V\n",
171 | "$$"
172 | ]
173 | },
174 | {
175 | "cell_type": "markdown",
176 | "metadata": {
177 | "slideshow": {
178 | "slide_type": "slide"
179 | }
180 | },
181 | "source": [
182 | "We can do this analytically for a pure material, but most other applications require relaxation and/or numerical methods."
183 | ]
184 | },
185 | {
186 | "cell_type": "markdown",
187 | "metadata": {
188 | "slideshow": {
189 | "slide_type": "slide"
190 | }
191 | },
192 | "source": [
193 | "## A Homogeneous Free Energy Function\n",
194 | "----\n",
195 | "\n",
196 | "It is necessary to have a smooth function that links the order parameter to other thermodynamic variables such as temperature or composition. There are two so-called helper functions that help the modeler homogenize the free energy space permitting continuous calculation of the free energy for all values of the order parameter and other thermodynamic variables. The interpolating function, $p(\\phi)$ and the double well function $g(\\phi)$ are used for this purpose. For reference they are plotted below; as is often stated in the literature, other functions are possible, but these are the simplest and have the properties we desire. The properties of interest are the values of the functions and the value and sign of the derivatives of these functions."
197 | ]
198 | },
199 | {
200 | "cell_type": "code",
201 | "execution_count": null,
202 | "metadata": {
203 | "slideshow": {
204 | "slide_type": "skip"
205 | }
206 | },
207 | "outputs": [],
208 | "source": [
209 | "%matplotlib inline\n",
210 | "\n",
211 | "import matplotlib.pyplot as plt\n",
212 | "import numpy as np\n",
213 | "\n",
214 | "def plot_p_and_g():\n",
215 | " phi = np.linspace(-0.1, 1.1, 200)\n",
216 | " g=phi**2*(1-phi)**2\n",
217 | " p=phi**3*(6*phi**2-15*phi+10)\n",
218 | "\n",
219 | " # Changed 3 to 1 in the figure call.\n",
220 | " plt.figure(1, figsize=(10,5))\n",
221 | " plt.subplot(121)\n",
222 | " plt.plot(phi, g, linewidth=1.0);\n",
223 | " plt.xlabel('$\\phi$', fontsize=12)\n",
224 | " plt.ylabel('$g(\\phi)$', fontsize=12)\n",
225 | "\n",
226 | " plt.subplot(122)\n",
227 | " plt.plot(phi, p, linewidth=1.0);\n",
228 | " plt.xlabel('$\\phi$', fontsize=12)\n",
229 | " plt.ylabel('$p(\\phi)$', fontsize=12)\n",
230 | " \n",
231 | " return"
232 | ]
233 | },
234 | {
235 | "cell_type": "code",
236 | "execution_count": null,
237 | "metadata": {
238 | "scrolled": false,
239 | "slideshow": {
240 | "slide_type": "slide"
241 | }
242 | },
243 | "outputs": [],
244 | "source": [
245 | "plot_p_and_g()"
246 | ]
247 | },
248 | {
249 | "cell_type": "markdown",
250 | "metadata": {
251 | "slideshow": {
252 | "slide_type": "slide"
253 | }
254 | },
255 | "source": [
256 | "## Example 1A: Pure Material\n",
257 | "----\n",
258 | "\n",
259 | "\n",
260 | "Using the Lagrangian with the squared gradient correction and the helper functions we are ready to construct the free energy of a pure system. We start by using the ordinary free energy of the pure components:\n",
261 | "\n",
262 | " * Pure A, liquid phase - $f_A^L(T)$\n",
263 | " * Pure A, solid phase - $f_A^S(T)$\n",
264 | "\n",
265 | "The superscripts refer to the free energy and the subscripts refer to the species. As we will be limiting ourselves to a pure material at this time, these are the only two free energies we need. Near the melting point these free energies are often modeled as straight lines using the relationship for the Gibbs free energy:\n",
266 | "\n",
267 | "$$G = H - TS$$"
268 | ]
269 | },
270 | {
271 | "cell_type": "markdown",
272 | "metadata": {
273 | "slideshow": {
274 | "slide_type": "slide"
275 | }
276 | },
277 | "source": [
278 | "taking H and S to be constants. Following conventions of the phase diagram modeling community we take the reference state of the component A to be the equilibrium phase at STP. If this were a metal like Cu then the reference state would be the FCC phase. For us, that will be the SOLID. This sets:\n",
279 | "\n",
280 | "$$f_A^S(T) = 0$$\n",
281 | "\n",
282 | "Expanding the difference in free energy between the solid and the liquid around the melting point results in:\n",
283 | "\n",
284 | "$$f_A^L(T)-f_A^S(T) = L_A \\frac{(T_M^A - T)}{T_M^A}$$"
285 | ]
286 | },
287 | {
288 | "cell_type": "markdown",
289 | "metadata": {
290 | "slideshow": {
291 | "slide_type": "slide"
292 | }
293 | },
294 | "source": [
295 | "The next step is to homogenize the free energy for component A. We build the free energy $f(\\phi,T)_A$ as follows:\n",
296 | "\n",
297 | "$$f(\\phi,T)_A = W_A~g(\\phi) + f_A^L(T) p(\\phi) + f_A^S(T) (1-p(\\phi))$$"
298 | ]
299 | },
300 | {
301 | "cell_type": "code",
302 | "execution_count": null,
303 | "metadata": {
304 | "slideshow": {
305 | "slide_type": "skip"
306 | }
307 | },
308 | "outputs": [],
309 | "source": [
310 | "%matplotlib notebook\n",
311 | "\n",
312 | "import matplotlib.pyplot as plt\n",
313 | "import numpy as np\n",
314 | "from mpl_toolkits.mplot3d import Axes3D\n",
315 | "from matplotlib.ticker import LinearLocator, FormatStrFormatter\n",
316 | "\n",
317 | "def plot_homogeneous_F():\n",
318 | " # Create the space for the figure and the axes \n",
319 | " # that will hold the three dimensional information.\n",
320 | " plt.fig = plt.figure(2, figsize=(5,5))\n",
321 | " plt.ax = plt.fig.gca(projection='3d')\n",
322 | "\n",
323 | " phi = np.linspace(0.0, 1.0, 100)\n",
324 | " temperature = np.linspace(0.0, 1.0, 100)\n",
325 | " phi,temperature = np.meshgrid(phi,temperature)\n",
326 | "\n",
327 | " W=30.0\n",
328 | " L=1.0\n",
329 | " Tm=0.5\n",
330 | " g=phi**2*(1-phi)**2\n",
331 | " p=phi**3*(6*phi**2-15*phi+10)\n",
332 | "\n",
333 | " f = W*g+L*p*(Tm-temperature)/Tm\n",
334 | "\n",
335 | " energyPlot = plt.ax.plot_surface(phi, temperature, f, label=None, \n",
336 | " cmap=plt.cm.coolwarm, rstride=5, cstride=5, alpha=0.5)\n",
337 | " energyPlot = plt.contour(phi, temperature, f, 20)\n",
338 | "\n",
339 | " plt.clabel(energyPlot, inline=1, fontsize=10)\n",
340 | " plt.ax.set_xlabel('$\\phi$')\n",
341 | " plt.ax.set_ylabel('T')\n",
342 | " plt.ax.set_zlabel('$f(\\phi,t)$')\n",
343 | " \n",
344 | " return"
345 | ]
346 | },
347 | {
348 | "cell_type": "markdown",
349 | "metadata": {
350 | "slideshow": {
351 | "slide_type": "slide"
352 | }
353 | },
354 | "source": [
355 | "so that:\n",
356 | "\n",
357 | "$$f(\\phi,T)_A = W_A~g(\\phi) + L_A \\frac{(T_M^A - T)}{T_M^A}p(\\phi)$$\n",
358 | "\n",
359 | "Plotting this and viewing the results from different angles will help us see how more traditional treatments are embedded within a continuous energy space."
360 | ]
361 | },
362 | {
363 | "cell_type": "code",
364 | "execution_count": null,
365 | "metadata": {
366 | "scrolled": false,
367 | "slideshow": {
368 | "slide_type": "slide"
369 | }
370 | },
371 | "outputs": [],
372 | "source": [
373 | "plot_homogeneous_F()"
374 | ]
375 | },
376 | {
377 | "cell_type": "markdown",
378 | "metadata": {
379 | "slideshow": {
380 | "slide_type": "skip"
381 | }
382 | },
383 | "source": [
384 | "If the notebook is working properly you should see a three dimensional representation of the free energy function $f(\\phi,T)_A$. There are two important views.\n",
385 | "\n",
386 | "In this view we can see that the edges of our surface at $\\phi=0$ and $\\phi=1$ show the trace of the Gibbs free energy in the vicinity of the melting temperature. The consequence of our linear free energy assumption is apparent and we can see that the two free energies are equal at the melting temperature. Our intution tells us that the Gibbs free energy of the most stable phase should be lowest in a pure material. At temperatures lower than the melting point, $G_S$ is the lowest energy and the reverse is true at temperatures higher than the melting point."
387 | ]
388 | },
389 | {
390 | "cell_type": "markdown",
391 | "metadata": {
392 | "slideshow": {
393 | "slide_type": "slide"
394 | }
395 | },
396 | "source": [
397 | "The projection of the homogeneous free energy in the $T,G$ plane. \n",
398 | "\n",
399 | ""
400 | ]
401 | },
402 | {
403 | "cell_type": "markdown",
404 | "metadata": {},
405 | "source": [
406 | "The second view is in the $T,\\phi$ plane. In this view we can see the effect of changing temperature on the equilibrium between the solid and liquid phases. At all temperatures, the free energy is continuous in the order parameter and is highest when the system is at intermediate states. At equilibrium, the bulk free energy of the solid and liquid is equal with an energy barrier between them. Later, we will see that this energy barrier is related to the excess Gibbs free energy due to the presence of an interface. The parameters in the phase field model scale the height of this barrier and permit us to control the interfacial thickness and the surface energy in our model."
407 | ]
408 | },
409 | {
410 | "cell_type": "markdown",
411 | "metadata": {},
412 | "source": [
413 | "The projection of the homogeneous free energy in the $T,\\phi$ plane. \n",
414 | "\n",
415 | ""
416 | ]
417 | },
418 | {
419 | "cell_type": "markdown",
420 | "metadata": {},
421 | "source": []
422 | },
423 | {
424 | "cell_type": "markdown",
425 | "metadata": {
426 | "slideshow": {
427 | "slide_type": "slide"
428 | }
429 | },
430 | "source": [
431 | "## The Analytic Solution\n",
432 | "----\n",
433 | "\n",
434 | "If we are at the melting temperature, the temeprature dependent term goes to zero and we are left with:\n",
435 | "\n",
436 | "$$\n",
437 | "F(\\phi,\\phi') = W \\phi^2 (1-\\phi)^2 + \\epsilon (\\nabla \\phi)^2\n",
438 | "$$\n",
439 | "\n",
440 | "Applying the Euler-Lagrange equation to our functional we get:\n",
441 | "\n",
442 | "\\begin{align}\n",
443 | "\\frac{\\delta F}{\\delta \\phi} & = \\frac{\\partial F}{\\partial \\phi} - \\frac{d}{dx} \\frac{\\partial F}{\\partial \\nabla \\phi} = 0 \\\\ &= 2 W \\phi \\left(\\phi - 1\\right) \\left(2 \\phi - 1\\right) - 2 \\epsilon \\nabla^2 \\phi = 0\n",
444 | "\\end{align}\n",
445 | "\n",
446 | "recall that $\\phi(x,t)$ and this equation implies equilibrium."
447 | ]
448 | },
449 | {
450 | "cell_type": "markdown",
451 | "metadata": {
452 | "slideshow": {
453 | "slide_type": "slide"
454 | }
455 | },
456 | "source": [
457 | "There is a fair bit of algebra and a few assumptions that enable the solution to the Euler-Lagrange equation above:\n",
458 | "\n",
459 | "* First - assume that you are at the melting temperature. The rationale is that this is the only temperature where BOTH phases CO-EXIST. Any other temperature and it does not make sense to discuss an interface. This removes the second term on the RHS of the above expression.\n",
460 | "* Second - you can use $\\frac{d\\phi}{dx}$ as an integrating factor to take the first integral of the Euler-Lagrange equation.\n",
461 | "* Third - after evaluating the constant (C=0 is the answer) the equation is seperable and can be integrated.\n",
462 | " \n",
463 | "The result is:\n",
464 | " \n",
465 | "$$\\phi(x) = \\frac{1}{2} \\Big[ 1 + \\tanh \\Big( \\frac{x}{2\\delta} \\Big) \\Big]$$\n",
466 | " \n",
467 | "where $\\delta$ (the interface thickness) is related to the $W$ and $\\epsilon$ parameters and can also be related to $\\gamma$ (the surface energy). "
468 | ]
469 | },
470 | {
471 | "cell_type": "code",
472 | "execution_count": null,
473 | "metadata": {
474 | "slideshow": {
475 | "slide_type": "skip"
476 | }
477 | },
478 | "outputs": [],
479 | "source": [
480 | "%matplotlib inline\n",
481 | "import matplotlib.pyplot as plt\n",
482 | "import numpy as np\n",
483 | "from ipywidgets import interact, fixed\n",
484 | "from IPython.display import clear_output\n",
485 | "\n",
486 | "fig = None\n",
487 | "def plot_equilibrium(W=500.0, epsilon=1.0):\n",
488 | " #global fig\n",
489 | " #if fig: plt.close(fig)\n",
490 | " fig = plt.figure()\n",
491 | " x = np.linspace(-1.0, 1.0, 200)\n",
492 | " phi = 0.5*(1+np.tanh(x*np.sqrt(2*W)/(2*epsilon)))\n",
493 | " plt.plot(x, phi, linewidth=1.0)\n",
494 | " plt.xlabel('$x$', fontsize=12)\n",
495 | " plt.ylabel('$\\phi(x)$', fontsize=12) \n",
496 | " plt.show()\n",
497 | " return"
498 | ]
499 | },
500 | {
501 | "cell_type": "code",
502 | "execution_count": null,
503 | "metadata": {
504 | "slideshow": {
505 | "slide_type": "slide"
506 | }
507 | },
508 | "outputs": [],
509 | "source": [
510 | "interact(plot_equilibrium, W=(0.001,1000,10), epsilon=fixed(1.0));"
511 | ]
512 | },
513 | {
514 | "cell_type": "markdown",
515 | "metadata": {},
516 | "source": [
517 | "## Takeaways\n",
518 | "----\n",
519 | "\n",
520 | "* The pure material illustrates how the thermodynamics relates to the order parameter.\n",
521 | "* The analytical solution illustrates more clearly the $W$ and $\\epsilon$ parameters in terms of the interface shape, from this we can precisely compute the excess Gibbs energy due to the interface (the surface energy) and the interface thickness.\n",
522 | "* Analytic solutions are not available (and likely not possible) for the alloy or more complicated cases. A relaxation method is used, instead.\n"
523 | ]
524 | },
525 | {
526 | "cell_type": "markdown",
527 | "metadata": {
528 | "slideshow": {
529 | "slide_type": "slide"
530 | }
531 | },
532 | "source": [
533 | "## Solving the PDE Using Relaxation\n",
534 | "----\n",
535 | "\n",
536 | "With the bulk free energy and the gradient energy contributions conceptually justified it is now necessary to identify the equations of motion. In the non conserved case:\n",
537 | "\n",
538 | "$$\n",
539 | "\\frac{\\partial \\phi}{\\partial t} = -M \\frac{\\delta F}{\\delta \\phi}\n",
540 | "$$\n",
541 | "\n",
542 | "and for a conserved order parameter the equations of motion are derived from:\n",
543 | "\n",
544 | "$$\n",
545 | "\\frac{\\partial \\phi}{\\partial t} = \\nabla \\cdot D \\nabla \\frac{\\delta F}{\\delta \\phi}\n",
546 | "$$\n",
547 | "\n",
548 | "There are other choices, but these are the simplest choices that guarantee a free energy decrease with time."
549 | ]
550 | },
551 | {
552 | "cell_type": "markdown",
553 | "metadata": {
554 | "slideshow": {
555 | "slide_type": "skip"
556 | }
557 | },
558 | "source": [
559 | "For this demonstration, FiPy will be used to solve the phase field equations. FiPy is a vinite volume PDE solver that provides access to _diffusion_, _convection_, and _source_ terms that would be present in a typical phase field PDE. FiPy has much more functionality then what is shown here. \n",
560 | "\n",
561 | "FiPy is imported along with some helper functions. This example is for a NON-conserved order parameter."
562 | ]
563 | },
564 | {
565 | "cell_type": "code",
566 | "execution_count": null,
567 | "metadata": {
568 | "slideshow": {
569 | "slide_type": "slide"
570 | }
571 | },
572 | "outputs": [],
573 | "source": [
574 | "%matplotlib notebook\n",
575 | "from fipy import *\n",
576 | "from IPython.display import clear_output"
577 | ]
578 | },
579 | {
580 | "cell_type": "markdown",
581 | "metadata": {
582 | "slideshow": {
583 | "slide_type": "skip"
584 | }
585 | },
586 | "source": [
587 | "The parameters that define our mesh are set."
588 | ]
589 | },
590 | {
591 | "cell_type": "code",
592 | "execution_count": null,
593 | "metadata": {
594 | "slideshow": {
595 | "slide_type": "slide"
596 | }
597 | },
598 | "outputs": [],
599 | "source": [
600 | "L = 1.\n",
601 | "nx = 400\n",
602 | "dx = L/nx"
603 | ]
604 | },
605 | {
606 | "cell_type": "markdown",
607 | "metadata": {
608 | "slideshow": {
609 | "slide_type": "skip"
610 | }
611 | },
612 | "source": [
613 | "We use FiPy's Grid1D method to produce a mesh and place our phase variable on that mesh."
614 | ]
615 | },
616 | {
617 | "cell_type": "code",
618 | "execution_count": null,
619 | "metadata": {
620 | "slideshow": {
621 | "slide_type": "slide"
622 | }
623 | },
624 | "outputs": [],
625 | "source": [
626 | "mesh = Grid1D(dx=dx, nx=nx)\n",
627 | "phase = CellVariable(name=\"phase\",mesh=mesh)"
628 | ]
629 | },
630 | {
631 | "cell_type": "markdown",
632 | "metadata": {
633 | "slideshow": {
634 | "slide_type": "skip"
635 | }
636 | },
637 | "source": [
638 | "Getting the centerpoint of each cell permits us to write geometric constraints for setting the intital conditions, etc."
639 | ]
640 | },
641 | {
642 | "cell_type": "code",
643 | "execution_count": null,
644 | "metadata": {
645 | "slideshow": {
646 | "slide_type": "slide"
647 | }
648 | },
649 | "outputs": [],
650 | "source": [
651 | "x = mesh.cellCenters\n",
652 | "phase.setValue(1.)\n",
653 | "phase.setValue(0., where=x > L/2)"
654 | ]
655 | },
656 | {
657 | "cell_type": "markdown",
658 | "metadata": {
659 | "slideshow": {
660 | "slide_type": "skip"
661 | }
662 | },
663 | "source": [
664 | "FiPy has built in viewers for observing the results."
665 | ]
666 | },
667 | {
668 | "cell_type": "code",
669 | "execution_count": null,
670 | "metadata": {
671 | "slideshow": {
672 | "slide_type": "slide"
673 | }
674 | },
675 | "outputs": [],
676 | "source": [
677 | "viewer = MatplotlibViewer(vars=(phase,),datamin=-0.1, datamax=1.1, legend=None)"
678 | ]
679 | },
680 | {
681 | "cell_type": "markdown",
682 | "metadata": {
683 | "slideshow": {
684 | "slide_type": "skip"
685 | }
686 | },
687 | "source": [
688 | "We set the remaining simulation parameters, such as the gradient energy coefficient, the double well potential, and the thermodynamic parameters. Note the addition of the mobility $M$ for the relaxation method. This term can be physically correlated to the atomic attachment kinetcs."
689 | ]
690 | },
691 | {
692 | "cell_type": "code",
693 | "execution_count": null,
694 | "metadata": {
695 | "slideshow": {
696 | "slide_type": "slide"
697 | }
698 | },
699 | "outputs": [],
700 | "source": [
701 | "eps_sqrd = 0.00025 \n",
702 | "M = 1.0\n",
703 | "W = 0.5\n",
704 | "Lv = 1.\n",
705 | "Tm = 1.\n",
706 | "T = 1.0\n",
707 | "enthalpy = Lv*(Tm-T)/Tm"
708 | ]
709 | },
710 | {
711 | "cell_type": "markdown",
712 | "metadata": {
713 | "slideshow": {
714 | "slide_type": "skip"
715 | }
716 | },
717 | "source": [
718 | "The final step to setup the problem is to partition our model into FiPy's terms. In this case we have a single diffusion term. We bundle all the remaining terms into the source term:"
719 | ]
720 | },
721 | {
722 | "cell_type": "code",
723 | "execution_count": null,
724 | "metadata": {
725 | "slideshow": {
726 | "slide_type": "slide"
727 | }
728 | },
729 | "outputs": [],
730 | "source": [
731 | "S0 = W*2.0*phase*(phase-1.0)*(2*phase-1.0) + 30*phase**2*(phase**2-2*phase+1)*enthalpy\n",
732 | "eq = TransientTerm() == DiffusionTerm(coeff=eps_sqrd*M) - S0"
733 | ]
734 | },
735 | {
736 | "cell_type": "markdown",
737 | "metadata": {
738 | "slideshow": {
739 | "slide_type": "skip"
740 | }
741 | },
742 | "source": [
743 | "FiPy has many solvers, we use the default solvers and call for the solution to the equations."
744 | ]
745 | },
746 | {
747 | "cell_type": "code",
748 | "execution_count": null,
749 | "metadata": {
750 | "slideshow": {
751 | "slide_type": "slide"
752 | }
753 | },
754 | "outputs": [],
755 | "source": [
756 | "for i in range(50):\n",
757 | " eq.solve(var = phase, dt=0.1)\n",
758 | " viewer.plot()\n",
759 | " clear_output(wait=True)\n",
760 | " display(viewer)"
761 | ]
762 | },
763 | {
764 | "cell_type": "markdown",
765 | "metadata": {
766 | "slideshow": {
767 | "slide_type": "slide"
768 | }
769 | },
770 | "source": [
771 | "## Example 1B: Pair of Particles\n",
772 | "----\n",
773 | "\n",
774 | "The Gibbs-Thomson effect is present within the energy formulation of the phase field equations. Because of this, curved particles in a system at the melting temperature will dissolve. As one would expect, a flat interface is an equilibrium interface. The example below illustrates this. Note that the only difference between this case and the previous case is the intitial condition. "
775 | ]
776 | },
777 | {
778 | "cell_type": "code",
779 | "execution_count": null,
780 | "metadata": {
781 | "slideshow": {
782 | "slide_type": "skip"
783 | }
784 | },
785 | "outputs": [],
786 | "source": [
787 | "%matplotlib notebook\n",
788 | "from fipy import *\n",
789 | "from IPython.display import clear_output\n",
790 | "\n",
791 | "\n",
792 | "L = 1.\n",
793 | "nx = 200\n",
794 | "dx = L/nx\n",
795 | "dy = L/nx\n",
796 | "mesh = Grid2D(dx=dx, dy=dx, nx=nx, ny=nx)\n",
797 | "phase = CellVariable(name=\"phase\", mesh=mesh)\n",
798 | "x = mesh.cellCenters()[0]\n",
799 | "y = mesh.cellCenters()[1]\n",
800 | "\n",
801 | "phase.setValue(1.)\n",
802 | "x0 = 0.0\n",
803 | "y0 = 0.0\n",
804 | "\n",
805 | "# A slightly more complicated initial condition that takes advantage\n",
806 | "# of access to the cell centers. The \"where\" syntax is very helpful.\n",
807 | "phase.setValue(0., where=(((x-x0)**2+(y-y0)**2 > L/3) & ((x-L)**2+(y-L)**2 > 0.2)))\n",
808 | "\n",
809 | "# If you want to start with a noisy initial condition, do this:\n",
810 | "# phase.setValue(ExponentialNoiseVariable(mesh=mesh, mean=0.5))\n",
811 | "# There are various reasons one would want to do this.\n",
812 | "\n",
813 | "viewer = Matplotlib2DGridViewer(vars=phase, datamin=0.0, datamax=1.0)\n",
814 | "\n",
815 | "eps_sqrd = 0.00025 \n",
816 | "M = 1.0\n",
817 | "W = 0.5\n",
818 | "Lv = 1.\n",
819 | "Tm = 1.\n",
820 | "T = 1.\n",
821 | "enthalpy = Lv*(Tm-T)/Tm\n",
822 | "\n",
823 | "S0 = W*2.0*phase*(phase-1.0)*(2*phase-1.0) + 30*phase**2*(phase**2-2*phase+1)*enthalpy\n",
824 | "eq = TransientTerm() == DiffusionTerm(coeff=eps_sqrd) - S0"
825 | ]
826 | },
827 | {
828 | "cell_type": "code",
829 | "execution_count": null,
830 | "metadata": {
831 | "slideshow": {
832 | "slide_type": "slide"
833 | }
834 | },
835 | "outputs": [],
836 | "source": [
837 | "for i in range(500):\n",
838 | " eq.solve(var = phase, dt=0.05)\n",
839 | " viewer.plot()\n",
840 | " clear_output(wait=True)\n",
841 | " display(viewer)"
842 | ]
843 | },
844 | {
845 | "cell_type": "markdown",
846 | "metadata": {
847 | "slideshow": {
848 | "slide_type": "slide"
849 | }
850 | },
851 | "source": [
852 | "## Example 2: Spinodal Decomposition (and Order/Disorder Transformations)\n",
853 | "----\n",
854 | "\n",
855 | "The method is general enough to deal with more complex solutions. We can replace the bulk free energy of a pure material with the free energy of a solid solution. The creation of a solution can be thought of as a sequence of steps:\n",
856 | "\n",
857 | "* Compute the free energy of two unmixed systems of pure A and pure B (unmixed)\n",
858 | "* Mix the A and B atoms together\n",
859 | "* Compute the energy change upon mixing\n",
860 | "* Add this change in energy to the energy of the initial state (mixed)"
861 | ]
862 | },
863 | {
864 | "cell_type": "markdown",
865 | "metadata": {
866 | "slideshow": {
867 | "slide_type": "slide"
868 | }
869 | },
870 | "source": [
871 | ""
872 | ]
873 | },
874 | {
875 | "cell_type": "markdown",
876 | "metadata": {
877 | "slideshow": {
878 | "slide_type": "skip"
879 | }
880 | },
881 | "source": [
882 | "To understand the energetics of a non-uniform system we need a model for a solution where the free energy (e.g. Gibbs or Helmholz) is a function of composition. This is most often represented as a free energy density (energy/volume). We will start by describing the ideal solution where the mixing process results in an entropy change alone. In an ideal solution there is no enthalpy of mixing."
883 | ]
884 | },
885 | {
886 | "cell_type": "markdown",
887 | "metadata": {
888 | "slideshow": {
889 | "slide_type": "slide"
890 | }
891 | },
892 | "source": [
893 | "## No Preference for Chemical Surroundings\n",
894 | "----\n",
895 | "\n",
896 | "* In an ideal solution the enthalpy change (or internal energy change) is zero.\n",
897 | "* The entropy arises from mixing effects only.\n",
898 | "* Stirling's Formula is used to approximate terms due to the energy change on mixing:\n",
899 | "\n",
900 | "$$\n",
901 | "\\Delta G_{\\mathrm{mix, \\, id}} = RT(X_A \\ln X_A + X_B \\ln X_B)\n",
902 | "$$"
903 | ]
904 | },
905 | {
906 | "cell_type": "markdown",
907 | "metadata": {
908 | "slideshow": {
909 | "slide_type": "slide"
910 | }
911 | },
912 | "source": [
913 | "The free energy for an ideal solution can therefore be written:\n",
914 | "\n",
915 | "\\begin{align}\n",
916 | "G_{\\mathrm{ideal}} &= G_{\\mathrm{unmixed}} + \\Delta G_{\\mathrm{mix, \\, id}} \\\\ &= X_A G_A + X_B G_B + RT(X_A \\ln X_A + X_B \\ln X_B)\n",
917 | "\\end{align}"
918 | ]
919 | },
920 | {
921 | "cell_type": "code",
922 | "execution_count": null,
923 | "metadata": {
924 | "slideshow": {
925 | "slide_type": "skip"
926 | }
927 | },
928 | "outputs": [],
929 | "source": [
930 | "%matplotlib inline\n",
931 | "import numpy as np\n",
932 | "import matplotlib.pyplot as plt\n",
933 | "from ipywidgets import interact, fixed\n",
934 | "\n",
935 | "def idealSolution(GA, GB, XB, temperature):\n",
936 | " \"\"\"\n",
937 | " Computes the free energy of solution for an ideal binary mixture.\n",
938 | " \n",
939 | " Parameters\n",
940 | " ----------\n",
941 | " GA : float\n",
942 | " The partial molar Gibbs free energy of pure A in Joules.\n",
943 | " GB : float\n",
944 | " The partial molar Gibbs free energy of pure B in Joules.\n",
945 | " XB : ndarray\n",
946 | " The mol fraction of component B as an array.\n",
947 | " temperature : float\n",
948 | " The temperature.\n",
949 | " \n",
950 | " Returns\n",
951 | " -------\n",
952 | " G : ndarray\n",
953 | " An array of the Gibbs free energy having the same shape as `XB`.\n",
954 | " \n",
955 | " Examples\n",
956 | " --------\n",
957 | " >>> XB = np.linspace(0.01,0.99,10)\n",
958 | " >>> G = idealSolution(0.0,0.0,XB,1.0)\n",
959 | " >>> array([ 0.53440324, -3.72037187, -4.76282566, -3.72037187, 0.53440324])\n",
960 | " \"\"\"\n",
961 | " return (1.0-XB)*GA+XB*GB+8.314*temperature*((1-XB)*np.log(1-XB)+XB*np.log(XB))\n",
962 | "\n",
963 | "def myfig(temperature):\n",
964 | " \"\"\"\n",
965 | " This function produces a plot of the Gibbs free energy of mixing for an ideal solution.\n",
966 | " \"\"\"\n",
967 | " GA = 1.0\n",
968 | " GB = 500.0\n",
969 | " \n",
970 | " XB = np.linspace(0.01,0.99,50)\n",
971 | " temperatureSpace = np.linspace(1.0,100.0,10)\n",
972 | " y = idealSolution(GA,GB,XB,temperature)\n",
973 | " greySolutionLines = [idealSolution(GA,GB,XB,greyT) for greyT in temperatureSpace]\n",
974 | " \n",
975 | " fig, axes = plt.subplots(figsize=(10,8))\n",
976 | " \n",
977 | " for greyLine in greySolutionLines:\n",
978 | " axes.plot(XB, greyLine, 'grey', alpha=0.3)\n",
979 | "\n",
980 | " axes.plot(XB, y, 'r', label=r\"$G_A X_A + G_B X_B + RT(X_A \\ln X_A + X_B \\ln X_B)$\")\n",
981 | " axes.legend()\n",
982 | " axes.grid(True, linestyle='dotted')\n",
983 | " axes.set_ylabel(r\"$G_{soln}$\")\n",
984 | " axes.set_xlabel(r\"$X_B$\")\n",
985 | " # Location for annotations can always be done by extents instead of absolute values.\n",
986 | " axes.annotate(r'$G_A$='+str(GA)+'\\n'+r'$G_B$='+str(GB),xy=(0,200), size='large')\n",
987 | " plt.show()\n",
988 | " return"
989 | ]
990 | },
991 | {
992 | "cell_type": "code",
993 | "execution_count": null,
994 | "metadata": {
995 | "slideshow": {
996 | "slide_type": "slide"
997 | }
998 | },
999 | "outputs": [],
1000 | "source": [
1001 | "interact(myfig, temperature=(1.0,100.0,1.0));"
1002 | ]
1003 | },
1004 | {
1005 | "cell_type": "markdown",
1006 | "metadata": {
1007 | "slideshow": {
1008 | "slide_type": "slide"
1009 | }
1010 | },
1011 | "source": [
1012 | "## Correcting the Ideal Solution for Local Chemical Effects\n",
1013 | "----\n",
1014 | "\n",
1015 | "Compared to an ideal solution, the regular solution is a more general thermodynamic treatment. In this case the enthalpy of mixing is nonzero. This results from energy differences arising due to the chemistry of an atom's nearest neighbors.\n",
1016 | "\n",
1017 | "* In general the free energy of solution includes both enthalpic and entropic terms\n",
1018 | "* The previous treatment of the ideal solution neglects any contribution from the enthalpy.\n",
1019 | "* Before mixing - there are only A-A and B-B bonds and NO A-B bonds.\n",
1020 | "* After mixing the number of A-B bonds is estimated from statistical and structural considerations to produce a model of the excess enthalpy"
1021 | ]
1022 | },
1023 | {
1024 | "cell_type": "markdown",
1025 | "metadata": {
1026 | "slideshow": {
1027 | "slide_type": "slide"
1028 | }
1029 | },
1030 | "source": [
1031 | ""
1032 | ]
1033 | },
1034 | {
1035 | "cell_type": "markdown",
1036 | "metadata": {
1037 | "slideshow": {
1038 | "slide_type": "slide"
1039 | }
1040 | },
1041 | "source": [
1042 | "$$\n",
1043 | "\\Delta H_{\\mathrm{mix}} = \\Omega(\\epsilon)X_A X_B\n",
1044 | "$$"
1045 | ]
1046 | },
1047 | {
1048 | "cell_type": "markdown",
1049 | "metadata": {
1050 | "slideshow": {
1051 | "slide_type": "slide"
1052 | }
1053 | },
1054 | "source": [
1055 | "The regular solution model is then writte as:\n",
1056 | "\n",
1057 | "\\begin{align}\n",
1058 | "G_{\\mathrm{regular}} = X_A G_A + X_B G_B &+ \\Omega(\\epsilon)X_A X_B \\\\ &+ RT(X_A \\ln X_A + X_B \\ln X_B)\n",
1059 | "\\end{align}"
1060 | ]
1061 | },
1062 | {
1063 | "cell_type": "code",
1064 | "execution_count": null,
1065 | "metadata": {
1066 | "slideshow": {
1067 | "slide_type": "skip"
1068 | }
1069 | },
1070 | "outputs": [],
1071 | "source": [
1072 | "def regularSolution(GA, GB, XB, omega, temperature):\n",
1073 | " return omega*(1.0-XB)*XB+(1.0-XB)*GA+XB*GB+8.314*temperature*((1.0-XB)*np.log(1.0-XB)+XB*np.log(XB))\n",
1074 | "\n",
1075 | "def myfig2(omega, temperature):\n",
1076 | " \"\"\"\n",
1077 | " This function produces a plot of the Gibbs free energy of mixing for a regular solution.\n",
1078 | " \"\"\"\n",
1079 | " GA = 1.0\n",
1080 | " GB = 1.0\n",
1081 | " \n",
1082 | " XB = np.linspace(0.01,0.99,50)\n",
1083 | " temperatureSpace = np.linspace(1.0,200.0,10)\n",
1084 | " y = regularSolution(GA, GB, XB, omega, temperature)\n",
1085 | " greySolutionLines = [regularSolution(GA, GB, XB, omega, greyT) for greyT in temperatureSpace]\n",
1086 | " \n",
1087 | " fig2, axes2 = plt.subplots(figsize=(14,9))\n",
1088 | " \n",
1089 | " for greyLine in greySolutionLines:\n",
1090 | " axes2.plot(XB, greyLine, 'grey', alpha=0.3)\n",
1091 | "\n",
1092 | " axes2.plot(XB, y, 'r', label=r\"$G_{soln}$\")\n",
1093 | "\n",
1094 | " # Location for annotations can always be done by extents instead of absolute values.\n",
1095 | " axes2.annotate('GA='+str(GA)+'\\n'+'GB='+str(GB),xy=(0,400), fontsize=20)\n",
1096 | " \n",
1097 | " axes2.set_ylabel(r\"$G_{soln}$\", fontsize=15)\n",
1098 | " axes2.set_xlabel(r\"$X_B$\", fontsize=15)\n",
1099 | " axes2.legend(loc=\"upper right\", fontsize=15)\n",
1100 | " axes2.xaxis.set_tick_params(labelsize=15)\n",
1101 | " axes2.yaxis.set_tick_params(labelsize=15)\n",
1102 | " \n",
1103 | " plt.show()\n",
1104 | " return"
1105 | ]
1106 | },
1107 | {
1108 | "cell_type": "code",
1109 | "execution_count": null,
1110 | "metadata": {
1111 | "slideshow": {
1112 | "slide_type": "slide"
1113 | }
1114 | },
1115 | "outputs": [],
1116 | "source": [
1117 | "interact(myfig2, omega=(0.0,5000.0,1.0), temperature=(1.0,200.0,1.0));"
1118 | ]
1119 | },
1120 | {
1121 | "cell_type": "markdown",
1122 | "metadata": {
1123 | "slideshow": {
1124 | "slide_type": "slide"
1125 | }
1126 | },
1127 | "source": [
1128 | "## The PDE to Determine Profiles and Kinetic Evolution of a Non-Uniform System\n",
1129 | "----\n",
1130 | "To approximate the regular solution energetics the first term following function is used rather than the regular solution model above. The functional contains the (approximate) bulk free energy and a gradient energy term:\n",
1131 | "\n",
1132 | "$$\n",
1133 | "F(\\phi,\\phi') = W \\phi^2 (1-\\phi)^2 + \\epsilon (\\nabla \\phi)^2\n",
1134 | "$$\n",
1135 | "\n",
1136 | "Applying the Euler-Lagrange equation to our functional we get:\n",
1137 | "\n",
1138 | "\\begin{align}\n",
1139 | "\\frac{\\delta F}{\\delta \\phi} & = \\frac{\\partial F}{\\partial \\phi} - \\frac{d}{dx} \\frac{\\partial F}{\\partial \\nabla \\phi} = 0 \\\\ &= 2 W \\phi \\left(\\phi - 1\\right) \\left(2 \\phi - 1\\right) - 2 \\epsilon \\nabla^2 \\phi = 0\n",
1140 | "\\end{align}\n",
1141 | "\n",
1142 | "recall that $\\phi(x,t)$ and this equation implies equilibrium."
1143 | ]
1144 | },
1145 | {
1146 | "cell_type": "markdown",
1147 | "metadata": {
1148 | "slideshow": {
1149 | "slide_type": "skip"
1150 | }
1151 | },
1152 | "source": [
1153 | "## Quick Aside on Writing the E.O.M.\n",
1154 | "----\n",
1155 | "\n",
1156 | "When writing the equations of motion - things can get messy. It is better therefore to write the leading term on the LHS as $A(\\phi)$. This gives:\n",
1157 | "\n",
1158 | "$$\n",
1159 | "\\frac{\\delta F}{\\delta \\phi} = A(\\phi) - \\epsilon \\frac{d^{2}}{d x^{2}} \\phi{\\left (x \\right )}\n",
1160 | "$$\n",
1161 | "\n",
1162 | "\n",
1163 | "and for a conserved order parameter, we write:\n",
1164 | "\n",
1165 | "$$\n",
1166 | "\\nabla \\cdot D \\nabla \\frac{\\delta F}{\\delta \\phi} = \\nabla \\cdot D \\left( \\frac{\\partial A}{\\partial \\phi} \\nabla \\phi(x) - \\epsilon \\frac{d^{3}}{d x^{3}} \\phi(x) \\right)\n",
1167 | "$$\n",
1168 | "\n",
1169 | "By distributing the divergence and diffusion coefficient, we arrive at:\n",
1170 | "\n",
1171 | "$$\n",
1172 | "\\frac{\\partial \\phi}{\\partial t} = \\nabla \\cdot D \\frac{\\partial A}{\\partial \\phi} \\nabla \\phi(x) - D \\epsilon \\nabla^4 \\phi(x)\n",
1173 | "$$\n",
1174 | "\n",
1175 | "This can be solved with standard numerical techniques. The example below is for a CONSERVED order parameter and is taken directly from FiPy's examples in the user documentation."
1176 | ]
1177 | },
1178 | {
1179 | "cell_type": "code",
1180 | "execution_count": null,
1181 | "metadata": {
1182 | "slideshow": {
1183 | "slide_type": "skip"
1184 | }
1185 | },
1186 | "outputs": [],
1187 | "source": [
1188 | "%matplotlib notebook\n",
1189 | "from IPython.display import clear_output\n",
1190 | "from fipy import *\n",
1191 | "nx = ny = 100\n",
1192 | "\n",
1193 | "mesh = Grid2D(nx=nx, ny=ny, dx=0.5, dy=0.5)\n",
1194 | "phi = CellVariable(name=r\"$\\phi$\", mesh=mesh)\n",
1195 | "psi = CellVariable(name=r\"$\\psi$\", mesh=mesh)\n",
1196 | "\n",
1197 | "noise = GaussianNoiseVariable(mesh=mesh,mean=0.5,variance=0.01).value\n",
1198 | "phi[:] = noise\n",
1199 | "\n",
1200 | "viewer = Viewer(vars=phi)#, datamin=-0.1, datamax=1.1)\n",
1201 | "\n",
1202 | "D = a = epsilon = 1.\n",
1203 | "dfdphi = a**2 * 2 * phi * (1 - phi) * (1 - 2 * phi)\n",
1204 | "dfdphi_ = a**2 * 2 * (1 - phi) * (1 - 2 * phi)\n",
1205 | "d2fdphi2 = a**2 * 2 * (1 - 6 * phi * (1 - phi))\n",
1206 | "eq1 = (TransientTerm(var=phi) == DiffusionTerm(coeff=D, var=psi))\n",
1207 | "eq2 = (ImplicitSourceTerm(coeff=1., var=psi) \n",
1208 | " == ImplicitSourceTerm(coeff=-d2fdphi2, var=phi) - d2fdphi2 * phi + dfdphi \n",
1209 | " - DiffusionTerm(coeff=epsilon**2, var=phi))\n",
1210 | "eq3 = (ImplicitSourceTerm(coeff=1., var=psi) \n",
1211 | " == ImplicitSourceTerm(coeff=dfdphi_, var=phi)\n",
1212 | " - DiffusionTerm(coeff=epsilon**2, var=phi))\n",
1213 | "\n",
1214 | "eq = eq1 & eq3\n",
1215 | "\n",
1216 | "dexp = -3\n",
1217 | "elapsed = 0.\n",
1218 | "duration = 100.0"
1219 | ]
1220 | },
1221 | {
1222 | "cell_type": "code",
1223 | "execution_count": null,
1224 | "metadata": {
1225 | "slideshow": {
1226 | "slide_type": "slide"
1227 | }
1228 | },
1229 | "outputs": [],
1230 | "source": [
1231 | "# Run the model.\n",
1232 | "while elapsed < duration:\n",
1233 | " dt = min(100, numerix.exp(dexp))\n",
1234 | " elapsed += dt\n",
1235 | " dexp += 0.01\n",
1236 | " eq.solve(dt=dt)\n",
1237 | " viewer.plot()\n",
1238 | " clear_output(wait=True)\n",
1239 | " display(viewer)"
1240 | ]
1241 | },
1242 | {
1243 | "cell_type": "markdown",
1244 | "metadata": {
1245 | "slideshow": {
1246 | "slide_type": "slide"
1247 | }
1248 | },
1249 | "source": [
1250 | "## Snapshots from the Spinodal Decomposition (Conserved) Simulation\n",
1251 | "----\n",
1252 | "\n",
1253 | ""
1254 | ]
1255 | },
1256 | {
1257 | "cell_type": "markdown",
1258 | "metadata": {
1259 | "slideshow": {
1260 | "slide_type": "slide"
1261 | }
1262 | },
1263 | "source": [
1264 | "## Animation of the Simulation\n",
1265 | "---"
1266 | ]
1267 | },
1268 | {
1269 | "cell_type": "code",
1270 | "execution_count": null,
1271 | "metadata": {
1272 | "slideshow": {
1273 | "slide_type": "fragment"
1274 | }
1275 | },
1276 | "outputs": [],
1277 | "source": [
1278 | "%%HTML\n",
1279 | ""
1280 | ]
1281 | },
1282 | {
1283 | "cell_type": "markdown",
1284 | "metadata": {
1285 | "slideshow": {
1286 | "slide_type": "slide"
1287 | }
1288 | },
1289 | "source": [
1290 | "## Example 3: Multi Order Parameter Phase Field\n",
1291 | "----\n",
1292 | "\n",
1293 | "Multi order parameter models include one phase field for every phase (or grain) in the system. The major concern with these models relates to the energy landscape and the sense of curvature at intermediate states.\n",
1294 | "\n",
1295 | "Looking carefully - all the components outlined in previous models are here, too.\n",
1296 | "\n",
1297 | "These models created the possibility of multiphase growth and are essential for predicting both equilibrium and out-of-equilibrium phase formation in problems like additive manufacturing."
1298 | ]
1299 | },
1300 | {
1301 | "cell_type": "markdown",
1302 | "metadata": {
1303 | "slideshow": {
1304 | "slide_type": "slide"
1305 | }
1306 | },
1307 | "source": [
1308 | "The bulk free energy of our system consists of a bulk and gradient energy as before:\n",
1309 | "\n",
1310 | "$$F = \\int_V \\left[ w g(\\phi) + \\frac{\\epsilon^2}{2} \\sum_{i=1} (\\nabla \\phi_i)^2 \\right] dV$$\n",
1311 | "\n",
1312 | "where $g(\\phi)$ is the homogeneous free energy landscape connecting ALL phase field variables and is given by:\n",
1313 | "\n",
1314 | "$$g(\\phi) = \\frac{1}{12} + \\sum_{i=1}^N \\left[ \\left( \\frac{\\phi_i^4}{4} - \\frac{\\phi_i^3}{3} \\right) + \\frac{1}{2} \\sum_{i