# Find distribution of local maxima in a Gaussian Random Field

In this notebook, I evaluate different known distributions for local maxima in a Gaussian Random Field.  I followed several steps to (a) look at the distribution through simulations, (b) apply different possible approaches to find the distribution.

It seems that none of these are applicable for local maxima without applying a filtering threshold.

# 1. Simulate random fields (without activation) and extract local maxima

In a first step, I
- Simulate a guassian random field using nipype.  This field is huge (500x500x500) and therefore memory-intensive to ensure we have lots of local maxima.
- Look at the GRF.
- Export the GRF to a nifti file.  Before saving the data, I make sure all values are positive, because step (d) only extracts local maxima that are above 0.
- Extract all local maxima using nipype and fsl cluster.
- Look at the table of local maxima and print the total number of peaks.
- Look at the distribution of these maxima.

#### Import libraries

```python
% matplotlib inline
import os
import numpy as np
import nibabel as nib
from nipy.labs.utils.simul_multisubject_fmri_dataset import surrogate_3d_dataset
import nipy.algorithms.statistics.rft as rft
from __future__ import print_function, division
import math
import matplotlib.pyplot as plt
import palettable.colorbrewer as cb
from nipype.interfaces import fsl
import pandas as pd
import nipy.algorithms.statistics.intvol as intvol
from matplotlib import colors
import scipy.stats as stats

```

#### Simulate very large RF

```python
smooth_FWHM = 3
smooth_sd = smooth_FWHM/(2*math.sqrt(2*math.log(2)))
data = surrogate_3d_dataset(n_subj=1,sk=smooth_sd,shape=(500,500,500),noise_level=1)
```

#### Show part of the RF (20x20x1)

```python
plt.figure(figsize=(6,4))
plt.imshow(data[1:20,1:20,1])
plt.colorbar()
plt.show()
```

#### Save RF

```python
minimum = data.min()
newdata = data - minimum #little trick because fsl.model.Cluster ignores negative values
img=nib.Nifti1Image(newdata,np.eye(4))
img.to_filename("files/RF.nii.gz")
```

#### Run fsl cluster to extract local maxima

```python
cl=fsl.model.Cluster()
cl.inputs.threshold = 0
cl.inputs.in_file="files/RF.nii.gz"
cl.inputs.out_localmax_txt_file="files/locmax.txt"
cl.inputs.num_maxima=1000000
cl.inputs.connectivity=26
cl.inputs.terminal_output='none'
cl.run()
```

    <nipype.interfaces.base.InterfaceResult at 0x10deddc90>

#### Read and print top of file with peaks ,  print total number of peaks.

```python
peaks = pd.read_csv("files/locmax.txt",sep="\t").drop('Unnamed: 5',1)
peaks.Value = peaks.Value + minimum
peaks[:5]
```

      Cluster Index
      Value
      x
      y
      z

      0
       1
       6.668672
        66
         0
       243

      1
       1
       6.468672
       468
         0
       255

      2
       1
       6.268672
       227
       499
       401

      3
       1
       6.168672
       291
       499
       191

      4
       1
       6.168672
       312
       499
       330

```python
len(peaks)
```

    615036

#### Plot histogram local maxima

```python
col=cb.qualitative.Set1_8.mpl_colors
plt.figure(figsize=(6,3))
ax=plt.subplot(111)
ax.hist(peaks.Value,40,normed=1,facecolor=col[0],alpha=0.75,lw=0)
ax.set_xlim([-1,5])
plt.show()
```

# 2. First approach: analytically derived distribution of local maxima above u

#a. (Cheng & Schwartzman, 2015)
#b. RFT

[Cheng and Schwartzman](https://www.dropbox.com/s/goo6bj5vw82gd8b/Cheng%2C%20Schwartzman%20-%202015%20-%20Extremes%20-%20Distribution%20of%20the%20height%20of%20local%20maxima%20of%20Gaussian%20random%20fields.pdf?dl=0) recently published a paper in which they derive a distribution of local maxima over a certain threshold.  They make (like RFT) the assumption that the field is a GRF whose interior is non-empty... A consequence is that we can only compute this when the threshold is high enough to ensure there are only blobs and no holes.  We'll take a look how the theoretical distribution performs for lower thresholds.  This is their derived distribution:

For all local maxima above threshold $v$, extending $u$ above this threshold,

For each $t_0 \in T$ and each fixed $u>0$, as $v \rightarrow \infty$,

\begin{equation*}
F_t(u,v) = \frac{(u+v)^{N-1}e^{-(u+v)^2/2}}{v^{N-1}e^{-v^2/2}}
\end{equation*}

Below you can see that the approximation indeed only works well on very high thresholds, and therefore cannot be used to obtain the full distribution of all peaks.

We also compare with the random field theory approximation, with u the threshold:

\begin{equation}
F_t(u,t_0) = u e^{-u(t_0 - u)}
\end{equation}

#### Function for pdf of peaks given a certain threshold

```python
def nulprobdens(exc,peaks):
	v = exc
	u = peaks - v
	f0 = (2+(u+v)**2)*(u+v)*np.exp(-(u+v)**2/2)/(v**2*np.exp(-v**2/2))
	return f0
```

#### Function for pdf of peaks given a certain threshold, RFT

```python
def nulprobdensRFT(exc,peaks):
    f0 = exc*np.exp(-exc*(peaks-exc))
    return f0
```

#### Compute density function over a range with different excursion thresholds

```python
fig,axs=plt.subplots(1,5,figsize=(13,3))
fig.subplots_adjust(hspace = .5, wspace=0.3)
axs=axs.ravel()
thresholds=[2,2.5,3,3.5,4]
bins=np.arange(2,5,0.5)
x=np.arange(2,10,0.0001)

twocol=cb.qualitative.Paired_10.mpl_colors
for i in range(5):
    thr=thresholds[i]
    axs[i].hist(peaks.Value[peaks.Value>thr],lw=0,facecolor=twocol[i*2-2],normed=True,bins=np.arange(thr,5,0.1))
    axs[i].set_xlim([thr,5])
    axs[i].set_ylim([0,3])
    xn = x[x>thr]
    yn = nulprobdens(thr,xn)
    ynb = nulprobdensRFT(thr,xn)
    axs[i].plot(xn,yn,color=twocol[i*2-1],lw=3,label="C&S")
    axs[i].plot(xn,ynb,color=twocol[i*2-1],lw=3,linestyle="--",label="RFT")
    axs[i].set_title("threshold:"+str(thr))
    axs[i].set_xticks(np.arange(thr,5,0.5))
    axs[i].set_yticks([1,2])
    axs[i].legend(loc="upper right",frameon=False)
    axs[i].set_xlabel("peak height")
    axs[i].set_ylabel("density")
plt.show()
```

In this figure, we see the observed tail distribution of the local maxima (light colored) in our simulated data.  The thick line represents the theoretical distribution of the local maxima above a certain threshold. It is only a good approximation for really high thresholds. RFT works better for lower thresholds.

# 3. Second approach: what can we use the Euler Characteristic for?

The [Euler Characteristic](https://en.wikipedia.org/wiki/Euler_characteristic) is a topological invariant that represents for a certain threshold applied to a certain random field \#peaks - \#holes.  Therefore, again, if the threshold is high enough (there are no holes), it computes the number of peaks.

If we compute the EC for a range of thresholds (above a certain threshold), we can construct a pdf of peak heights.  It should be noted that the number of peaks is dependent of the smoothness, but this dependency can be taken out by dividing the smoothness by the total volume as a correcting factor.

This principle has led to a theoretical approximation of the pdf of peaks, which is closely related to the approach of Cheng \& Schwartzmann.

#### Let's first look at the masks that result from our random field with certain thresholds

```python
fig,axs=plt.subplots(1,4,figsize=(13,7))
fig.subplots_adjust(hspace = .1, wspace=0.1)
axs=axs.ravel()
thresholds=np.arange(0,4,1)
cmap = colors.ListedColormap(['white', 'black'])
bounds=[0,0.5,1]
norm = colors.BoundaryNorm(bounds, cmap.N)

for t in range(len(thresholds)):
    mask = np.zeros(shape=data.shape,dtype=np.intp)
    mask[data>thresholds[t]]=1
    axs[t].imshow(mask[1:200,1:200,20],cmap=cmap,norm=norm)
    axs[t].set_title("threshold:"+str(thresholds[t]))
    axs[t].patch.set_visible(False)
    axs[t].axis('off')

```

#### Now we'll look at the number of peaks and the Euler Characteristic against the threshold.

```python
EulerDens = []
EulerDensInv = []
urange = np.arange(-4,4,0.3)
for t in urange:
    mask = np.zeros(shape=data.shape,dtype=np.intp)
    mask[data>t]=1
    EulerDens.append(intvol.EC3d(mask))
    mask2 = 1-mask
    EulerDensInv.append(intvol.EC3d(mask2))

```

```python
sumpeak = []
for t in urange:
    sumpeak.append(sum(peaks.Value>t))

```

```python
plt.figure(figsize=(7,5))
plt.plot(urange,EulerDens,color=col[1],lw=3,label="observed Euler Characteristic")
plt.plot(urange,EulerDensInv,color=col[2],lw=3,label="observed inverse Euler Characteristic")
plt.plot(urange,sumpeak,color=col[3],lw=3,label="Number of peaks")
plt.legend(loc="upper right",frameon=False)
plt.ylim([-600000,1200000])
plt.show()
```

In this plot we can indeed see that the Euler Characteristic gives the number of peaks, but only above a certain threshold that is high enough.  Below these higher thresholds, is gives \# peaks - \# holes.  Is there a way to estimate the number of holes in the presence of peaks using the EC?  I don't think so, it's the exact same problem as the number of paeks in the presence of holes?  Therefore the Euler Characteristic cannot give us information for lower thresholds.

# 4. Third approach: peaks as the maximum of something of which we know the distribution?

The above procedures use the fact that these fields are (should be) continuous fields.  However, what comes out of the scanner is **not** continuous at all.  We make it __more__ continuous by applying a smoothing kernel, which allows to use these processes.  However, I wonder, is it possible to look at the peaks simply as the maximum of a sample of a known distribution?

Can we look at the random fields as multivariate normal with a correlation structure dependent on the smoothness?

Here the smoothness comes into play!  How does smoothness affect the distribution of the peaks?

#### First simulate random fields for different smoothnesses.

```python
smoothnesses = [0,3,6,9]
minima = []
for sm in range(len(smoothnesses)):
    smooth_FWHM = smoothnesses[sm]
    smooth_sd = smooth_FWHM/(2*math.sqrt(2*math.log(2)))
    data = surrogate_3d_dataset(n_subj=1,sk=smooth_sd,shape=(500,500,500),noise_level=1)
    minimum = data.min()
    newdata = data - minimum #little trick because fsl.model.Cluster ignores negative values
    minima.append(minimum)
    img=nib.Nifti1Image(newdata,np.eye(4))
    img.to_filename(os.path.join("files/RF_"+str(sm)+".nii.gz"))
    cl=fsl.model.Cluster()
    cl.inputs.threshold = 0
    cl.inputs.in_file=os.path.join("files/RF_"+str(sm)+".nii.gz")
    cl.inputs.out_localmax_txt_file=os.path.join("files/locmax_"+str(sm)+".txt")
    cl.inputs.num_maxima=10000000
    cl.inputs.connectivity=26
    cl.inputs.terminal_output='none'
    cl.run()

```

#### Look at the distribution of the voxels?

We simulated the random fields to be a GRF, from a normal distribution.  Here you can see that indeed the distribution is normal independent from the smoothness.

```python
col=cb.qualitative.Set1_8.mpl_colors+cb.qualitative.Set2_8.mpl_colors
plt.figure(figsize=(10,5))
ax=plt.subplot(111)
for sm in range(len(smoothnesses)):
    file = os.path.join("files/RF_"+str(sm)+".nii.gz")
    tvals = nib.load(file).get_data().astype('float64')+minima[sm]
    values, base = np.histogram(tvals,100,normed=1)
    ax.plot(base[:-1],values,label="smoothness: "+str(smoothnesses[sm]),color=col[sm],lw=2)
ax.set_xlim([-4,4])
ax.set_ylim([0,0.5])
ax.legend(loc="lower right",frameon=False)
ax.set_title("distribution of peak heights for different smoothing kernels (FWHM)")
plt.show()
```

#### So how about the distribution of the maximum of a sample of these distributions?

```python
all = []
for sm in range(len(smoothnesses)):
    peaks = pd.read_csv(os.path.join("files/locmax_"+str(sm)+".txt"),sep="\t").drop('Unnamed: 5',1).Value
    peaks = peaks + minima[sm]
    all.append(peaks)

```

```python
col=cb.qualitative.Set1_8.mpl_colors+cb.qualitative.Set2_8.mpl_colors
plt.figure(figsize=(10,5))
ax=plt.subplot(111)
for sm in range(len(smoothnesses)):
    values, base = np.histogram(all[sm],30,normed=1)
    ax.plot(base[:-1],values,label="smoothness: "+str(smoothnesses[sm]),color=col[sm],lw=2)
ax.set_xlim([-1,5])
ax.set_ylim([0,1.2])
ax.legend(loc="lower right",frameon=False)
ax.set_title("distribution of peak heights for different smoothing kernels (FWHM)")
plt.show()

```

As expected, from a certain smoothness (3 x voxel size), the distribution remains the same.

## First: the distribution without smoothness!

The RF with smoothness 0 should be just normally distributed values without correlation.  The peaks with smoothness 0 should be just maxima of a sample of normally distributed values.  We know the distribution of a sample with normally distribution values from order statistics:

$f_{max}(x) = nF(x)^{n-1}f(x)$

#### This is to show that the distribution of the maximum of the sample is the right one.
* Simulate n normally distributed values
* Take the maximum
* Compute for a range of values the distribution of the maximum
* Show histogram of simulated maxima with the pdf on top

```python
# random sample

smplm = []

for i in range(100000):
    smpl = np.random.standard_normal((n,))
    smplm.append(max(smpl))

# distribution

xm = np.arange(-1,5,0.001)
ym = n*stats.norm.cdf(xm)**(n-1)*stats.norm.pdf(xm)

# histogram

twocol=cb.qualitative.Paired_10.mpl_colors
plt.figure(figsize=(6,3))
ax=plt.subplot(111)
ax.hist(smplm,100,normed=1,facecolor=twocol[0],alpha=0.75,lw=0)
ax.plot(xm,ym,color=twocol[1],lw=3)
ax.set_xlim([-1,5])
plt.show()
```

#### Now can we just state that the peaks in our unsmoothed random field is the maxima of a sample?

First: the sample size is variable!  Let's try it on the average sample size, i.e. the average number of voxels per peak.

```python
n = (500**3)/len(all[1])
n
```

    203.16118808662793

#### Show histogram of peaks of uncorrected field with the distribution of maximum of a sample of size n.

```python
# distribution of a maximum

xm = np.arange(-1,5,0.001)
ym = n*stats.norm.cdf(xm)**(n-1)*stats.norm.pdf(xm)

# histogram

twocol=cb.qualitative.Paired_10.mpl_colors
plt.figure(figsize=(6,3))
ax=plt.subplot(111)
ax.hist(all[1],100,normed=1,facecolor=twocol[0],alpha=0.75,lw=0)
ax.plot(xm,ym,color=twocol[1],lw=3)
ax.set_xlim([-1,5])
plt.show()
```

# Ok, I'm stuck.

The stuff below is the maximum of a sample with correlation.  We have the distribution, but if it doesn't work for uncorrelated values, it's not going to work for correlated values.  So I leave it here but it's non-informative.

```python
# random sample

smplmc = []
n = 2
mean = [0,0]
r = 0.2
cov = [[1,r],[r,1]]

for i in range(100000):
    smpl = np.random.multivariate_normal(mean,cov,int(n/n))
    smplmc.append(np.max(smpl))

# distribution

xmc = np.arange(-2,3,0.001)
corf = (1-r)/np.sqrt(1-r**2)
ymc = n*stats.norm.cdf(corf*xmc)**(n-1)*stats.norm.pdf(xmc)

# histogram

twocol=cb.qualitative.Paired_10.mpl_colors
plt.figure(figsize=(6,3))
ax=plt.subplot(111)
ax.hist(smplmc,100,normed=1,facecolor=twocol[2],alpha=0.75,lw=0)
ax.plot(xmc,ymc,color=twocol[3],lw=3)
ax.set_xlim([-1,5])
plt.show()

```

```python
# random sample

smplmc = []
n = 10
mean = np.array([0,0,0,0,0,0,0,0,0,0])
r = 0.5
cov = np.array([[1,r,r,r,r,r,r,r,r,r],
       [r,1,r,r,r,r,r,r,r,r],
       [r,r,1,r,r,r,r,r,r,r],
       [r,r,r,1,r,r,r,r,r,r],
       [r,r,r,r,1,r,r,r,r,r],
       [r,r,r,r,r,1,r,r,r,r],
       [r,r,r,r,r,r,1,r,r,r],
       [r,r,r,r,r,r,r,1,r,r],
       [r,r,r,r,r,r,r,r,1,r],
       [r,r,r,r,r,r,r,r,r,1]
      ])

for i in range(100):
    smpl = np.random.multivariate_normal(mean,cov,int(n/n))
    smplmc.append(np.max(smpl))

# distribution (just max of gaussian normal)

xm = np.arange(-1,5,0.001)
corf = (1-r)/np.sqrt(1-r**2)
ym = n*stats.norm.cdf(xm)**(n-1)*stats.norm.pdf(xm)

# histogram

twocol=cb.qualitative.Paired_10.mpl_colors
plt.figure(figsize=(6,3))
ax=plt.subplot(111)
ax.hist(smplm,100,normed=1,facecolor=twocol[0],alpha=0.75,lw=0)
ax.plot(xm,ym,color=twocol[1],lw=3)
ax.set_xlim([-1,5])
plt.show()
```

```python
newline
```

```python

```


# Introduction

This book aims to introduce readers to programming for mathematics.

It is assumed that readers are used to solving high school mathematics problems
of the form:

---

```{admonition} Problem
Given the function $f:\mathbb{R}\to\mathbb{R}$ defined by
$f(x) = x ^ 2 - 3 x + 1$ obtain the global minima of the function.
```

```{admonition} Solution
:class: tip

To solve this we need to apply our **mathematical knowledge** which tells us to:

1. Differentiate $f(x)$ to get $\frac{df}{dx}$;
2. Equate $\frac{df}{dx}=0$;
3. Use the second derivative test on the solution to the previous equation.

For each of those 3 steps we will usually make use of our **mathematical
techniques**:

1. Differentiate $f(x)$:

   $$\frac{df}{dx} = 2 x - 3$$

2. Equate $\frac{df}{dx}=0$:

   $$2x-3 =0 \Rightarrow x = 3/2$$

3. Use the second derivative test on the solution:

   $$\frac{d^2f}{dx^2} = 2 > 0\text{ for all values of }x$$

   Thus $x=3/2$ is the global minima of the function.
```

```{attention}
As we progress as mathematicians **mathematical knowledge** is more prominent
than **mathematical technique**: often knowing what to do is the real problem as
opposed to having the technical ability to do it.
```

This is what this book will cover: **programming** allows us to instruct a
computer to carry out mathematical techniques.

We will for example learn how to solve the above problem by instructing a
computer which **mathematical technique** to carry out.

**This book will teach us how to give the correct instructions to a
computer.**

The following is an example, do not worry too much about the specific code used
for now:

## Differentiate $f(x)$ to get $\frac{df}{dx}$

```python
import sympy as sym

x = sym.Symbol("x")
sym.diff(x ** 2 - 3 * x + 1, x)
```

$\displaystyle 2 x - 3$

## Equate $\frac{df}{dx}=0$

```python
sym.solveset(2 * x - 3, x)
```

$\displaystyle \left\{\frac{3}{2}\right\}$

## Use the second derivative test on the solution

```python
sym.diff(x ** 2 - 3 * x + 1, x, 2)
```

$\displaystyle 2$

{ref}`Knowledge versus technique <fig:knowledge_vs_technique>` is a brief summary.

```{figure} ./img/knowledge_vs_technique/main.png
---
width: 50%
name: fig:knowledge_vs_technique
---
Knowledge versus technique in this book.
```


# Teorema de Norton

Jupyter Notebook desenvolvido por [Gustavo S.S.](https://github.com/GSimas)

**O teorema de Norton afirma que um circuito linear de dois terminais pode
ser substituído por um circuito equivalente formado por uma fonte de
corrente IN em paralelo com um resistor RN, em que IN é a corrente de curto-
-circuito através dos terminais e RN é a resistência de entrada ou equivalente
nos terminais quando as fontes independentes forem desligadas.**

\begin{align}
{\Large R_{Th} = R_N}
\end{align}

Para descobrir a corrente IN de Norton, determinamos a corrente de curto--circuito que flui entre os terminais a e b em ambos os circuitos da Figura 4.37.
Os circuitos equivalentes
de Thévenin e de Norton
estão relacionados por uma
transformação de fontes.

\begin{align}
{\Large I_N = \frac{V_{Th}}{R_{Th}}}
\end{align}

**Exemplo 4.11**

Determine o circuito equivalente de Norton do circuito da Figura 4.39 nos terminais a-b

```python
print("Exemplo 4.11")

#Superposicao

#Analise Fonte de Tensao
#Req1 = 4 + 8 + 8 = 20
#i1 = 12/20 = 3/5 A

#Analise Fonte de Corrente
#i2 = 2*4/(4 + 8 + 8) = 8/20 = 2/5 A

#in = i1 + i2 = 1A

In = 1

#Req2 = paralelo entre Req 1 e 5
#20*5/(20 + 5) = 100/25 = 4

Rn = 4

print("Corrente In:",In,"A")
print("Resistência Rn:",Rn)
```

    Exemplo 4.11
    Corrente In: 1 A
    Resistência Rn: 4

**Problema Prático 4.11**

Determine o equivalente de Norton para o circuito da Figura 4.42 nos terminais a-b.

```python
print("Problema Prático 4.11")

#Analise Vs
#i1 = 15/(3 + 3) = 15/6 A

#Analise Cs
#i2 = 4*3/(3 + 3) = 2 A

#in = i1 + i2 = 15/6 + 2 = 27/6 = 4.5

In = 4.5

#Rn = 6*6/(6 + 6) = 3
Rn = 3

print("Corrente In:",In,"A")
print("Resistência Rn:",Rn)
```

    Problema Prático 4.11
    Corrente In: 4.5 A
    Resistência Rn: 3

**Exemplo 4.12**

Usando o teorema de Norton, determine RN e IN do circuito da Figura 4.43 nos terminais
a-b.

```python
print("Exemplo 4.12")

#Aplica-se tensao Vo = 1V entre os terminais a-b
#Assim Rth = Rn = 5
Rn = 5

#Analise Nodal
#ix = 10/4 = 2.5 A
#i1 = 10/5 = 2 A
#in = 2ix + i1 = 5 + 2 = 7 A
In = 7
print("Corrente In:",In,"A")
print("Resistência Rn:",Rn)
```

    Exemplo 4.12
    Corrente In: 7 A
    Resistência Rn: 5

**Problema Prático 4.12**

Determine o circuito equivalente de Norton do circuito da Figura 4.45 nos terminais a-b.

```python
print("Problema Prático 4.12")

#Aplica-se Vo = 2V entre os terminais a-b
#Assim Vx = 2V

#Analise Nodal
#V1 = tensao sobre resistor 6 = 3Vx = 6V
#i1 = 1 A
#i2 = 2/2 = 1 A
#io = i1 + i2 = 2 A
#Rn = Vo/io = 2/2 = 1
Rn = 1

#In = 10 = corrente de curto circuito
In = 10

print("Resistência Rn:",Rn)
print("Corrente In:",In,"A")
```

    Problema Prático 4.12
    Resistência Rn: 1
    Corrente In: 10 A

## Máxima Transferência de Potência

Para um dado circuito, VTh e RTh são fixas. Variando a resistência de carga RL, a
potência liberada à carga varia conforme descrito na Figura 4.49. Percebemos,
dessa figura, que a potência é pequena para valores pequenos ou grandes de RL,
mas máxima para o mesmo valor de RL entre 0 e ∞.

\begin{align}
{\Large P = i^2R_L = (\frac{V_{Th}}{R_{Th} + R_L})^2R_L}
\end{align}

**A potência máxima é transferida a uma carga quando a resistência de carga
for igual à resistência de Thévenin quando vista da carga (RL = RTh).**

A fonte e a carga são ditas casadas
quando RL = RTh. Assim, a potência máxima para elementos casados é:

\begin{align}
{\Large P_{max} = \frac{V_{Th}^2}{4R_{Th}}}
\end{align}

**Exemplo 4.13**

Determine o valor de RL para a máxima transferência de potência no circuito da Figura
4.50. Determine a potência máxima.

```python
print("Exemplo 4.13")

#Req1 = 6*12/(6 + 12) = 4
#Rn = 4 + 3 + 2 = 9
Rn = 9

#Superposicao
#Fonte de Corrente
#i1 = 2*7/(7 + 2) = 14/9

#Fonte de Tensao
#Req2 = 12*5/(12 + 5) = 60/17
#Req3 = 6 + 60/17 = 162/17
#it = 12/(162/17) = 12*17/162
#i2 = it*12/(12 + 5) = 8/9

#in = i1 + i2 = 14/9 + 8/9 = 22/9
In = 22/9

P = (Rn/4)*In**2

print("Corrente In:",In,"A")
print("Potência Máxima Transferida:",P,"W")
```

    Exemplo 4.13
    Corrente In: 2.4444444444444446 A
    Potência Máxima Transferida: 13.444444444444446 W

**Problema Prático 4.13**

Determine o valor de RL que irá drenar a potência máxima do restante do circuito na
Figura 4.52. Calcule a potência máxima.

```python
print("Problema Prático 4.13")

import numpy as np

#Analise In
#vx = 2i1

#vx + (i1 - i2) + 3vx = 9
    #i1 - i2 + 4vx = 9
    #9i1 - i2 = 9

#(i2 - i1) + 4i2 = 3vx
    #-i1 + 5i2 = 6i1
    #-7i1 + 5i2 = 0

coef = np.matrix("9 -1;-7 5")
res = np.matrix("9;0")
I = np.linalg.inv(coef)*res
In = -I[1]

#Analise Rn
#io = 1 A

#vx = 2i1

#vx + (i1 + io) + 3vx = 0
    #i1 + 4vx = -1
    #i1 + 8i1 = -1
    #i1 = -1/9

#vx = -2/9

#Vab = 4io + (io + i1) + 3vx
    #Vab = 4 + 1 -1/9 -6/9 = 38/9

#Rn = Vab/io = 38/9
Rn = 38/9

P = (Rn/4) * In**2

print("Resistencia Rl para potência Maxima:",Rn)
print("Potencia maxima:",float(P),"W")
```

    Problema Prático 4.13
    Resistencia Rl para potência Maxima: 4.222222222222222
    Potencia maxima: 2.9013157894736836 W

## Resumo

1. Uma **rede linear** é formada por elementos lineares, fontes dependentes lineares e fontes independentes lineares.

2. O princípio da **superposição** afirma que para um circuito com várias fontes independentes, a tensão (ou a corrente) em um elemento é igual à soma algébrica de todas as tensões (ou correntes) individuais devido a cada fonte independente atuando em dado instante.

3. **Transformação de fontes** é um procedimento para transformar uma fonte de tensão em série com um resistor em uma fonte de corrente em paralelo com um resistor, ou vice-versa.

4. O circuito equivalente de **Thévenin** é formado por uma fonte de tensão VTh em série com um resistor RTh, enquanto o de **Norton** é constituído por uma fonte de corrente IN em paralelo com um resistor RN.

\begin{align}
{\Large R_n = R_{Th}}
\\
\\{\Large I_n = \frac{V_{Th}}{R_{Th}}}
\end{align}

5. O circuito equivalente de Thévenin é formado por uma fonte de tensão VTh em série com um resistor RTh, enquanto o de Norton é constituído por uma fonte de corrente IN em paralelo com um resistor RN.

\begin{align}
{\Large P_{max} = \frac{V_{Th}^2}{4R_{Th}}}
\end{align}


# Lecture 23:  The Equilbrium and Kinetic Properties of a Non-Uniform System

## Reading and Reference

* Free Energy of a Nonuniform System. I. Interfacial Free Energy, Journal of chemical Physics, v28, n2, p258-267 (1958)
* Essential Mathematical Methods for Physicists, H. Weber and G. Arfken, Academic Press, 2003 (Chapter 5)
* Calculus of Variations, L. Elsgolc, Dover Publications, 2007 (Chapters 1 and 2)
* Thermodynamics in Materials Science, R. DeHoff, Taylor and Francis 2006.  (Chapters 8, 9 and 14)

## What to Learn?

* A Taylor's Series can be used to introduce correction terms to the bulk free energy.
* The energy functional can be minimized using variational principles
* The equations of motion can be developed from simple kinetic postulates
* Solutions to these equations show capillarity effects

## What to do?

* Develop a model for the thermodynamics of an inhomogeneous system.
* Derive the equation of motion for the phase seperation.
* Solve the kinetic equations and show the microstructural evolution.

### Introduction
----

Cahn and Hilliard’s paper from 1958 appears in Journal of Chemical Physics v. 28, n. 2, p. 258-267.  They generalize the free energy of a system with composition gradients. They do so using a Taylor expansion and develop a free energy functional and solve the resulting differential equation. Then a paper in 1961 titled "On Spinodal Decomposition" outlines the differential equation for the time rate of change of the composition in a spinodally decomposing system.

### An Example of a Spinodially Decomposing Structure
----

### Lecture Outline
----

* The Free Energy of Mixing
* Beyond the Bulk:  Energy Correction Terms
* Minimizing the Total Energy
* The Surface Energy and Concentration Profile of a Non-Uniform System
* Spinodal Decomposition

### The Free Energy of Mixing
-----

The formation of a solution can be thought of as a sequence of steps:

* Compute the free energy of the unmixed state for an amount of pure A and pure B
* Allow both A and B to form a chemical solution
* Compute the energy change upon mixing and add this change to the energy of the initial state

### The Free Energy of Mixing
----

To understand the energetics of a non-uniform system we need a model for a solution where the free energy of solution (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 without any contribution from the enthalpy.

Recall from thermodynamics that energy is an extensive quantity and that the Gibbs free energy is defined as:

$$
G = H - TS
$$

If we want to describe the isothermal change between states 1 and 2, we can write the following:

$$
G_2 - G_1 = H_2 - H_1 - T(S_2-S_1)
$$

Resulting in:

$$
\Delta G_{1 \rightarrow 2} = \Delta H_{1 \rightarrow 2} - T \Delta S_{1 \rightarrow 2}
$$

We will used this formula to describe the change from unmixed (state 1) to mixed (state 2) in a thermodynamic solution.

### No Preference for Chemical Surroundings

* In an ideal solution the enthalpy change (or internal energy change) is zero.
* The entropy arises from mixing effects only.
* Stirling's Formula is used to approximate terms due to the energy change on mixing:

$$
\Delta G_{\mathrm{mix, \, id}} = RT(X_A \ln X_A + X_B \ln X_B)
$$

The free energy for an ideal solution can therefore be written:

\begin{align}
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)
\end{align}

```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interact, fixed

def idealSolution(GA, GB, XB, temperature):
    """
    Computes the free energy of solution for an ideal binary mixture.

    Parameters
    ----------
    GA : float
        The partial molar Gibbs free energy of pure A in Joules.
    GB : float
        The partial molar Gibbs free energy of pure B in Joules.
    XB : ndarray
        The mol fraction of component B as an array.
    temperature : float
        The temperature.

    Returns
    -------
    G : ndarray
        An array of the Gibbs free energy having the same shape as `XB`.

    Examples
    --------
    >>> XB = np.linspace(0.01,0.99,10)
    >>> G = idealSolution(0.0,0.0,XB,1.0)
    >>> array([ 0.53440324, -3.72037187, -4.76282566, -3.72037187,  0.53440324])
    """
    return (1.0-XB)*GA+XB*GB+8.314*temperature*((1-XB)*np.log(1-XB)+XB*np.log(XB))

def myfig(temperature):
    """
    This function produces a plot of the Gibbs free energy of mixing for an ideal solution.
    """
    GA = 1.0
    GB = 500.0

    XB = np.linspace(0.01,0.99,50)
    temperatureSpace = np.linspace(1.0,100.0,10)
    y = idealSolution(GA,GB,XB,temperature)
    greySolutionLines = [idealSolution(GA,GB,XB,greyT) for greyT in temperatureSpace]

    fig, axes = plt.subplots(figsize=(10,8))

    for greyLine in greySolutionLines:
        axes.plot(XB, greyLine, 'black', alpha=0.9)

    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)$", linewidth=4)
    axes.legend()
    axes.grid(True, linestyle='dotted')
    axes.set_ylabel(r"$G_{soln}$")
    axes.set_xlabel(r"$X_B$")
    # Location for annotations can always be done by extents instead of absolute values.
    axes.annotate(r'$G_A$='+str(GA)+'\n'+r'$G_B$='+str(GB),xy=(0,200), size='large')
    plt.show()
    return
```

```python
interact(myfig, temperature=(1.0,100.0,1.0));
```

Failed to display Jupyter Widget of type interactive.

  If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean
  that the widgets JavaScript is still loading. If this message persists, it
  likely means that the widgets JavaScript library is either not installed or
  not enabled. See the Jupyter
  Widgets Documentation for setup instructions.

  If you're reading this message in another frontend (for example, a static
  rendering on GitHub or NBViewer),
  it may mean that your frontend doesn't currently support widgets.

### Correcting the Ideal Solution for Local Chemical Effects
----

* In general, the free energy of solution includes both enthalpic and entropic terms
* The previous treatment of the ideal solution neglects any contribution from the enthalpy.
* Before mixing - there are only A-A and B-B bonds and NO A-B bonds.
* After mixing the number of A-B bonds is estimated from statistical and structural considerations to produce a model of the excess enthalpy

As outlined above, it is possible to have both enthalpy and entropy of mixing effects when forming a solution.  A more general approach would include the possibility that there may be enthalpy changes upon mixing.  Two types of arguments can be made:  mathematical and physical.

A simple mathematical argument for the enthalpy of solution is based on the fact that the functions of mixing have the property that their values must pass through zero at the pure end member compositions.  A simple function that captures this requirement would have a form:

$$
\Delta H_{mix} = \Omega X_A X_B
$$

where $\Omega$ is a single adjustable parameter.  As is pointed out by DeHoff, simpler functions than this are not possible.  A physical argument for this form is known as the _quasichemical model_ for solutions.  A summary of the quasichemical model and the probability argument for finding like and unlike bonds in a random solution is given by DeHoff (and other texts on thermodynamics), but the important points are as follows:

The heat of mixing of a non-ideal solution, called the _regular solution_ is proportional:

* to the number of unlike bonds, and
* includes a parameter that scales with the difference in energy between like and unlike bonds.

$$
\Delta H_{\mathrm{mix}} = \Omega(\epsilon)X_A X_B
$$

### DIY:  Exploration of Bond Types
----

Simulate three different types of solutions:  clustered, random and ordered.  Compute the fraction of bond types as a function of mole fraction solute (e.g. $X_B$) for each type.  Justify the form of the enthalpy of mixing based on your calculations.

The regular solution model is then writte as:

\begin{align}
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)
\end{align}

```python
def regularSolution(GA, GB, XB, omega, temperature):
    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))

def myfig2(omega, temperature):
    """
    This function produces a plot of the Gibbs free energy of mixing for a regular solution.
    """
    GA = 1.0
    GB = 1.0

    XB = np.linspace(0.01,0.99,50)
    temperatureSpace = np.linspace(1.0,200.0,10)
    y = regularSolution(GA, GB, XB, omega, temperature)
    greySolutionLines = [regularSolution(GA, GB, XB, omega, greyT) for greyT in temperatureSpace]

    fig2, axes2 = plt.subplots(figsize=(14,9))

    for greyLine in greySolutionLines:
        axes2.plot(XB, greyLine, 'black', alpha=0.9)

    axes2.plot(XB, y, 'r', label=r"$G_{soln}$", linewidth=4)

    # Location for annotations can always be done by extents instead of absolute values.
    axes2.annotate('GA='+str(GA)+'\n'+'GB='+str(GB),xy=(0,400), fontsize=20)

    axes2.set_ylabel(r"$G_{soln}$", fontsize=15)
    axes2.set_xlabel(r"$X_B$", fontsize=15)
    axes2.legend(loc="upper right", fontsize=15)
    axes2.xaxis.set_tick_params(labelsize=15)
    axes2.yaxis.set_tick_params(labelsize=15)

    plt.show()
    return
```

```python
interact(myfig2, omega=(0.0,5000.0,1.0), temperature=(1.0,200.0,1.0));
```

### A Small Simplification
----

Although the regular solution can approximate more kinds of chemical solutions, all the effects we wish to show are produced by a simple function that replaces the regular solution model:

$$
f(\phi) = W \phi^2 (1-\phi)^2
$$

I switch to $\phi$ as a reminder of this simplification.  A plot of this function and the regular solution are shown side-by-side below:

```python
def regularSolution(GA, GB, XB, omega, temperature):
    return omega*(1.0-XB)*XB+(1.0-XB)*GA+XB*GB+8.314*temperature*((1-XB)*np.log(1-XB)+XB*np.log(XB))

def simplifiedSolution(XB, W):
    return (1.0-XB)**2*XB**2*W

def myfig3(omega, W, temperature):
    """
    This function ...
    """
    GA = 1.0
    GB = 1.0

    XB = np.linspace(0.01,0.99,50)

    temperatureSpace = np.linspace(1.0,100.0,10)
    y1 = regularSolution(GA, GB, XB, omega, temperature)
    greySolutionLines = [regularSolution(GA, GB, XB, omega, greyT) for greyT in temperatureSpace]

    wSpace = np.linspace(0.01,100.0,10)
    y2 = simplifiedSolution(XB, W)
    greyWLines = [simplifiedSolution(XB, greyW) for greyW in wSpace]

    fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(14,8))
    plt.tight_layout(pad=5.0)

    #for greyLine in greyMagLines:
    #    axes[0].plot(eta, greyLine, 'grey', alpha=0.3)
    #for greyLine in greyPhiLines:
    #    axes[1].plot(eta, greyLine, 'grey', alpha=0.3)
    #axes[0].set_ylim(0,4)
    #axes[0].plot(eta, y1, 'r', label=r"$MF(\eta,\xi)$")
    #axes[1].set_ylim(0,180)
    #axes[1].plot(eta, y2, 'b', label=r"$\phi(\eta,\xi)$")

    for greyLine in greySolutionLines:
        axes[0].plot(XB, greyLine, 'black', alpha=0.9)
    axes[0].plot(XB, y1, 'r', label=r"Regular Solution", linewidth=4)
    axes[0].annotate('GA='+str(GA)+'\n'+'GB='+str(GB),xy=(0,40), fontsize=15)

    for greyLine in greyWLines:
        axes[1].plot(XB, greyLine, 'black', alpha=0.9)
    axes[1].plot(XB, y2, 'g', label=r"$W \phi^2 (1-\phi)^2$", linewidth=4)
    axes[1].set_ylim(0.0,4.0)

    #axes.plot(XB, y, 'r', label=r"$G_{soln}$")
    #axes.legend()
    #axes.grid(True, linestyle='dotted')
    #axes.set_ylim(-600,200)
    #axes.set_ylabel(r"$G_{soln}$")
    #axes.set_xlabel(r"$X_B$")

    for ax in axes:
        ax.legend(loc="upper right", fontsize=15)
        ax.set_ylabel(r"$G_{soln}$", fontsize=20)
        ax.xaxis.set_tick_params(labelsize=15)
        ax.yaxis.set_tick_params(labelsize=15)

    axes[0].set_xlabel(r"$X_B$", fontsize=20)
    axes[1].set_xlabel(r"$\phi$", fontsize=20)

    plt.show()
    return
```

```python
interact(myfig3, omega=(0.0,1000.0,1.0), W=(0.0,100.0,1.0), temperature=(1.0,100.0,1.0));
```

### Beyond the Bulk:  Energy Correction Terms
----

* Diffusive length scales are on the order of microns.
* Precipitation and phase seperation length scales are on the order of nanometers.
* At nanometer length scales the gradient energy becomes comparable to the bulk free energy and must be accounted for.

Cahn and Hilliard's insight was that a non-uniform system's total energy should depend on the average values of the order parameter as well as the spatial gradients within the order parameter.  One viewpoint is that these gradient terms are "correction terms" needed when the order parameter gradients are very large.

Practically, this insight impacts the way scientists think of interfaces and phase transitions in areas primarily related to chemical solutions, magnetic domains and ferroelectric materials.  The development of a free energy functional that includes bulk and gradient terms permits a unified treatment of the bulk and interface regions in a material.  This, in turn, allows new understanding of both non-uniform and heterogenerous systems.  To understand the origin of the energy correction terms, the series expansion and the independence of the order parameter and its gradients are first discussed.

### Review:  A Taylor Series Expansion
----

In multiple independent variables the Taylor's Series is:

\begin{align}
f(x,y) & = f(a,b) + (x-a)\frac{\partial f}{\partial x} + (x-b)\frac{\partial f}{\partial y}\\
& + \frac{1}{2!} \left[ (x-a)^2 \frac{\partial^2 f}{\partial x^2} + 2(x-a)(y-b) \frac{\partial^2 f}{\partial x \partial y} + (y-b)^2 \frac{\partial^2 f}{\partial y^2} \right] \\
& + \; ...
\end{align}

### The Independence of $\phi$, $\nabla \phi$ and $\nabla^2 \phi$
----

* The total energy of the system can depend on the concentration and local variations
* This phase space can be sampled by permitting $\phi(x)$ and its gradients to vary independently
* Consider that the average composition of a system is independent of the wavelengths of concentration variations.

The Taylor's series above is written assuming that the variables $x$ and $y$ are independent.  More than two independent variables can be treated similarly.  When we write the free energy of a non-uniform system we will postulate that the concentration and its gradients are independent quantities.  Help establish that this is a reasonable assumption, consider the following example.

An intuitive argument is that in a conservative field a particle's instantaneous energy can be determined by its position and velocity.  It is possible to choose a particle's potential energy and kinetic energy by setting the position and velocity at an instant in time.  These two quantities can be chosen independently to return any desired value of the system's total energy.  In an effort to extend this analogy to chemical systems, below I plot three functions.  The first is the average composition.  The other two are functions that have the same average, but have different gradients and second derivatives.

```python
def phiPlots():
    """
    This function's docstring explaining the function.
    """
    t = np.linspace(0,10,100)
    y1 = np.cos(t)
    y2 = np.cos(2*t)
    y3 = 0*t
    fig, axes = plt.subplots(figsize=(14,9))
    axes.plot(t, y3, 'g--', label="Average")
    axes.plot(t, y1, 'r', label="Profile 1")
    axes.plot(t, y2, 'b', label="Profile 2")
    axes.set_xlabel(r"$t$", fontsize=15)
    axes.set_ylabel(r"$c(x)$", fontsize=15)
    axes.legend(fontsize=15)
    axes.xaxis.set_tick_params(labelsize=15)
    axes.yaxis.set_tick_params(labelsize=15)
    plt.show()
    return
```

```python
phiPlots()
```

### The Free Energy of Our System
----

If the temperature and pressure are our process variables, then we can use the Gibbs free energy per unit volume.  The total energy of the system is then found by integrating the Gibbs free energy density over the volume of the system as in the integral below.  Furthermore we assume that the order parameter (the composition proxy) and powers of the derivatives of order parameter all contribute to the free energy and are independent:

$$
F = \int_V f_v(\phi, \nabla \phi, \nabla^2 \phi, ...) \delta V
$$

It is possible to expand the integrand explicitly in powers of the independent parameters using a Taylor's series formalism (DeHoff explains this in Chapter 14 of his thermodynamics text, also), in a shorthand notation we write an equivalent statement:

$$
f_v = f_v^0 + L \nabla \phi + K_1 \nabla^2 \phi + K_2 (\nabla \phi)^2 + \; ...
$$

with

$$
L = \left( \frac{\partial f_v}{\partial (\nabla \phi)} \right)
$$

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 in the vicinity of the average alloy composition.

### The Free Energy Functional
----

Three arguments and manipulations are made to arrive at the desired functional:

1. The sign of the gradient should not affect the total energy;
1. The energy should be invariant with respect to inversion symmetry;
1. The energy should be invariant with respect to four fold rotations about a principal axis.

$$
F = \int_V f_v + K (\nabla \phi)^2 \delta V
$$

We keep the lowest order, nonzero correction term in the gradient of the order parameter.  The above assumptions can be relaxed for different applications.  We can now proceed to find the function $\phi(x)$ that minimizes the integral.

### A Result from the Calculus of Variations
----

The main purpose of the CoV is to find the function $y$ minimizing (or making extreme) the integral:

$$
I(y) = \int_{x_0}^{x_1} F(x,y',y'') dx
$$

* One application is a minimum path problem:  a straight line connects two points in the plane.
* One important result is the Euler-Lagrange equation:

$$
\frac{\partial F}{\partial y} - \frac{d}{dx} \frac{\partial F}{\partial y'} = 0
$$

A functional is a "function of functions".  To find the function that makes the integral stationary (most often a minimum) we will need to apply the Euler Lagrange result from the calculus of variations.

If you examine a series of nearby functions to the extreme function, $y(x)$, it can be demonstrated that the Euler-Lagrange equation is the only possibility for making $I(y)$ stationary.

Using the functional that includes the gradient correction term, we can write a differential equation where the solution is the minimizing function.

### DIY:  Use The Euler Lagrange Equation
---

Using a path length integral, demonstrate that the shortest distance between two points on the plane is a straight line.

### The PDE to Determine Profiles and Kinetic Evolution of a Non-Uniform System
----

$$
F(\phi,\phi') = W \phi^2 (1-\phi)^2 + \epsilon (\nabla \phi)^2
$$

Applying the Euler-Lagrange equation to our functional we get:

\begin{align}
\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
\end{align}

recall that $\phi(x,t)$ and this equation implies equilibrium.

```python
import sympy as sp
sp.init_session(quiet=True)
phi, W, epsilon = symbols('phi W epsilon', real=true)

functionalForm = W*phi(x)**2*(1-phi(x))**2 + epsilon*(phi(x).diff(x))**2
ele = sp.euler_equations(functionalForm, phi(x), x)
ele
```

```python
delFdelPhi = (ele[0].lhs).simplify()
delFdelPhi
```

```python
firstTermsFactored = sp.factor(4*W*phi**3-6*W*phi**2+2*W*phi)
firstTermsFactored
```

### Solving the ODE Explicitly
----

The use of an integrating factor results in the ODE:

$$
\frac{d \phi}{d x} \frac{1}{\phi(1-\phi)} - \sqrt{\frac{W}{\epsilon}} = 0
$$

```python
import sympy as sp
sp.init_session(quiet=True)
sp.dsolve(sp.diff(f(x),x)*(1/(f(x)*(1-f(x))))-k,f(x),hint='lie_group')
```

and after some exciting manipulation, a solution is:

$$
\phi(x) = \frac{1}{2}\left(1 + \tanh{\sqrt{\frac{W}{\epsilon}}\frac{x}{2}}\right)
$$

Contributions from the bulk free energy (through W) and the gradient correction term (through $\epsilon$) shape the profile of the concentration, order, etc. at the interface.

```python
def phiSolution(W, epsilon):
    """
    This function's docstring explaining the function.
    """
    x = np.linspace(-10,10,100)
    y = 0.5*(1.0 + np.tanh((np.sqrt(W/epsilon))*(x/2.0)))

    fig, axes = plt.subplots(figsize=(14,9))
    axes.plot(x, y, 'r', label=r"$\phi(x)$")
    axes.set_xlabel(r"$x$", fontsize=20)
    axes.set_ylabel(r"$\phi(x)$", fontsize=20)
    axes.xaxis.set_tick_params(labelsize=15)
    axes.yaxis.set_tick_params(labelsize=15)
    axes.legend(fontsize=20)
    plt.show()
    return
```

```python
interact(phiSolution, W=(0.01,10,0.1), epsilon=(0.01,10,0.1));
```

### Solving the PDE Using Relaxation
----

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:

$$
\frac{\partial \phi}{\partial t} = -M \frac{\delta F}{\delta \phi}
$$

and for a conserved order parameter the equations of motion are derived from:

$$
\frac{\partial \phi}{\partial t} = \nabla \cdot D \nabla \frac{\delta F}{\delta \phi}
$$

There are other choices, but these are the simplest choices that guarantee a free energy decrease with time.

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:

$$
\frac{\delta F}{\delta \phi} = A(\phi) - \epsilon \frac{d^{2}}{d x^{2}}  \phi{\left (x \right )}
$$

with

$$
\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)
$$

By distributing the divergence and diffusion coefficient, we arrive at:

$$
\frac{\partial \phi}{\partial t} = \nabla \cdot D \frac{\partial A}{\partial \phi} \nabla \phi(x) - D \epsilon \nabla^4 \phi(x)
$$

### Plots of the Progress of a Spinodal Decomposition Simulation

```python
%%HTML

```

```python
from fipy import *
from IPython.display import clear_output
import time
nx = ny = 100

mesh = Grid2D(nx=nx, ny=ny, dx=0.5, dy=0.5)
phi = CellVariable(name=r"$\phi$", mesh=mesh)
psi = CellVariable(name=r"$\psi$", mesh=mesh)

noise = GaussianNoiseVariable(mesh=mesh,mean=0.5,variance=0.01).value
phi[:] = noise

viewer = Viewer(vars=phi)

D = a = epsilon = 1.
dfdphi = a**2 * 2 * phi * (1 - phi) * (1 - 2 * phi)
dfdphi_ = a**2 * 2 * (1 - phi) * (1 - 2 * phi)
d2fdphi2 = a**2 * 2 * (1 - 6 * phi * (1 - phi))
eq1 = (TransientTerm(var=phi) == DiffusionTerm(coeff=D, var=psi))
eq2 = (ImplicitSourceTerm(coeff=1., var=psi)
       == ImplicitSourceTerm(coeff=-d2fdphi2, var=phi) - d2fdphi2 * phi + dfdphi
       - DiffusionTerm(coeff=epsilon**2, var=phi))
eq3 = (ImplicitSourceTerm(coeff=1., var=psi)
       == ImplicitSourceTerm(coeff=dfdphi_, var=phi)
       - DiffusionTerm(coeff=epsilon**2, var=phi))

eq = eq1 & eq3

dexp = -3
elapsed = 0.
duration = 100.0
```

```python
# Run the model.
while elapsed < duration:
    dt = min(100, numerix.exp(dexp))
    elapsed += dt
    dexp += 0.01
    eq.solve(dt=dt)
    viewer.plot()
    clear_output(wait=True)
    display(viewer)
```

### Summary
---

* When the enthalpy of mixing is non-zero phase seperation may occur.

* A correction term to the energy is found by a Taylor's Series expansion.

* The equilibrium profile of the order parameter is found by making the free energy an extreme value.

* This profile is determined by BOTH contributions from the bulk free energy and a "non-classical" gradient energy.

* A kinetic **postulate** can be used to solve the differential equation to gain insight into the kinetic evolution of phase seperating systems.

### Homework
----

Redo the derivation for a non-conserved order parameter and solve the resulting kinetic equations.  Comment on the differences between the two simulations.  Your submission should include written text, equations, visualizations, and numerical results and analysis.

July 26, 2018