# rE and rV measures for 2D Vector-Base Amplitude Panning (VBAP,VBIP,MDAP)

Acoustic Holography and Holophony

Franz Zotter, 2016.

The examples below show the directions and widths indicated by the $\boldsymbol{r}_\mathrm{V}$ and $\boldsymbol{r}_\mathrm{E}$ measures for pairwise vector-base amplitude panning along the horizon.

Assume that you are given the desired panning direction as

\begin{equation}
   \boldsymbol\theta=\begin{bmatrix}
      \cos\varphi_\mathrm{s}\\
      \sin\varphi_\mathrm{s}
      \end{bmatrix}
\end{equation}

and the coordinates of your loudspeakers as

\begin{equation}
   \mathbf{X}=\begin{bmatrix}
      \cos\varphi_1, &\dots, &\cos\varphi_\mathrm{L}\\
      \sin\varphi_1, &\dots, &\sin\varphi_\mathrm{L}
      \end{bmatrix}.
\end{equation}

Then VBAP searches the loudspeaker pair that encloses the direction $\boldsymbol\theta$. This is done by constructing the convex hull, which defines the lines (simplices) connecting all the loudspeaker pairs by the loudspeaker indices involved. For a ring of $\mathrm{L}$ loudspeakers with loudspeakers sorted in azimuth, it looks like this

\begin{equation}
   Q=\mathrm{convhull}\{\mathbf{X}\}=\begin{bmatrix}
   1& 2\\
   2& 3\\
   \vdots &\vdots\\
   a& b\\
   \vdots &\vdots\\
   \mathrm{L}-1& \mathrm{L}\\
   \mathrm{L} & 1
   \end{bmatrix}.
\end{equation}

Now VBAP searches through the convex hull to find the loudspeaker pair (simplex of the indices $a,b$) of which the wheighted superposition of direction vectors allows for a solution with numerically all-positive weights $\boldsymbol{g}_q=[g_a,g_b]$ with $g_a,g_b>0$ to represent $\boldsymbol\theta$:

$\boldsymbol g=\boldsymbol 0$ with length $\mathrm{L}$

For all $q$ indexing through all pairs:

 $\boldsymbol{g}_q=\mathbf{X}[Q[q,:]]^{-1}\boldsymbol{\theta}$

 if $g_a,g_b>-10^{-3}$:

$\boldsymbol{g}[[a,b],:]=\boldsymbol{g}_q$

break loop

This algorithm is defined below.

```python
import numpy as np
from numpy.linalg import inv

def vectorpan(xys,xyls,simplices):
    g=np.zeros(phils.shape[0])
    for n in range(0,simplices.shape[0]):
        na=simplices[n,0]
        nb=simplices[n,1]
        M=np.array([xyls[:,na],xyls[:,nb]]).T
        gnm=np.dot(inv(M),xys)
        if np.sum(gnm<-1e-3)==0:
            g[na]=gnm[0]
            g[nb]=gnm[1]
            break
    return g
```

## Vector measures for arbitrarily many loudspeakers
The vector measures are easily extended to more than 2 loudspeakers, i.e., to

\begin{equation}
\boldsymbol{r}_\mathrm{V}=
\frac{\sum_{l=1}^\mathrm{L}g_l\,\boldsymbol\theta_l}{\sum_{l=1}^\mathrm{L}g_l},
\qquad\qquad
\boldsymbol{r}_\mathrm{E}=
\frac{\sum_{l=1}^\mathrm{L}g_l^2\,\boldsymbol\theta_l}{\sum_{l=1}^\mathrm{L}g_l^2}.
\end{equation}

```python
def r_vector(g,theta):
    L=theta.size
    thx=np.sin(theta[:])
    thy=np.cos(theta[:])
    rx=np.dot(thx,g).T
    ry=np.dot(thy,g).T
    normalizer=sum(g,0)
    rx/=normalizer
    ry/=normalizer
    return np.array([rx,ry])
```

This vector model is now applied on an example using 5 equally spaced loudpeaker on the horizon and panning for all directions from $-180^\circ\dots180^\circ$. It shows again the directions and widths displayed by $\boldsymbol r_\mathrm{V,E}$.

```python
from scipy.spatial import ConvexHull
from bokeh.plotting import figure, output_file, show
from bokeh.io import output_notebook

L=5
Npts=200
phils=np.arange(0,L)*2*np.pi/L
xyls=np.array([np.cos(phils),np.sin(phils)])
phis=np.linspace(-np.pi*0.99,np.pi,Npts)
xys=np.array([np.cos(phis),np.sin(phis)])
g=np.zeros([L,Npts])
qh=ConvexHull(xyls.T)
for n in range(0,Npts):
    gn=vectorpan(xys[:,n],xyls,qh.simplices)
    gn=np.abs(gn)
    gn/=np.sqrt(np.sum(gn))
    g[:,n]=gn

output_notebook()
p1=figure(title="r vector direction VBAP",plot_width=300,plot_height=250)
p2=figure(title="r vector width VBAP",plot_width=300,plot_height=250,x_range=[-180,180],y_range=[0,100])

r=r_vector(g,phils)
mr=np.sqrt(np.sum(r**2,0))
p1.line(phis*180/np.pi,np.arctan2(r[0,:],r[1,:])*180/np.pi,legend_label="rV",line_width=2)
p2.line(phis*180/np.pi,2*np.arccos(mr)*180/np.pi,legend_label="2acos||rV||",line_width=2)
r=r_vector(g**2,phils)
mr=np.sqrt(np.sum(r**2,0))
p1.line(phis*180/np.pi,np.arctan2(r[0,:],r[1,:])*180/np.pi,legend_label="rE",color="red",line_width=2)
p2.line(phis*180/np.pi,2*np.arccos(mr)*180/np.pi,legend_label="2acos||rE||",color="red",line_width=2)
p1.legend.location="top_left"
p2.legend.background_fill_alpha = 0.5
show(p1)
show(p2)
```

    Loading BokehJS ...

It seems that the vectors do not indicate the same directions: only the $\boldsymbol r_\mathrm{V}$ vector is perfectly controlled. Alternatively to VBAP, VBIP could be done, which just takes the square roots of the panning gains obtained from VBAP, before normalization.

```python
for n in range(0,Npts):
    gn=vectorpan(xys[:,n],xyls,qh.simplices)
    gn=np.abs(gn)
    # new line inserted just for VBIP:
    gn=np.sqrt(gn)
    gn/=np.sqrt(np.sum(gn))
    g[:,n]=gn

p3=figure(title="r vector direction VBIP",plot_width=300,plot_height=250)
p4=figure(title="r vector width VBIP",plot_width=300,plot_height=250,x_range=[-180,180],y_range=[0,100])

r=r_vector(g,phils)
mr=np.sqrt(np.sum(r**2,0))
p3.line(phis*180/np.pi,np.arctan2(r[0,:],r[1,:])*180/np.pi,legend_label="rV",line_width=2)
p4.line(phis*180/np.pi,2*np.arccos(mr)*180/np.pi,legend_label="2acos||rV||",line_width=2)
r=r_vector(g**2,phils)
mr=np.sqrt(np.sum(r**2,0))
p3.line(phis*180/np.pi,np.arctan2(r[0,:],r[1,:])*180/np.pi,legend_label="rE",color="red",line_width=2)
p4.line(phis*180/np.pi,2*np.arccos(mr)*180/np.pi,legend_label="2acos||rE||",color="red",line_width=2)
p3.legend.location="top_left"
p4.legend.background_fill_alpha = 0.5
show(p3)
show(p4)
```

Now the $\boldsymbol r_\mathrm{E}$ vector is perfectly controlled and the widths tend to have narrower notches.

## Multi-Direction Amplitude Panning (MDAP)
MDAP does the same as VBAP, but it always superimposes several sources to obtain the panning gains. One could describe it as

\begin{equation}
   \boldsymbol{\tilde g}=\mathrm{VBAP}\{\varphi_\mathrm{s}-\textstyle\frac{\alpha}{2},\mathbf{X}\}+\mathrm{VBAP}\{\varphi_\mathrm{s},\mathbf{X}\}+\mathrm{VBAP}\{\varphi_\mathrm{s}+\textstyle\frac{\alpha}{2},\mathbf{X}\}\qquad\qquad
   \boldsymbol g=\frac{\boldsymbol{\tilde g}}{\|\boldsymbol{\tilde g}\|}
\end{equation}

```python
alpha=phils[1]-phils[0]
xys2=np.array([np.cos(phis-alpha/2),np.sin(phis-alpha/2)])
xys3=np.array([np.cos(phis+alpha/2),np.sin(phis+alpha/2)])

for n in range(0,Npts):
    gn=vectorpan(xys[:,n],xyls,qh.simplices)
    # lines inserted for MDAP
    gn+=vectorpan(xys2[:,n],xyls,qh.simplices)
    gn+=vectorpan(xys3[:,n],xyls,qh.simplices)
    gn=np.abs(gn)
    gn/=np.sqrt(np.sum(gn))
    g[:,n]=gn

p5=figure(title="r vector direction MDAP",plot_width=300,plot_height=250)
p6=figure(title="r vector width MDAP",plot_width=300,plot_height=250,x_range=[-180,180],y_range=[0,100])

r=r_vector(g,phils)
mr=np.sqrt(np.sum(r**2,0))
p5.line(phis*180/np.pi,np.arctan2(r[0,:],r[1,:])*180/np.pi,legend_label="rV",line_width=2)
p6.line(phis*180/np.pi,2*np.arccos(mr)*180/np.pi,legend_label="2acos||rV||",line_width=2)
r=r_vector(g**2,phils)
mr=np.sqrt(np.sum(r**2,0))
p5.line(phis*180/np.pi,np.arctan2(r[0,:],r[1,:])*180/np.pi,legend_label="rE",color="red",line_width=2)
p6.line(phis*180/np.pi,2*np.arccos(mr)*180/np.pi,legend_label="2acos||rE||",color="red",line_width=2)
p5.legend.location="top_left"
p6.legend.background_fill_alpha = 0.5
show(p5)
show(p6)
```

Obviously MDAP could smooth out much of the variations in $\boldsymbol{r}_\mathrm{V,E}$ and also the difference between both of the measures.

```python

```


```python
import numpy as np
import sympy as sp
import pandas as pd
import math
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split

data=pd.read_csv('C:\\Users\\Utsav\\Desktop\\pulsar_prediction\\pulsar_stars.csv', sep=',',header=0)

pulsarData=data.values
pulsarData=np.array(pulsarData)
```

```python
#split the data

train, test = train_test_split(data, test_size=0.2)
pulsar=np.array(train)
test=np.array(test)
```

```python
train=np.array(train)
NegativeCount=0
PositiveCount=0
for i in range(14318):
    if pulsar[i][8]==0 :
        NegativeCount=NegativeCount+1
    if pulsar[i][8]==1:
        PositiveCount=PositiveCount+1
print(NegativeCount)
print(PositiveCount)
print(PositiveCount+NegativeCount)
```

    12991
    1327
    14318

```python
positives=np.zeros((PositiveCount,9))
negatives=np.zeros((NegativeCount,9))

j=0
k=0

for i in range(14318):

    if(pulsar[i,8] == 1):
        positives[j,:] = pulsar[i,:]
        j=j+1
    if(pulsar[i,8] == 0):
        negatives[k,:] = pulsar[i,:]
        k=k+1
```

```python

```

- # Visualizing the data

# Histogram of various features for positive and negative data
(Remember we have an unbalanced dataset)

# Mean of IPP

```python
#Mean IPP
bins=20
clr=['r', 'b']
lbl=['1', '0']
x=[positives[:,0], negatives[:,0]]
plt.hist(x, bins, density=True, color=clr, label=lbl)
plt.legend(loc='upper right')
plt.title('Density')
plt.show()
#
bins = np.linspace(-1, 250, 50)
plt.hist(positives[:,0], bins, alpha=0.5, label='1', color='r', density=True)
plt.hist(negatives[:,0], bins, alpha=0.5, label='0', color='b', density=True)
plt.legend(loc='upper right')
plt.title('Density overlap')
plt.show()
#
bins = np.linspace(-1, 250, 50)
plt.hist(positives[:,0], bins, alpha=0.5, label='1', color='r')
plt.hist(negatives[:,0], bins, alpha=0.5, label='0', color='b')
plt.legend(loc='upper right')
plt.title('without Density overlap')
plt.show()
#
bins=100
plt.hist(positives[:,0], bins)
plt.title('only postives (Not density)')
plt.show()
#
bins=100
plt.hist(negatives[:,0],bins)
plt.title('only negatives (Not density)')
plt.show()
#
print("Mean of Mean of IPP for positive data = ", np.mean(positives[:,0]))
print("Mean of Mean of IPP for negative data = ", np.mean(negatives[:,0]))
```

Inference:

Means are significantly away

Overlap is not much

# Std Dev of IPP

```python
#Mstddev of IPP
bins=20
clr=['r', 'b']
lbl=['1', '0']
x=[positives[:,1], negatives[:,1]]
plt.hist(x, bins, density=True, color=clr, label=lbl)
plt.legend(loc='upper right')
plt.title('Density')
plt.show()
#
bins = np.linspace(-1, 100, 50)
plt.hist(positives[:,1], bins, alpha=0.5, label='1', color='r', density=True)
plt.hist(negatives[:,1], bins, alpha=0.5, label='0', color='b', density = True)
plt.legend(loc='upper right')
plt.title('Density overlap')
plt.show()
#
bins = np.linspace(-1, 100, 50)
plt.hist(positives[:,1], bins, alpha=0.5, label='1', color='r')
plt.hist(negatives[:,1], bins, alpha=0.5, label='0', color='b')
plt.legend(loc='upper right')
plt.title('without Density overlap')
plt.show()
#
bins=100
plt.hist(positives[:,1], bins)
plt.title('only postives (Not density)')
plt.show()
#
bins=100
plt.hist(negatives[:,1],bins)
plt.title('only negatives (Not density)')
plt.show()
#
print("Mean of stddev of IPP for positive data = ", np.mean(positives[:,1]))
print("Mean of stddev of IPP for negative data = ", np.mean(negatives[:,1]))
```

Inference:

Means are not very far away

Significant overlap

Very bad distinguisher

# Excess Kurtosis of IPP

```python
#EK IPP
bins=20
clr=['r', 'b']
lbl=['1', '0']
x=[positives[:,2], negatives[:,2]]
plt.hist(x, bins, density=True, color=clr, label=lbl)
plt.legend(loc='upper right')
plt.title('Density')
plt.show()
#
bins = np.linspace(-1, 10, 50)
plt.hist(positives[:,2], bins, alpha=0.5, label='1', color='r', density=True)
plt.hist(negatives[:,2], bins, alpha=0.5, label='0', color='b', density = True)
plt.legend(loc='upper right')
plt.title('Density overlap')
plt.show()
#
bins = np.linspace(-1, 10, 50)
plt.hist(positives[:,2], bins, alpha=0.5, label='1', color='r')
plt.hist(negatives[:,2], bins, alpha=0.5, label='0', color='b')
plt.legend(loc='upper right')
plt.title('without Density overlap')
plt.show()
#
bins=100
plt.hist(positives[:,2], bins)
plt.title('only postives (Not density)')
plt.show()
#
bins=100
plt.hist(negatives[:,2],bins)
plt.title('only negatives (Not density)')
plt.show()
#
print("Mean of excess kurt of IPP for positive data = ", np.mean(positives[:,2]))
print("Mean of excess kurt of IPP for negative data = ", np.mean(negatives[:,2]))
```

Inference:

All negatives are concentrated in region of <2

Not much overlap

Many negatives have value <0 ; almost no positive has value <0

# Skewness of IPP

```python
#skewness ipp
bins=20
clr=['r', 'b']
lbl=['1', '0']
x=[positives[:,3], negatives[:,3]]
plt.hist(x, bins, density=True, color=clr, label=lbl)
plt.legend(loc='upper right')
plt.title('Density')
plt.show()
#
bins = np.linspace(-1, 80, 50)
plt.hist(positives[:,3], bins, alpha=0.5, label='1', color='r', density=True)
plt.hist(negatives[:,3], bins, alpha=0.5, label='0', color='b', density = True)
plt.legend(loc='upper right')
plt.title('Density overlap')
plt.show()
#
bins = np.linspace(-1, 80, 50)
plt.hist(positives[:,3], bins, alpha=0.5, label='1', color='r')
plt.hist(negatives[:,3], bins, alpha=0.5, label='0', color='b')
plt.legend(loc='upper right')
plt.title('without Density overlap')
plt.show()
#
bins=100
plt.hist(positives[:,3], bins)
plt.title('only postives (Not density)')
plt.show()
#
bins=100
plt.hist(negatives[:,3],bins)
plt.title('only negatives (Not density)')
plt.show()
#
print("Mean of skewness of IPP for positive data = ", np.mean(positives[:,3]))
print("Mean of skewness of IPP for negative data = ", np.mean(negatives[:,3]))
```

Inference:

Means are significantly far away, but all negative data overlaps positive data

For larger value, almost certain that data is positive, but cannot distinguish for smaller values

# Mean of DMSNR

```python
#Mean dmsnr
bins=20
clr=['r', 'b']
lbl=['1', '0']
x=[positives[:,4], negatives[:,4]]
plt.hist(x, bins, density=True, color=clr, label=lbl)
plt.legend(loc='upper right')
plt.title('Density')
plt.show()
#
bins = np.linspace(-1, 100, 50)
plt.hist(positives[:,4], bins, alpha=0.5, label='1', color='r', density=True)
plt.hist(negatives[:,4], bins, alpha=0.5, label='0', color='b', density = True)
plt.legend(loc='upper right')
plt.title('Density overlap')
plt.show()
#
bins = np.linspace(-1, 100, 50)
plt.hist(positives[:,4], bins, alpha=0.5, label='1', color='r')
plt.hist(negatives[:,4], bins, alpha=0.5, label='0', color='b')
plt.legend(loc='upper right')
plt.title('without Density overlap')
plt.show()
#
bins=100
plt.hist(positives[:,4], bins)
plt.title('only postives (Not density)')
plt.show()
#
bins=100
plt.hist(negatives[:,4],bins)
plt.title('only negatives (Not density)')
plt.show()
#
print("Mean of Mean of DMSNR for positive data = ", np.mean(positives[:,4]))
print("Mean of Mean of DMSNR for negative data = ", np.mean(negatives[:,4]))
```

Inference:

Means are significantly away

Most of the negatives have a small mean value of the dmsnr curve

But a significant fraction of positives also have small mean dmnsr value

Therefore Mean dmsnr alone is not a good distinguisher

This is also mentioned in R J Lyon's PhD Thesis, Refer Fig 7.3

# Std Dev of DMSNR

```python
#stddev dmsnr
bins=20
clr=['r', 'b']
lbl=['1', '0']
x=[positives[:,5], negatives[:,5]]
plt.hist(x, bins, density=True, color=clr, label=lbl)
plt.legend(loc='upper right')
plt.title('Density')
plt.show()
#
bins = np.linspace(-1, 100, 50)
plt.hist(positives[:,5], bins, alpha=0.5, label='1', color='r', density=True)
plt.hist(negatives[:,5], bins, alpha=0.5, label='0', color='b', density = True)
plt.legend(loc='upper right')
plt.title('Density overlap')
plt.show()
#
bins = np.linspace(-1, 100, 50)
plt.hist(positives[:,5], bins, alpha=0.5, label='1', color='r')
plt.hist(negatives[:,5], bins, alpha=0.5, label='0', color='b')
plt.legend(loc='upper right')
plt.title('without Density overlap')
plt.show()
#
bins=100
plt.hist(positives[:,5], bins)
plt.title('only postives (Not density)')
plt.show()
#
bins=100
plt.hist(negatives[:,4],bins)
plt.title('only negatives (Not density)')
plt.show()
#
print("Mean of stddev of DMSNR for positive data = ", np.mean(positives[:,5]))
print("Mean of stddev of DMSNR for negative data = ", np.mean(negatives[:,5]))
```

Inference:

Means are far away

Overlap is significant

# Excess kurtosis of DMSNR

```python
#kurt dmsnr
bins=20
clr=['r', 'b']
lbl=['1', '0']
x=[positives[:,6], negatives[:,6]]
plt.hist(x, bins, density=True, color=clr, label=lbl)
plt.legend(loc='upper right')
plt.title('Density')
plt.show()
#
bins = np.linspace(-1, 100, 50)
plt.hist(positives[:,6], bins, alpha=0.5, label='1', color='r', density=True)
plt.hist(negatives[:,6], bins, alpha=0.5, label='0', color='b', density = True)
plt.legend(loc='upper right')
plt.title('Density overlap')
plt.show()
#
bins = np.linspace(-1, 100, 50)
plt.hist(positives[:,6], bins, alpha=0.5, label='1', color='r')
plt.hist(negatives[:,6], bins, alpha=0.5, label='0', color='b')
plt.legend(loc='upper right')
plt.title('without Density overlap')
plt.show()
#
bins=100
plt.hist(positives[:,6], bins)
plt.title('only postives (Not density)')
plt.show()
#
bins=100
plt.hist(negatives[:,6],bins)
plt.title('only negatives (Not density)')
plt.show()
#
print("Mean of excess kurt of DMSNR for positive data = ", np.mean(positives[:,6]))
print("Mean of excess kurt of DMSNR for negative data = ", np.mean(negatives[:,6]))
```

Inference:

Means are far away

Overlap is significant

# Skewness of DMSNR

```python
#skewness dmsnr
bins=20
clr=['r', 'b']
lbl=['1', '0']
x=[positives[:,7], negatives[:,7]]
plt.hist(x, bins, density=True, color=clr, label=lbl)
plt.legend(loc='upper right')
plt.title('Density')
plt.show()
#
bins = np.linspace(-5, 1200, 50)
plt.hist(positives[:,7], bins, alpha=0.5, label='1', color='r', density=True)
plt.hist(negatives[:,7], bins, alpha=0.5, label='0', color='b', density = True)
plt.legend(loc='upper right')
plt.title('Density overlap')
plt.show()
#
bins = np.linspace(-5, 1200, 50)
plt.hist(positives[:,7], bins, alpha=0.5, label='1', color='r')
plt.hist(negatives[:,7], bins, alpha=0.5, label='0', color='b')
plt.legend(loc='upper right')
plt.title('without Density overlap')
plt.show()
#
bins=100
plt.hist(positives[:,7], bins)
plt.title('only postives (Not density)')
plt.show()
#
bins=100
plt.hist(negatives[:,7],bins)
plt.title('only negatives (Not density)')
plt.show()
#
print("Mean of skewness of DMSNR for positive data = ", np.mean(positives[:,7]))
print("Mean of skewness of DMSNR for negative data = ", np.mean(negatives[:,7]))
```

Inference:

Means are far away

negative almost etirely lies in positive

```python

```


| |Pierre Proulx, ing, professeur|
|:---|:---|
|Département de génie chimique et de génie biotechnologique |** GCH200-Phénomènes d'échanges I **|

### Section18.7, Difffusion et réaction dans un catalyseur poreux sphérique

Voir les hypothèses dans le livre de Transport Phenomena.

```python
#
# Pierre Proulx
#
# Préparation de l'affichage et des outils de calcul symbolique
#
import sympy as sp
from IPython.display import *
sp.init_printing(use_latex=True)
%matplotlib inline
```

```python
# Paramètres, variables et fonctions
r,delta_r,R,k_1,a,D_A,C_AR=sp.symbols('r,delta_r,R,k_1,a,D_A,C_AR')
N_A=sp.symbols('N_A')
C_A=sp.Function('C_A')(r)
```

```python
#
# Résultat du bilan sur une coquille sphérique
#
eq=sp.Eq(sp.diff(N_A(r)*r**2,r)+k_1*a*C_A*r**2,0)
display(eq)
```

```python
#
# Solution après transformation, avant les conditions aux limites (annexe C-1)
#
C1,C2=sp.symbols('C1,C2')
C_A=C_AR*C1/r*sp.cosh((k_1*a/D_A)**0.5*r)+C_AR*C2/r*sp.sinh((k_1*a/D_A)**0.5*r)
display(C_A)
```

```python
#C_A=C_A.subs(sp.symbols('C1'),0)
cl1=r**2*sp.diff(C_A,r)   # même approche que faite en classe
cl1=sp.cancel(cl1) # Truc pour faire effectuer les multiplications et divisions de r et r2
                # et éviter les indeterminations 0/0.
cl1=cl1.subs(r,0)
cl2=sp.Eq(C_A.subs(r,R)-C_AR,0)    # à la surface, C_A = C_AR
cl2=cl2.lhs
constantes=sp.solve([cl1,cl2],sp.symbols('C1 C2'))
display(constantes)
C_A1=C_A.subs(constantes)
display(sp.simplify(C_A1))
```

```python
#
# en r=0, le flux est fini ou la concentration est finie. les deux conditions sont ok et donnent
# que C1 = 0, il est plus facile de le faire ' a la main ', mais je montre comment on peut
# utiliser la condition de flux nul plus haut (calcul de C_A1)
C_A=C_A.subs('C1',0)
#
# résoudre pour C2
#
eq=sp.Eq(C_A.subs(r,R)-C_AR)
constante=sp.solve((eq,0),'C2')
C_A=C_A.subs(constante)
display(C_A)
display(sp.simplify(C_A)-sp.simplify(C_A1)) # vérifier que les deux approches sont correctes
```

```python
#
# Tracer le profil pour différentes valeurs de la constante de réaction
#
dico1={'C_AR':1,'D_A':1.e-7,'R':0.01,'a':16,'k_1':1.e-5}
Thiele1=((k_1*a/D_A)**(0.5)*R).subs(dico1).evalf(3)
C_A1=C_A.subs(dico1)
dico2={'C_AR':1,'D_A':1.e-7,'R':0.01,'a':16,'k_1':4.e-5}
Thiele2=((k_1*a/D_A)**(0.5)*R).subs(dico2).evalf(3)
C_A2=C_A.subs(dico2)
dico3={'C_AR':1,'D_A':1.e-7,'R':0.01,'a':16,'k_1':16.e-5}
Thiele3=((k_1*a/D_A)**(0.5)*R).subs(dico3).evalf(3)
C_A3=C_A.subs(dico3)
dico4={'C_AR':1,'D_A':1.e-7,'R':0.01,'a':16,'k_1':64.e-5}
Thiele4=((k_1*a/D_A)**(0.5)*R).subs(dico4).evalf(3)
C_A4=C_A.subs(dico4)
dico5={'C_AR':1,'D_A':1.e-7,'R':0.01,'a':16,'k_1':256.e-5}
Thiele5=((k_1*a/D_A)**(0.5)*R).subs(dico5).evalf(3)
C_A5=C_A.subs(dico5)
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize']=10,8
p = sp.plot((C_A1,(r,0,0.01)),(C_A2,(r,0,0.01)),(C_A3,(r,0,0.01)),(C_A4,(r,0,0.01)),(C_A5,(r,0,0.01)),
            legend=True, title='Concentration radiale en fonction de Thiele',
            xlabel='r',ylabel='C_a',show=False)
p[0].line_color = 'blue'
p[0].label=str(Thiele1)
p[1].line_color = 'black'
p[1].label=str(Thiele2)
p[2].line_color = 'red'
p[2].label=str(Thiele3)
p[3].line_color = 'green'
p[3].label=str(Thiele4)
p[4].line_color = 'yellow'
p[4].label=str(Thiele5)
p.show()
```


# Velocity and Acceleration of a point of a rigid body

> Renato Naville Watanabe, Marcos Duarte
> [Laboratory of Biomechanics and Motor Control](http://pesquisa.ufabc.edu.br/bmclab)
> Federal University of ABC, Brazil

This notebook shows the expressions of  the velocity and acceleration of a point on rigid body, given the angular velocity of the body.

## Frame of reference attached to a body

The concept of reference frame in Biomechanics and motor control is very important and central to the understanding of human motion. For example, do we see, plan and control the movement of our hand with respect to reference frames within our body or in the environment we move? Or a combination of both?
The figure below, although derived for a robotic system, illustrates well the concept that we might have to deal with multiple coordinate systems.

Figure. Multiple coordinate systems for use in robots (figure from Corke (2017)).

For three-dimensional motion analysis in Biomechanics, we may use several different references frames for convenience and refer to them as global, laboratory, local, anatomical, or technical reference frames or coordinate systems (we will study this later).
There has been proposed different standardizations on how to define frame of references for the main segments and joints of the human body. For instance, the International Society of Biomechanics has a [page listing standardization proposals](https://isbweb.org/activities/standards) by its standardization committee and subcommittees:

## Position of a point on a rigid body

The description of the position of a point P of a rotating rigid body is given by:

\begin{equation}
{\bf\vec{r}_{P/O}} = x_{P/O}^*{\bf\hat{i}'} + y_{P/O}^*{\bf\hat{j}'}
\end{equation}

where $x_{P/O}^*$ and $y_{P/O}^*$ are the coordinates of the point P position at a reference state with the versors described as:

\begin{equation}
{\bf\hat{i}'} = \cos(\theta){\bf\hat{i}}+\sin(\theta){\bf\hat{j}}
\end{equation}

\begin{equation}
{\bf\hat{j}'} = -\sin(\theta){\bf\hat{i}}+\cos(\theta){\bf\hat{j}}
\end{equation}

Note that the vector ${\bf\vec{r}_{P/O}}$ has always the same description for any point P of the rigid body when described as a linear combination of ${\bf\hat{i}'}$ and ${\bf\hat{j}'}$.

## Translation of a rigid body

 Let's consider now the case in which, besides a rotation, a translation of the body happens. This situation is represented in the figure below. In this case, the position of the point P is given by:

\begin{equation}
{\bf\vec{r}_{P/O}} = {\bf\vec{r}_{A/O}}+{\bf\vec{r}_{P/A}}= {\bf\vec{r}_{A/O}}+x_{P/A}^*{\bf\hat{i}'} + y_{P/A}^*{\bf\hat{j}'}
\end{equation}

## Angular velocity of a body

The magnitude of the angular velocity of a rigid  body rotating on a plane is defined as:

\begin{equation}
    \omega = \frac{d\theta}{dt}
\end{equation}

Usually, it is defined an angular velocity vector perpendicular to the plane where the rotation occurs (in this case the x-y plane) and with magnitude $\omega$:

\begin{equation}
    \vec{\bf{\omega}} = \omega\hat{\bf{k}}
\end{equation}

## Velocity of a point with no translation

First we will consider the situation with no translation. The velocity of the point P is given by:

\begin{equation}
{\bf\vec{v}_{P/O}} = \frac{d{\bf\vec{r}_{P/O}}}{dt} = \frac{d(x_{P/O}^*{\bf\hat{i}'} + y_{P/O}^*{\bf\hat{j}'})}{dt}
\end{equation}

To continue this deduction, we have to find the expression of the derivatives of
${\bf\hat{i}'}$ and ${\bf\hat{j}'}$. This is very similar to the derivative expressions of ${\bf\hat{e_R}}$ and  ${\bf\hat{e_\theta}}$ of [polar basis](http://nbviewer.jupyter.org/github/BMClab/bmc/blob/master/notebooks/PolarBasis.ipynb).

\begin{equation}
\frac{d{\bf\hat{i}'}}{dt} = -\dot{\theta}\sin(\theta){\bf\hat{i}}+\dot{\theta}\cos(\theta){\bf\hat{j}} = \dot{\theta}{\bf\hat{j}'}
\end{equation}

\begin{equation}
\frac{d{\bf\hat{j}'}}{dt} = -\dot{\theta}\cos(\theta){\bf\hat{i}}-\dot{\theta}\sin(\theta){\bf\hat{j}} = -\dot{\theta}{\bf\hat{i}'}
\end{equation}

Another way to represent the expressions above is by using the vector form to express the angular velocity $\dot{\theta}$. It is usual to represent the angular velocity as a vector in the direction ${\bf\hat{k}}$: ${\bf\vec{\omega}} = \dot{\theta}{\bf\hat{k}} = \omega{\bf\hat{k}}$. Using this definition of the angular velocity, we can write the above expressions as:

\begin{equation}
\frac{d{\bf\hat{i}'}}{dt} = \dot{\theta}{\bf\hat{j}'} = \dot{\theta} {\bf\hat{k}}\times {\bf\hat{i}'} = {\bf\vec{\omega}} \times {\bf\hat{i}'}
\end{equation}

\begin{equation}
\frac{d{\bf\hat{j}'}}{dt} = -\dot{\theta}{\bf\hat{i}'} = \dot{\theta} {\bf\hat{k}}\times {\bf\hat{j}'} ={\bf\vec{\omega}} \times {\bf\hat{j}'}
\end{equation}

So, the velocity of the point P in the situation of no translation is:

\begin{equation}
{\bf\vec{v}_{P/O}} = \frac{d(x_{P/O}^*{\bf\hat{i}'} + y_{P/O}^*{\bf\hat{j}'})}{dt} = x_{P/O}^*\frac{d{\bf\hat{i}'}}{dt} + y_{P/O}^*\frac{d{\bf\hat{j}'}}{dt}=x_{P/O}^*{\bf\vec{\omega}} \times {\bf\hat{i}'} + y_{P/O}^*{\bf\vec{\omega}} \times {\bf\hat{j}'} = {\bf\vec{\omega}} \times \left(x_{P/O}^*{\bf\hat{i}'}\right) + {\bf\vec{\omega}} \times \left(y_{P/O}^*{\bf\hat{j}'}\right) ={\bf\vec{\omega}} \times \left(x_{P/O}^*{\bf\hat{i}'}+y_{P/O}^*{\bf\hat{j}'}\right)
\end{equation}

\begin{equation}
{\bf\vec{v}_{P/O}} = {\bf\vec{\omega}} \times {\bf{\vec{r}_{P/O}}}
\end{equation}

This expression shows that the velocity vector of any point of a rigid body is orthogonal to the vector linking the point O and the point P.

It is worth to note that despite the above expression was deduced for a planar movement, the expression above is general, including three dimensional movements.

## Relative velocity of a point on a rigid body to another point

To compute the velocity of a point on a rigid body that is translating, we need to find the expression of the velocity of a point (P) in relation to another point on the body (A). So:

\begin{equation}
{\bf\vec{v}_{P/A}} = {\bf\vec{v}_{P/O}}-{\bf\vec{v}_{A/O}} = {\bf\vec{\omega}} \times {\bf{\vec{r}_{P/O}}} - {\bf\vec{\omega}} \times {\bf{\vec{r}_{A/O}}} = {\bf\vec{\omega}} \times ({\bf{\vec{r}_{P/O}}}-{\bf{\vec{r}_{A/O}}}) =  {\bf\vec{\omega}} \times {\bf{\vec{r}_{P/A}}}
\end{equation}

## Velocity of a point on rigid body translating

The velocity of a point on a rigid body that is translating is given by:

\begin{equation}
{\bf\vec{v}_{P/O}}  = \frac{d{\bf\vec{r}_{P/O}}}{dt} = \frac{d({\bf\vec{r}_{A/O}}+x_{P/A}^*{\bf\hat{i}'} + y_{P/A}^*{\bf\hat{j}'})}{dt} = \frac{d{\bf\vec{r}_{A/O}}}{dt}+\frac{d(x_{P/A}^*{\bf\hat{i}'} + y_{P/A}^*{\bf\hat{j}'})}{dt} = {\bf\vec{v}_{A/O}} + {\bf\vec{\omega}} \times {\bf{\vec{r}_{P/A}}}
\end{equation}

Below is an example of a body rotating with the angular velocity of $\omega = \pi/10$ rad/s and translating at the velocity of
${\bf\vec{v}} = 0.7 {\bf\hat{i}} + 0.5 {\bf\hat{j}}$ m/s. The red arrow indicates the velocity of the geometric center of the body and the blue arrow indicates the velocity of the lower point of the body

```python
import numpy as np
import matplotlib.pyplot as plt
%matplotlib notebook
from matplotlib.animation import FuncAnimation
from matplotlib.patches import FancyArrowPatch
t = np.linspace(0,13,10)
omega = np.pi/10 #[rad/s]
voa = np.array([[0.7],[0.5]]) # velocity of center of mass
fig = plt.figure()
plt.grid()
ax = fig.add_axes([0, 0, 1, 1])
ax.axis("on")
plt.rcParams['figure.figsize']=5,5
def run(i):
    ax.clear()
    theta = omega * t[i]
    phi = np.linspace(0,2*np.pi,100)
    B = np.squeeze(np.array([[2*np.cos(phi)],[6*np.sin(phi)]]))
    Baum = np.vstack((B,np.ones((1,np.shape(B)[1]))))
    roa = voa * t[i]
    R = np.array([[np.cos(theta), -np.sin(theta)],[np.sin(theta), np.cos(theta)]])
    T = np.vstack((np.hstack((R,roa)), np.array([0,0,1])))
    BRot = R@B
    BRotTr = T@Baum

    plt.plot(BRotTr[0,:],BRotTr[1,:], roa[0], roa[1],'.')
    plt.fill(BRotTr[0,:],BRotTr[1,:], 'g')

    vVoa = FancyArrowPatch(np.array([float(roa[0]), float(roa[1])]),
                           np.array([float(roa[0]+5*voa[0]),
                                       float(roa[1]+5*voa[1])]), mutation_scale=20,
                           lw=2, arrowstyle="->", color="r", alpha=1)
    ax.add_artist(vVoa)

    element = 75
    Vp = np.array([voa[0]-omega*BRot[1,element], voa[1]+omega*BRot[0,element]])

    vVP = FancyArrowPatch(np.array([float(BRotTr[0,element]), float(BRotTr[1,element])]),
                          np.array([float(BRotTr[0,element]+5*Vp[0]),
                                      float(BRotTr[1,element]+5*Vp[1])]),
                          mutation_scale=20,
                          lw=2, arrowstyle="->", color="b", alpha=1)
    ax.add_artist(vVP)

    plt.xlim((-10, 20))
    plt.ylim((-10, 20))

ani = FuncAnimation(fig, run, frames = 50,repeat=False,  interval =500)
plt.show()

```

    <IPython.core.display.Javascript object>

## Acceleration of  a point on a rigid body

The acceleration of a point on a rigid body is obtained by deriving the previous expression:

\begin{equation}
{\bf\vec{a}_{P/O}} = {\bf\vec{a}_{A/O}} + \dot{\bf\vec{\omega}} \times {\bf{\vec{r}_{P/A}}} + {\bf\vec{\omega}} \times {\bf{\vec{v}_{P/A}}} = {\bf\vec{a}_{A/O}} + \dot{\bf\vec{\omega}} \times {\bf{\vec{r}_{P/A}}} + {\bf\vec{\omega}} \times ({\bf\vec{\omega}} \times {\bf{\vec{r}_{P/A}}}) = {\bf\vec{a}_{A/O}} + \ddot{\theta}\bf\hat{k} \times {\bf{\vec{r}_{P/A}}} - \dot{\theta}^2{\bf{\vec{r}_{P/A}}}
\end{equation}

The acceleration has three terms:

- ${\bf\vec{a}_{A/O}}$ -- the acceleration of the point O.
- $\ddot{\theta}\bf\hat{k} \times {\bf{\vec{r}_{P/A}}}$ -- the acceleration of the point P due to the angular acceleratkion of the body.
- $- \dot{\theta}^2{\bf{\vec{r}_{P/A}}}$ -- the acceleration of the point P due to the angular velocity of the body. It is known as centripetal acceleration.

Below is an example of a rigid  body with an angular acceleration of $\alpha = \pi/150$ rad/s$^2$ and initial angular velocity of $\omega_0 = \pi/100$ rad/s. Consider also that the center of the body accelerates with ${\bf\vec{a}} = 0.01{\bf\hat{i}} + 0.05{\bf\hat{j}}$, starting from rest.

```python
t = np.linspace(0, 20, 40)

alpha = np.pi/150 #[rad/s^2] angular acceleration

omega0 = np.pi/100 #[rad/s] angular velocity

aoa = np.array([[0.01],[0.05]]) # linear acceleration

fig = plt.figure()
plt.grid()
ax = fig.add_axes([0, 0, 1, 1])
ax.axis("on")
plt.rcParams['figure.figsize']=5,5
theta = 0
omega = 0
def run(i):
    ax.clear()
    phi = np.linspace(0,2*np.pi,100)
    B = np.squeeze(np.array([[2*np.cos(phi)],[6*np.sin(phi)]]))
    Baum = np.vstack((B,np.ones((1,np.shape(B)[1]))))

    omega = alpha*t[i]+omega0 #[rad/s] angular velocity

    theta = alpha/2*t[i]**2 + omega0*t[i] # [rad] angle

    voa = aoa*t[i] # linear velocity

    roa = aoa/2*t[i]**2 # position of the center of the body

    R = np.array([[np.cos(theta), -np.sin(theta)],[np.sin(theta), np.cos(theta)]])
    T = np.vstack((np.hstack((R,roa)), np.array([0,0,1])))
    BRot = R@B
    BRotTr = T@Baum

    plt.plot(BRotTr[0,:],BRotTr[1,:], roa[0], roa[1],'.')
    plt.fill(BRotTr[0,:],BRotTr[1,:],'g')

    element = 75
    ap = np.array([aoa[0] - alpha*BRot[1,element] - omega**2*BRot[0,element],
                   aoa[1] + alpha*BRot[0,element] - omega**2*BRot[1,element]])
    vVP = FancyArrowPatch(np.array([float(BRotTr[0,element]), float(BRotTr[1,element])]),
                          np.array([float(BRotTr[0,element]+5*ap[0]),
                                      float(BRotTr[1,element]+5*ap[1])]),
                          mutation_scale=20,
                          lw=2, arrowstyle="->", color="b", alpha=1)
    ax.add_artist(vVP)

    plt.xlim((-10, 20))
    plt.ylim((-10, 20))

ani = FuncAnimation(fig, run, frames=50,repeat=False,  interval=500)
plt.show()

```

    <IPython.core.display.Javascript object>

## Problems

1. Solve the problems 16.2.5, 16.2.10, 16.2.11 and 16.2.20 from [Ruina and Rudra's book](http://ruina.tam.cornell.edu/Book/index.html).
2. Solve the problems 17.1.2, 17.1.8, 17.1.9, 17.1.10, 17.1.11 and 17.1.12 from [Ruina and Rudra's book](http://ruina.tam.cornell.edu/Book/index.html).

## Reference

- Ruina A, Rudra P (2019) [Introduction to Statics and Dynamics](http://ruina.tam.cornell.edu/Book/index.html). Oxford University Press.
- Corke P (2017) [Robotics, Vision and Control: Fundamental Algorithms in MATLAB](http://www.petercorke.com/RVC/). 2nd ed. Springer-Verlag Berlin.


# Diagrama de Especiación en Python

En este notebook vamos a ver como podemos calcular la fracción molar de todas las especies generadas en la disociación de un ácido poliprótico a lo largo de la escala de pH

## CALCULOS PARA EL DIAGRAMA DE ESPECIACION
........................................

Estas funciones, dado un conjunto de pkas, calculan las fracciones
molares de cada especie para cada punto en la escala de pH.

Se da como ejemplo particular la especiacion de un ácido diprótico
y luego se extiende a la fórmula general de un ácido poliprótico.

Para un ácido poliprótico definido genericamente como $H_NA$, donde N es el
número de protones capaces de ser intercambiados, si se conocen
sus constantes de disociación puede calcularse la fracción molar
de cada una de las especies con distinto grado de disociación para cada pH.

Llamemos P al polinomio definido como

\begin{equation}
P = [H^+]^N + k_1[H^+]^{(N-1)} + k_1k_2[H^+]^{(N-2)}\ + ... +\ k_1k_2...k_N[H^+]^{(N-N\ =\ 0)}
\end{equation}

y Llamamos $T_k$ a cada término del polinomio, de forma tal que

\begin{equation}
P = T_0 + T_1 + T_2 + ... + T_N
\end{equation}

Vemos que el polinomio tiene N+1 términos, es decir, hay un término en el
polinomio por cada especie en el equilibrio.

Si se trabaja con los balances de masa y las ecuaciones de equilibrio puede
llegarse a la siguiente expresión:

\begin{equation}
X_{H_kA} = \frac{T_{N-k}}{P} \ ;\ donde\ k=0,1,2,...,N
\end{equation}

```python
import numpy as np
import pandas as pd
import plotly.express as px
```

### Funciones

A continuación definimos las funciones que nos permitirán calcular la fracción molar de cada especie para un dado pH

```python
def generateColNames(protons):
    colNames = []
    for i in range(0,protons+1):
        if(i == 0):
            colNames.append("A")
        elif(i == 1):
            colNames.append("HA")
        else:
            colNames.append("H"+ str(i) + "A")
    return colNames

def constantFromPka(pka):
    return 10**(-pka)

def poliproticSpeciation(pkas, res=10):
    # Ordeno pkas y obtengo ctes
    pkas = sorted(pkas)
    ks = list(map(constantFromPka, pkas))
    N = len(pkas)
    # Genero los nombres de las especies
    colNames = generateColNames(N)

    # Genero una lista de valores de pH entre 0 y 14
    pHvals = np.linspace(0, 14, res)
    data = {"pH": pHvals}
    for name in colNames:
        data[name] = []
    # Genero los coeficientes del polinomio
    # [1, k1, k1*k2,...]
    coefs = [1]
    for k in ks:
        coefs.append(coefs[-1]*k)

    # calculo las fracciones molares de cada especie:
    for pH in pHvals:
        # Obtengo la conc de protones al pH dado
        H = 10**(-pH)
        # Calculo los términos del polinomio para ese pH
        pTerms = []
        for i, coef in enumerate(coefs):
            pTerms.append(coef*(H**(N-i)))
        # Uso la fórmula para calcular la fracción
        # molar de cada especie
        for j, name in enumerate(colNames):
            data[name].append(pTerms[N-j]/sum(pTerms))
    return data, colNames
```

### Cálculos
Reemplaza los siguientes valores con los del ácido de interés

```python
pkas = [2,4,6]
res  =1000
```

```python
data, colNames = poliproticSpeciation(pkas, res)
df = pd.DataFrame(data = data)
df
```

      pH
      A
      HA
      H2A
      H3A

      0
      0.000000
      9.900980e-13
      9.900980e-07
      9.900980e-03
      9.900980e-01

      1
      0.014014
      1.090383e-12
      1.055760e-06
      1.022236e-02
      9.897766e-01

      2
      0.028028
      1.200814e-12
      1.125765e-06
      1.055406e-02
      9.894448e-01

      3
      0.042042
      1.322414e-12
      1.200398e-06
      1.089641e-02
      9.891024e-01

      4
      0.056056
      1.456311e-12
      1.279965e-06
      1.124973e-02
      9.887490e-01

      ...
      ...
      ...
      ...
      ...
      ...

      995
      13.943944
      1.000000e+00
      1.137774e-08
      1.294530e-18
      1.472883e-30

      996
      13.957958
      1.000000e+00
      1.101646e-08
      1.213624e-18
      1.336984e-30

      997
      13.971972
      1.000000e+00
      1.066665e-08
      1.137774e-18
      1.213624e-30

      998
      13.985986
      1.000000e+00
      1.032795e-08
      1.066665e-18
      1.101646e-30

      999
      14.000000
      1.000000e+00
      1.000000e-08
      1.000000e-18
      1.000000e-30

1000 rows × 5 columns

## Gráfico

```python
fig = px.line(df, x="pH", y=colNames)

fig.update_layout(
    title="Diagrama de especiación",
    xaxis_title="pH",
    yaxis_title="Fracción molar",
    legend_title="Especies",
)
fig.show()
```

```python

```


```
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import tensorflow as tf
import warnings
from matplotlib.colors import ListedColormap
from mlxtend.plotting import plot_decision_regions
from pylab import rcParams
from sklearn.datasets import make_circles, make_moons
from sklearn.model_selection import train_test_split
from tensorflow.keras.layers import Dense
from tensorflow.keras.models import Sequential
```

```
np.set_printoptions(suppress=True)
zero_one_colourmap = ListedColormap(('white', 'red'))
rcParams['figure.figsize'] = 14, 7
rcParams['axes.facecolor'] = '#383838'
```

```
X, y = make_circles(n_samples = 1000,
                    factor=0.85,
                    random_state=2021,
                    noise=0.1)
```

```
plt.scatter(X[:,0],X[:,1],
            c=y, s=100,
            cmap = zero_one_colourmap)
plt.show()
```

```
X_train, X_test, y_train, y_test = train_test_split(X, y,
                                                    test_size=0.33,
                                                    random_state=42)
```

```
def get_model(inp_activation):
  model = Sequential()
  model.add(Dense(10,input_dim=2, activation=inp_activation))
  model.add(Dense(10, activation = inp_activation))
  model.add(Dense(10, activation = inp_activation))
  model.add(Dense(10, activation = inp_activation))
  model.add(Dense(10, activation = inp_activation))
  model.add(Dense(10, activation = inp_activation))
  model.add(Dense(10, activation = inp_activation))
  model.add(Dense(10, activation = inp_activation))
  model.add(Dense(10, activation = inp_activation))
  model.add(Dense(10, activation = inp_activation))
  model.add(Dense(10, activation = inp_activation))
  model.add(Dense(1, activation="sigmoid"))
  model.compile(loss='binary_crossentropy',
                optimizer='adam',
                metrics=['accuracy'])
  return model
```

\begin{equation}w_{n e w}=w_{o l d}-\alpha * \frac{\delta Loss}{\delta w}\end{equation}

\begin{equation} \frac{\delta Loss}{\delta w} = \frac{w_{old} - w_{new}}{\alpha } \end{equation}

```
def change_in_weight_gradient(old_weight, new_weight, learning_rate):
  gradient = (old_weight - new_weight)/ learning_rate
  pct_change_weight = abs(100*(old_weight - new_weight)/ old_weight)
  return gradient, pct_change_weight
```

# Sigmoid Model

```
sigmoid_model = get_model("sigmoid")
```

```
sigmoid_model.summary()
```

    Model: "sequential_2"
    _________________________________________________________________
    Layer (type)                 Output Shape              Param #
    =================================================================
    dense_24 (Dense)             (None, 10)                30
    _________________________________________________________________
    dense_25 (Dense)             (None, 10)                110
    _________________________________________________________________
    dense_26 (Dense)             (None, 10)                110
    _________________________________________________________________
    dense_27 (Dense)             (None, 10)                110
    _________________________________________________________________
    dense_28 (Dense)             (None, 10)                110
    _________________________________________________________________
    dense_29 (Dense)             (None, 10)                110
    _________________________________________________________________
    dense_30 (Dense)             (None, 10)                110
    _________________________________________________________________
    dense_31 (Dense)             (None, 10)                110
    _________________________________________________________________
    dense_32 (Dense)             (None, 10)                110
    _________________________________________________________________
    dense_33 (Dense)             (None, 10)                110
    _________________________________________________________________
    dense_34 (Dense)             (None, 10)                110
    _________________________________________________________________
    dense_35 (Dense)             (None, 1)                 11
    =================================================================
    Total params: 1,141
    Trainable params: 1,141
    Non-trainable params: 0
    _________________________________________________________________

```
sigmoid_model_first_layer_weights = sigmoid_model.get_weights()[0][0]
```

```
sigmoid_model_first_layer_weights
```

    array([-0.16459507, -0.64107174, -0.4518722 ,  0.5953385 , -0.27797413,
           -0.00229126, -0.16018331, -0.5528967 , -0.39661735, -0.55106837],
          dtype=float32)

```
sigmoid_history = sigmoid_model.fit(X_train, y_train, epochs = 1,
                            validation_data = (X_test, y_test))
```

    21/21 [==============================] - 0s 10ms/step - loss: 0.6932 - accuracy: 0.5030 - val_loss: 0.6941 - val_accuracy: 0.4758

```
sigmoid_model_first_layer_weights_updated = sigmoid_model.get_weights()[0][0]
```

```
sigmoid_model_first_layer_weights_updated
```

    array([-0.16458306, -0.6410732 , -0.45187178,  0.59536225, -0.2779746 ,
           -0.00230035, -0.16016504, -0.55289865, -0.39662656, -0.5510717 ],
          dtype=float32)

```
s_gradient, s_weight_change = change_in_weight_gradient(sigmoid_model_first_layer_weights,
                                                        sigmoid_model_first_layer_weights_updated,
                                                        sigmoid_model.optimizer.get_config()["learning_rate"])
```

```
s_gradient
```

    array([-0.01201033,  0.00149012, -0.00041723, -0.02372265,  0.00047684,
            0.00908785, -0.01826882,  0.00196695,  0.00920892,  0.00333786],
          dtype=float32)

```
s_weight_change
```

    array([0.0072969 , 0.00023244, 0.00009233, 0.00398473, 0.00017154,
           0.39663056, 0.01140495, 0.00035575, 0.00232186, 0.00060571],
          dtype=float32)

# Relu Model

```
activation_model = get_model("relu")
```

```
activation_model_old_first_layer_weight = activation_model.get_weights()[0][0]
activation_model_old_first_layer_weight
```

    array([-0.27921528,  0.4221422 , -0.03037506, -0.5281516 , -0.19517076,
            0.25885516, -0.08015287, -0.6070751 ,  0.5501974 ,  0.49056894],
          dtype=float32)

```
activation_history = activation_model.fit(X_train, y_train, epochs = 1,
                                          validation_data = (X_test, y_test))
```

    21/21 [==============================] - 0s 10ms/step - loss: 0.6931 - accuracy: 0.5090 - val_loss: 0.6930 - val_accuracy: 0.4758

```
activation_model_updated_first_layer_weight = activation_model.get_weights()[0][0]
```

```
activation_model_updated_first_layer_weight
```

    array([-0.2782503 ,  0.41516545, -0.03877841, -0.5319206 , -0.19781157,
            0.25725055, -0.07946245, -0.602711  ,  0.5458591 ,  0.49701372],
          dtype=float32)

```
relu_gradient, relu_weight_change = change_in_weight_gradient(activation_model_old_first_layer_weight,
                                                              activation_model_updated_first_layer_weight,
                                                              activation_model.optimizer.get_config()["learning_rate"])
```

```
relu_gradient
```

    array([-0.96496934,  6.976753  ,  8.403349  ,  3.7690399 ,  2.6408134 ,
            1.6046165 , -0.6904229 , -4.3640733 ,  4.338324  , -6.444782  ],
          dtype=float32)

```
relu_weight_change
```

    array([ 0.34560052,  1.6527022 , 27.66529   ,  0.7136285 ,  1.3530785 ,
            0.61988974,  0.8613827 ,  0.7188688 ,  0.78850317,  1.3137363 ],
          dtype=float32)

```

```


# Zombie SIR Differential Equations

In this notebook we will model the spread of disease using the Kermack-McKendrick Model which is a compartment model. This is inspired by the movies of George A Romero such as "Night of the Living Dead."

```python
## Library
import numpy as np
import math
import matplotlib.pyplot as plt
```

The model is a system of three non-linear first order differential equations:
\begin{equation}
\begin{array}{cl}
\frac{dS}{dt}=-\frac{\beta BS}{N},\\
\frac{dB}{dt}=\frac{\beta BS}{N}-\gamma B,\\
\frac{dZ}{dt}=\gamma B
\end{array}
\end{equation}
where
S is the people who are susceptible (not zombie) in the population,
B is the number of people bitten by zombies,
Z is the number of Zombies, and
N is the sum of these three $$ N=S(t_0)+B(t_0)+Z(t_0).$$
$\beta$ is the infection rate, and $\gamma$ is the time to convert to zombie.
$$ R_{0}={\frac{\beta }{\gamma }},$$

the so-called basic reproduction number (also called basic reproduction ratio).

```python
beta=4
gamma=2
```

## Discrete Domain
To numerically approximate the solution we discretise the continuous domain using a step size, $h=0.001$,
which gives
\begin{equation}
t_i=0+i h,
\end{equation}
for $i=0,...n$.
The figure below illustrate the discrete domain.

```python
h=0.001
t=np.arange(0,10+h/2,h)
fig = plt.figure(1,figsize=(5,4))
ax = fig.add_subplot(1,1,1)
ax.plot(t,0*t,'o:',color='k')
ax.set_xlabel('Time (yrs)')
plt.title('Discrete Time Domain')
plt.show()
```

```python
n=len(t) # Number of Steps
S=np.zeros(n) # Numerical Solution Sus.
S[0]=99 # Intial Hare population
B=np.zeros(n) # Numerical Solution for Bitten
B[0]=1# Initial Bitten population
Z=np.zeros(n) # Numerical Solution Zombie
Z[0]=0# Initial Zombie population

N=S[0]+B[0]+Z[0]

```

We numerically approximate the differential equation using Eulers method to give the difference equation
\begin{equation}
S[i+1] = S[i]+ h*\big(-beta*S[i]*B[i]/N \big)
\end{equation}

\begin{equation}
B[i+1] = B[i]+ h*\big(\beta*S[i]*B[i] /N- gamma B[i] \big)
\end{equation}

\begin{equation}
Z[i+1] = Z[i]+ h*\big(gamma B[i] \big)
\end{equation}

where $h$ is the stepsize and  $S[i]$, $B[i]$ and $Z[i]$ are the numerical approximation of the susceptible $S(t_i)$, bitten $I(t_i)$ and zombies $Z(t_i)$ at time $t_i$, for $i=0,...,n$

```python
for i in range (0,n-1):
    S[i+1]=S[i]+h*(-beta*S[i]*B[i]/N)
    B[i+1]=B[i]+h*(beta*S[i]*B[i]/N-gamma*B[i])
    Z[i+1]=Z[i]+h*gamma*B[i]

```

## Results

```python
## Plotting Figure
fig = plt.figure(1,figsize=(10,4))

ax = fig.add_subplot(1,1,1)

ax.plot(t,S,':',color='green',label='Sus')
ax.plot(t,B,':',color='red',label='Bitten')
ax.plot(t,Z,':',color='blue',label='Zombie')

ax.legend()
ax.set_xlabel('Time (yrs)')
ax.set_ylabel('Population (tens of thousands)')
#ax.legend(loc='best')
plt.tight_layout()
plt.subplots_adjust(top=0.85)
plt.show()

```

## Phase Plane Plot

```python
## Plotting Figure
fig = plt.figure(1,figsize=(5,5))

ax = fig.add_subplot(1,1,1)

ax.plot(B,S,'-',color='black',label='B vs S')
ax.plot(B,Z,'-',color='red',label='B vs Z')

ax.set_xlabel('Bitten Population (tens of thousands)')
ax.set_ylabel('Population (tens of thousands)')
ax.legend(loc='best')
plt.tight_layout()
plt.ylim(0,110)
plt.xlim(0,110)

plt.show()
```

# References

[1] Weisstein, Eric W. "Kermack-McKendrick Model." From MathWorld--A Wolfram Web Resource. https://mathworld.wolfram.com/Kermack-McKendrickModel.html

[2]  Wikipedia contributors. (2021, February 28). Compartmental models in epidemiology. In Wikipedia, The Free Encyclopedia. Retrieved 18:12, March 15, 2021, from https://en.wikipedia.org/w/index.php?title=Compartmental_models_in_epidemiology&oldid=1009414539

[3] Wikipedia contributors. (2021, March 3). George A. Romero. In Wikipedia, The Free Encyclopedia. Retrieved 11:20, March 16, 2021, from https://en.wikipedia.org/w/index.php?title=George_A._Romero&oldid=1010119184

```python

```


###### Content under Creative Commons Attribution license CC-BY 4.0, code under MIT license (c)2015 L.A. Barba, Pi-Yueh Chuang.

# Exercise: Derivation of the vortex-source panel method

The potential at location $(x, y)$ induced by an uniform flow, a source sheet, and a vortex sheet can be represented as

$$
\begin{equation}
\begin{split}
\phi(x, y)
&= \phi_{uniform\ flow}(x, y) \\
&+ \phi_{source\ sheet}(x, y) + \phi_{vortex\ sheet}(x, y)
\end{split}
\end{equation}
$$

That is

$$
\begin{equation}
\begin{split}
\phi(x, y) &= xU_{\infty}\cos(\alpha) + yU_{\infty}\sin(\alpha) \\
&+
\frac{1}{2\pi} \int_{sheet} \sigma(s)\ln\left[(x-\xi(s))^2+(y-\eta(s))^2\right]^{\frac{1}{2}}ds \\
&-
\frac{1}{2\pi} \int_{sheet} \gamma(s)\tan^{-1} \frac{y-\eta(s)}{x-\xi(s)}ds
\end{split}
\end{equation}
$$

where $s$ is local coordinate on the sheet, and $\xi(s)$ and $\eta(s)$ are coordinate of the infinite source and vortex on the sheet. In the above equation, we assume the source sheet and the vortex sheet overlap.

------------------------------------------------------

### Q1:

If we discretize the sheet into $N$ panels, re-write the above equation using discretized integral. Assume $l_j$ represents the length of the panel $j$. And so that

$$
\begin{equation}
\left\{
\begin{array}{l}
\xi_j(s)=x_j-s\sin\beta_j \\
\eta_j(s)=y_j+s\cos\beta_j
\end{array}
,\ \ \
0\le s \le l_j
\right.
\end{equation}
$$

The following figure shows the panel $j$:

<center>  </center>

HINT: for example, consider the integral $\int_0^L f(x) dx$, if we discretize the domain $0\sim L$ into 3 panels, the integral can be writen as:

$$
\int_0^L f(x) dx = \int_0^{L/3} f(x)dx+\int_{L/3}^{2L/3} f(x)dx+\int_{2L/3}^{L} f(x)dx \\
= \sum_{j=1}^3 \int_{l_j}f(x)dx
$$

----------------------------

Now let's assume

1. $\sigma_j(s) = constant = \sigma_j$
2. $\gamma_1(s) = \gamma_2(s) = ... = \gamma_N(s) = \gamma$

------------------------------------------------

### Q2:

Apply the above assumption into the equation of $\phi(x, y)$ you derived in Q1.

---------------------------

The normal velocity $U_n$ can be derived from the chain rule:

$$
\begin{equation}
\begin{split}
U_n &= \frac{\partial \phi}{\partial \vec{n}} \\
&=
\frac{\partial \phi}{\partial x}\frac{\partial x}{\partial \vec{n}}
+
\frac{\partial \phi}{\partial y}\frac{\partial y}{\partial \vec{n}} \\
&=
\frac{\partial \phi}{\partial x}\nabla x\cdot \vec{n}
+
\frac{\partial \phi}{\partial y}\nabla y\cdot \vec{n} \\
&=
\frac{\partial \phi}{\partial x}n_x
+
\frac{\partial \phi}{\partial y}n_y
\end{split}
\end{equation}
$$

The tangential velocity can also be obtained using the same technique. So we can have the normal and tangential velocity at the point $(x, y)$ using:

$$
\begin{equation}
\left\{
\begin{array}{l}
U_n(x, y)=\frac{\partial \phi}{\partial x}(x, y) n_x(x, y)+\frac{\partial \phi}{\partial y}(x, y) n_y(x, y) \\
U_t(x, y)=\frac{\partial \phi}{\partial x}(x, y) t_x(x, y)+\frac{\partial \phi}{\partial y}(x, y) t_y(x, y)
\end{array}
\right.
\end{equation}
$$

-------------------------------------

### Q3:

Using the above equation, derive the $U_n(x,y)$ and $U_t(x,y)$ from the equation you obtained in Q2.

-----------------------------------------

### Q4:

Consider the normal velocity at the center of $i$-th panel, i.e., $(x_{c,i}, y_{c,i})$, after replacing $(x_{c,i}, y_{c,i})$ with $(x, y)$ in the equation you derived in the Q3, we can re-write the equation in matrix form:

$$
\begin{equation}
\begin{split}
U_n(x_{c,i}, y_{c,i}) &= U_{n,i} \\
&= b^n_i + \left[\begin{matrix} A^n_{i1} && A^n_{i2} && ... && A^n_{iN}\end{matrix}\right]\left[\begin{matrix} \sigma_1 \\ \sigma_2 \\ \vdots \\ \sigma_N \end{matrix}\right] + \left(\sum_{j=1}^N B^n_{ij}\right)\gamma \\
&= b^n_i + \left[\begin{matrix} A^n_{i1} && A^n_{i2} && ... && A^n_{iN} && \left(\sum_{j=1}^N B^n_{ij}\right) \end{matrix}\right]\left[\begin{matrix} \sigma_1 \\ \sigma_2 \\ \vdots \\ \sigma_N \\ \gamma \end{matrix}\right]
\end{split}
\end{equation}
$$

$$
\begin{equation}
\begin{split}
U_t(x_{c,i}, y_{c,i}) &= U_{t,i} \\
&= b^t_i + \left[\begin{matrix} A^t_{i1} && A^t_{i2} && ... && A^t_{iN}\end{matrix}\right]\left[\begin{matrix} \sigma_1 \\ \sigma_2 \\ \vdots \\ \sigma_N \end{matrix}\right] + \left(\sum_{j=1}^N B^t_{ij}\right)\gamma \\
&= b^t_i + \left[\begin{matrix} A^t_{i1} && A^t_{i2} && ... && A^t_{iN} && \left(\sum_{j=1}^N B^t_{ij}\right) \end{matrix}\right]\left[\begin{matrix} \sigma_1 \\ \sigma_2 \\ \vdots \\ \sigma_N \\ \gamma \end{matrix}\right]
\end{split}
\end{equation}
$$

What are the $b^n_i$, $A^n_{ij}$, $B^n_{ij}$, $b^t_i$, $A^t_{ij}$, and $B^t_{ij}$?

-----------------------

Given the fact that (from the Fig. 1)

$$
\begin{equation}
\left\{\begin{matrix} \vec{n}_i=n_{x,i}\vec{i}+n_{y,i}\vec{j} = \cos(\beta_i)\vec{i}+\sin(\beta_i)\vec{j} \\ \vec{t}_i=t_{x,i}\vec{i}+t_{y,i}\vec{j} = -\sin(\beta_i)\vec{i}+\cos(\beta_i)\vec{j} \end{matrix}\right.
\end{equation}
$$

we have

$$
\begin{equation}
\left\{
\begin{matrix}
n_{x,i}=t_{y,i} \\
n_{y,i}=-t_{x,i}
\end{matrix}
\right.
,\ or\
\left\{
\begin{matrix}
t_{x,i}=-n_{y,i} \\
t_{y,i}=n_{x,i}
\end{matrix}
\right.
\end{equation}
$$

-----------------------

### Q5:

Applying the above relationship between $\vec{n}_i$ and $\vec{t}_i$ to your answer of the Q4, you should find that relationships exist between $B^n_{ij}$ and $A^t_{ij}$ and between $B^t_{ij}$ and $A^n_{ij}$. This means, in your codes, you don't have to actually calculate the $B^n_{ij}$ and $B^t_{ij}$. What are the relationship?

-------------------------

Now, note that when $i=j$, there is a singular point in the integration domain when calculating $A^n_{ii}$ and $A^t_{ii}$. This singular point occurs when $s=l_i/2$, i.e., $\xi_i(l_i/2)=x_{c,i}$ and $\eta_i(l_i/2)=y_{c,i}$. This means we need to calculate $A^n_{ii}$ and $A^t_{ii}$ analytically.

--------------------------

### Q6:

What is the exact values of $A^n_{ii}$ and $A^t_{ii}$?

------------------------------

In our problem, there are $N+1$ unknowns, that is, $\sigma_1, \sigma_2, ..., \sigma_N, \gamma$. We'll need $N+1$ linear equations to solve the unknowns. The first $N$ linear equations can be obtained from the non-penetration condition on the center of each panel. That is

$$
\begin{equation}
\begin{split}
U_{n,i} &= 0 \\
&= b^n_i + \left[\begin{matrix} A^n_{i1} && A^n_{i2} && ... && A^n_{iN} && \left(\sum_{j=1}^N B^n_{ij}\right) \end{matrix}\right]\left[\begin{matrix} \sigma_1 \\ \sigma_2 \\ \vdots \\ \sigma_N \\ \gamma \end{matrix}\right] \\
&,\ \ for\ i=1\sim N
\end{split}
\end{equation}
$$

or

$$
\begin{equation}
\begin{split}
&\left[\begin{matrix} A^n_{i1} && A^n_{i2} && ... && A^n_{iN} && \left(\sum_{j=1}^N B^n_{ij}\right) \end{matrix}\right]\left[\begin{matrix} \sigma_1 \\ \sigma_2 \\ \vdots \\ \sigma_N \\ \gamma \end{matrix}\right] =-b^n_i \\
&,\ \ for\ i=1\sim N
\end{split}
\end{equation}
$$

For the last equation, we use Kutta-condition to obtain that.

$$
\begin{equation}
U_{t,1} = - U_{t,N}
\end{equation}
$$

----------------------

### Q7:

Apply the matrix form of the $U_{t,i}$ and $U_{t,N}$ to the Kutta-condition and obtain the last linear equation. Re-arrange the equation so that unknowns are always on the LHS while the knowns on RHS.

---------------------

### Q8:

Now you have $N+1$ linear equations and can solve the $N+1$ unknowns. Try to combine the first $N$ linear equations and the last one (i.e. the Kutta-condition) in the Q7 and obtain the matrix form of the whole system of linear equations.

----------------------------

The equations can be solved now! This is the vortex-source panel method.

--------------------
Please ignore the cell below. It just loads our style for the notebook.

```python
from IPython.core.display import HTML
def css_styling(filepath):
    styles = open(filepath, 'r').read()
    return HTML(styles)
css_styling('../styles/custom.css')
```

<link href='http://fonts.googleapis.com/css?family=Fenix' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Source+Code+Pro:300,400' rel='stylesheet' type='text/css'>
<style>

@font-face {
    font-family: "Computer Modern";
    src: url('http://mirrors.ctan.org/fonts/cm-unicode/fonts/otf/cmunss.otf');
}

#notebook_panel { /* main background */
    background: rgb(245,245,245);
}

div.cell { /* set cell width */
    width: 750px;
}

div #notebook { /* centre the content */
    background: #fff; /* white background for content */
    width: 1000px;
    margin: auto;
    padding-left: 0em;
}

#notebook li { /* More space between bullet points */
margin-top:0.8em;
}

/* draw border around running cells */
div.cell.border-box-sizing.code_cell.running {
    border: 1px solid #111;
}

/* Put a solid color box around each cell and its output, visually linking them*/
div.cell.code_cell {
    background-color: rgb(256,256,256);
    border-radius: 0px;
    padding: 0.5em;
    margin-left:1em;
    margin-top: 1em;
}

div.text_cell_render{
    font-family: 'Alegreya Sans' sans-serif;
    line-height: 140%;
    font-size: 125%;
    font-weight: 400;
    width:600px;
    margin-left:auto;
    margin-right:auto;
}

/* Formatting for header cells */
.text_cell_render h1 {
    font-family: 'Alegreya Sans', sans-serif;
    font-style:regular;
    font-weight: 200;
    font-size: 50pt;
    line-height: 100%;
    color:#CD2305;
    margin-bottom: 0.5em;
    margin-top: 0.5em;
    display: block;
}

.text_cell_render h2 {
    font-family: 'Fenix', serif;
    font-size: 22pt;
    line-height: 100%;
    margin-bottom: 0.1em;
    margin-top: 0.3em;
    display: block;
}

.text_cell_render h3 {
    font-family: 'Fenix', serif;
    margin-top:12px;
    font-size: 16pt;
    margin-bottom: 3px;
    font-style: regular;
}

.text_cell_render h4 {    /*Use this for captions*/
    font-family: 'Fenix', serif;
    font-size: 2pt;
    text-align: center;
    margin-top: 0em;
    margin-bottom: 2em;
    font-style: regular;
}

.text_cell_render h5 {  /*Use this for small titles*/
    font-family: 'Alegreya Sans', sans-serif;
    font-weight: 300;
    font-size: 16pt;
    color: #CD2305;
    font-style: italic;
    margin-bottom: .5em;
    margin-top: 0.5em;
    display: block;
}

.text_cell_render h6 { /*use this for copyright note*/
    font-family: 'Source Code Pro', sans-serif;
    font-weight: 300;
    font-size: 9pt;
    line-height: 100%;
    color: grey;
    margin-bottom: 1px;
    margin-top: 1px;
}

.CodeMirror{
        font-family: "Source Code Pro";
        font-size: 90%;
}

.warning{
    color: rgb( 240, 20, 20 )
    }
</style>


###### Content under Creative Commons Attribution license CC-BY 4.0, code under MIT license (c)2014 L.A. Barba, C.D. Cooper, G.F. Forsyth.

# Reaction-diffusion model

This Jupyter Notebook presents the context and set-up for the coding assignment of Module 4: _Spreading out: Diffusion problems_, of the course [**"Practical Numerical Methods with Python"**](https://github.com/numerical-mooc/numerical-mooc) (a.k.a., numericalmooc).

So far in this module, we've studied diffusion in 1D and 2D. Now it's time to add in some more interesting physics.  You'll study a model represented by *reaction-diffusion* equations.  What are they?  The name says it all—it's a system that has the physics of diffusion but also has some kind of reaction that adds different behaviors to the solution.

We're going to look at the _Gray-Scott model_, which simulates the interaction of two generic chemical species reacting and ... you guessed it ... diffusing! Some amazing patterns can emerge with simple reaction models, eerily reminiscent of patterns formed in nature. It's fascinating! Check out this simulation by Karl Sims posted on You Tube ... it looks like a growing coral reef, doesn't it?

```python
from IPython.display import YouTubeVideo
YouTubeVideo('8dTmUr5qKvI')

```

## Gray-Scott model

The Gray-Scott model represents the reaction and diffusion of two generic chemical species, $U$ and $V$, whose concentration at a point in space is represented by variables $u$ and $v$. The model follows some simple rules.

* Each chemical _diffuses_ through space at its own rate.
* Species $U$ is added at a constant feed rate into the system.
* Two units of species V can 'turn' a unit of species U into V: $\; 2V+U\rightarrow 3V$
* There's a constant kill rate removing species $V$.

This model results in the following system of partial differential equations for the concentrations $u(x,y,t)$ and $v(x,y,t)$ of both chemical species:

$$
\begin{align}
\frac{\partial u}{\partial t} &= D_u \nabla ^2 u - uv^2 + F(1-u)\\
\frac{\partial v}{\partial t} &= D_v \nabla ^2 v + uv^2 - (F + k)v
\end{align}
$$

You should see some familiar terms, and some unfamiliar ones.  On the left-hand side of each equation, we have the time rate of change of the concentrations. The first term on the right of each equation correspond to the spatial diffusion of each concentration, with $D_u$ and $D_v$ the respective rates of diffusion.

In case you forgot, the operator $\nabla ^2$ is the Laplacian:

$$
\nabla ^2 u = \frac{\partial ^2 u}{\partial x^2} + \frac{\partial ^2 u}{\partial y^2}
$$

The second term on the right-hand side of each equation corresponds to the reaction. You see that this term decreases $u$ while it increases $v$ in the same amount: $uv^2$.  The reaction requires one unit of $U$ and two units of $V$,  resulting in a reaction rate proportional to the concentration $u$ and to the square of the concentration $v$. This result derives from the _law of mass action_, which we can explain in terms of probability: the odds of finding one molecule of species $U$ at a point in space is proportional to the concentration $u$, while the odds of finding two molecules of $V$ is proportional to the concentration squared, $v^2$. We assume here a reaction rate constant equal to $1$, which just means that the model is non-dimensionalized in some way.

The final terms in the two equations are the "feed" and "kill" rates, respectively: $F(1-u)$ replenishes the species $U$ (which would otherwise run out, as it is being turned into $V$ by the reaction); $-(F+k)v$ is diminishing the species $V$ (otherwise the concentration $v$ would simply increase without bound).

The values of $F$ and $k$ are chosen parameters and part of the fun of this assignment is to change these values, together with the diffusion constants, and see what happens.

### Problem setup

The system is represented by two arrays, `U` and `V`, holding the discrete values of the concentrations $u$ and $v$, respectively.  We start by setting `U = 1` everywhere and `V = 0` everywhere, then introduce areas of difference, as initial conditions.  We then add a little noise to the whole system to help the $u$ and $v$ reactions along.

Below is the code segment we used to generate the initial conditions for `U` and `V`.

**NOTE**: *DO NOT USE THIS CODE IN YOUR ASSIGNMENT*.
We are showing it here to help you understand how the system is constructed.  However, you _must use the data we've supplied below_ as your starting condition or your answers will not match those that the grading system expects.

```[Python]
num_blocks = 30
randx = numpy.random.randint(1, nx - 1, num_blocks)
randy = numpy.random.randint(1, nx - 1, num_blocks)
U = numpy.ones((n, n))
V = numpy.zeros((n, n))

r = 10
U[:, :] = 1.0

for i, j in zip(randx, randy):
    U[i - r:i + r, j - r:j + r] = 0.50
    V[i - r:i + r, j - r:j + r] = 0.25

U += 0.05 * numpy.random.random((n, n))
V += 0.05 * numpy.random.random((n, n))
```

## Your assignment

* Discretize the reaction-diffusion equations using forward-time/central-space and assume that $\Delta x = \Delta y = \delta$.

* For your time step, set
$$
\Delta t = \frac{9}{40}\frac{\delta^2}{\max(D_u, D_v)}
$$

* Use zero Neumann boundary conditions on all sides of the domain.

You should use the initial conditions and constants listed in the cell below. They correspond to the following domain:

* Grid of points with dimension `192x192` points
* Domain is $5{\rm m} \times 5{\rm m}$
* Final time is $8000{\rm s}$.

```python
import numpy
from matplotlib import pyplot, cm
%matplotlib inline
```

```python
# Set spatial parameters.
Lx, Ly = 5.0, 5.0  # domain dimensions
nx, ny = 192, 192  # number of points in each direction
dx, dy = Lx / (nx - 1), Ly / (ny - 1)  # grid spacings

# Set parameters of the pattern.
Du, Dv = 0.00016, 0.00008  # rates of diffusion
F, k = 0.035, 0.065  # parameters to feed and kill

# Set temporal parameters.
t = 8000.0  # final time
dt = 9.0 * dx**2 / (40.0 * max(Du, Dv))  # time-step size
nt = int(t / dt)  # number of time steps to compute
```

### Initial condition data files

In order to ensure that you start from the same initial conditions as we do, please download the file [uvinitial.npz](https://github.com/numerical-mooc/numerical-mooc/blob/master/lessons/04_spreadout/data/uvinitial.npz?raw=true) or execute the following code cell (it will download the file using the `urllib.request` module).

```python
import urllib.request

# Download and read the data file.
url = ('https://github.com/numerical-mooc/numerical-mooc/blob/master/'
       'lessons/04_spreadout/data/uvinitial.npz?raw=true')
filepath = 'uvinitial.npz'
urllib.request.urlretrieve(url, filepath);
```

This is a NumPy save-file that contains two NumPy arrays, holding the initial values for `U` and `V`, respectively.  Once you have downloaded the file into your working directory, you can load the data using the following code snippet.

```python
# Read the initial fields from the file.
uvinitial = numpy.load(filepath)
u0, v0 = uvinitial['U'], uvinitial['V']
```

```python
# Plot the initial fields.
fig, ax = pyplot.subplots(ncols=2, figsize=(9.0, 4.0))
ax[0].imshow(u0, cmap=cm.RdBu)
ax[0].axis('off')
ax[1].imshow(v0, cmap=cm.RdBu)
ax[1].axis('off');
```

## Sample Solution

Below is an animated gif showing the results of this solution for a different set of randomized initial block positions.  Each frame of the animation represents 100 time steps.

Just to get your juices flowing!

## Exploring extra patterns

Once you have completed the assignment, you might want to explore a few more of the interesting patterns that can be obtained with the Gray-Scott model.  The conditions below will result in a variety of patterns and should work without any other changes to your existing code.

This pattern is called "Fingerprints."

```python
#Du, Dv, F, k = 0.00014, 0.00006, 0.035, 0.065 # Bacteria 2
#Du, Dv, F, k = 0.00016, 0.00008, 0.060, 0.062 # Coral
#Du, Dv, F, k = 0.00019, 0.00005, 0.060, 0.062 # Fingerprint
#Du, Dv, F, k = 0.00010, 0.00010, 0.018, 0.050 # Spirals
#Du, Dv, F, k = 0.00012, 0.00008, 0.020, 0.050 # Spirals Dense
#Du, Dv, F, k = 0.00010, 0.00016, 0.020, 0.050 # Spirals Fast
#Du, Dv, F, k = 0.00016, 0.00008, 0.020, 0.055 # Unstable
#Du, Dv, F, k = 0.00016, 0.00008, 0.050, 0.065 # Worms 1
#Du, Dv, F, k = 0.00016, 0.00008, 0.054, 0.063 # Worms 2
#Du, Dv, F, k = 0.00016, 0.00008, 0.035, 0.060 # Zebrafish
```

## References

*  Reaction-diffusion tutorial, by Karl Sims
http://www.karlsims.com/rd.html

*  Pearson, J. E. (1993). [Complex patterns in a simple system](http://www.sciencemag.org/content/261/5118/189), _Science_, Vol. 261(5118), 189-192 // [PDF](http://www3.nd.edu/~powers/pearson.pdf) from nd.edu.

*  Pattern Parameters from [http://www.aliensaint.com/uo/java/rd/](http://www.aliensaint.com/uo/java/rd/)

---
###### The cell below loads the style of the notebook

```python
from IPython.core.display import HTML
css_file = '../../styles/numericalmoocstyle.css'
HTML(open(css_file, 'r').read())
```

<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Arvo:400,700,400italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=PT+Mono' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Shadows+Into+Light' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Nixie+One' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Source+Code+Pro' rel='stylesheet' type='text/css'>
<style>

@font-face {
    font-family: "Computer Modern";
    src: url('http://mirrors.ctan.org/fonts/cm-unicode/fonts/otf/cmunss.otf');
}

#notebook_panel { /* main background */
    background: rgb(245,245,245);
}

div.cell { /* set cell width */
    width: 750px;
}

div #notebook { /* centre the content */
    background: #fff; /* white background for content */
    width: 1000px;
    margin: auto;
    padding-left: 0em;
}

#notebook li { /* More space between bullet points */
    margin-top:0.8em;
}

/* draw border around running cells */
div.cell.border-box-sizing.code_cell.running {
    border: 1px solid #111;
}

/* Put a solid color box around each cell and its output, visually linking them*/
div.cell.code_cell {
    background-color: rgb(256,256,256);
    border-radius: 0px;
    padding: 0.5em;
    margin-left:1em;
    margin-top: 1em;
}

div.text_cell_render{
    font-family: 'Alegreya Sans' sans-serif;
    line-height: 140%;
    font-size: 125%;
    font-weight: 400;
    width:600px;
    margin-left:auto;
    margin-right:auto;
}

/* Formatting for header cells */
.text_cell_render h1 {
    font-family: 'Nixie One', serif;
    font-style:regular;
    font-weight: 400;
    font-size: 45pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.5em;
    margin-top: 0.5em;
    display: block;
}

.text_cell_render h2 {
    font-family: 'Nixie One', serif;
    font-weight: 400;
    font-size: 30pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.1em;
    margin-top: 0.3em;
    display: block;
}

.text_cell_render h3 {
    font-family: 'Nixie One', serif;
    margin-top:16px;
    font-size: 22pt;
    font-weight: 600;
    margin-bottom: 3px;
    font-style: regular;
    color: rgb(102,102,0);
}

.text_cell_render h4 {    /*Use this for captions*/
    font-family: 'Nixie One', serif;
    font-size: 14pt;
    text-align: center;
    margin-top: 0em;
    margin-bottom: 2em;
    font-style: regular;
}

.text_cell_render h5 {  /*Use this for small titles*/
    font-family: 'Nixie One', sans-serif;
    font-weight: 400;
    font-size: 16pt;
    color: rgb(163,0,0);
    font-style: italic;
    margin-bottom: .1em;
    margin-top: 0.8em;
    display: block;
}

.text_cell_render h6 { /*use this for copyright note*/
    font-family: 'PT Mono', sans-serif;
    font-weight: 300;
    font-size: 9pt;
    line-height: 100%;
    color: grey;
    margin-bottom: 1px;
    margin-top: 1px;
}

.CodeMirror{
    font-family: "Source Code Pro";
    font-size: 90%;
}

.alert-box {
    padding:10px 10px 10px 36px;
    margin:5px;
}

.success {
    color:#666600;
    background:rgb(240,242,229);
}
</style>


```python
from IPython.display import display, Markdown, Latex
import os
import os.path
import pandas
import functools

from ee import EeVal
from ee.formatting import e12, e24
#from ee.kicad.bom.io import read_bom
from ee.kicad import read_schematic, read_schematics, to_pandas

%load_ext autoreload
%autoreload 2

```

```python
bom_file = "alpha.sch"
components = to_pandas(read_schematics(bom_file))
```

### LED resistors

```python
leds = components[components.role == "LED"]

def resistance(led):
    i_target = EeVal(led.req_I).value
    v_resistor = EeVal(led.req_Vin).value - EeVal(led.req_Vf).value
    r_exact = v_resistor/i_target
    r = e12.closest(r_exact)
    i_exact = EeVal("{} A".format(v_resistor/r))
    return pandas.Series({"r": r, "r_exact": r_exact, "i": i_exact})

resistances = leds.apply(resistance, axis='columns', reduce=False)

for ref, led in leds.join(resistances).iterrows():
    display(Markdown("#### LED {}".format(ref)))
    display(Latex("""
$$
\\begin{{align}}
V_{{forward}} &= {} \\\\
I             &= {} \\\\
R_{{exact}}   &= {} \Omega \\\\
\\\\
R_{{e12}}     &= {} \Omega \\\\
I_{{LED}}     &= {} A
\\end{{align}}
$$
""".format(led.req_Vf, led.req_I, led.r_exact, led.r, led.i)))
```

#### LED D2

$$
\begin{align}
V_{forward} &= 1.9V \\
I             &= 5 mA \\
R_{exact}   &= 280.0 \Omega \\
\\
R_{e12}     &= 270.0 \Omega \\
I_{LED}     &= 5.19 m A
\end{align}
$$

#### LED D4

$$
\begin{align}
V_{forward} &= 1.9V \\
I             &= 5 mA \\
R_{exact}   &= 280.0 \Omega \\
\\
R_{e12}     &= 270.0 \Omega \\
I_{LED}     &= 5.19 m A
\end{align}
$$

#### LED D6

$$
\begin{align}
V_{forward} &= 1.9V \\
I             &= 5 mA \\
R_{exact}   &= 280.0 \Omega \\
\\
R_{e12}     &= 270.0 \Omega \\
I_{LED}     &= 5.19 m A
\end{align}
$$

#### LED D8

$$
\begin{align}
V_{forward} &= 1.9V \\
I             &= 5 mA \\
R_{exact}   &= 280.0 \Omega \\
\\
R_{e12}     &= 270.0 \Omega \\
I_{LED}     &= 5.19 m A
\end{align}
$$

#### LED D10

$$
\begin{align}
V_{forward} &= 1.9V \\
I             &= 5 mA \\
R_{exact}   &= 280.0 \Omega \\
\\
R_{e12}     &= 270.0 \Omega \\
I_{LED}     &= 5.19 m A
\end{align}
$$

#### LED D12

$$
\begin{align}
V_{forward} &= 1.9V \\
I             &= 5 mA \\
R_{exact}   &= 280.0 \Omega \\
\\
R_{e12}     &= 270.0 \Omega \\
I_{LED}     &= 5.19 m A
\end{align}
$$

#### LED D21

$$
\begin{align}
V_{forward} &= 2.0 V \\
I             &= 5 mA \\
R_{exact}   &= 600.0 \Omega \\
\\
R_{e12}     &= 560.0 \Omega \\
I_{LED}     &= 5.36 m A
\end{align}
$$

#### LED D22

$$
\begin{align}
V_{forward} &= 2.0 V \\
I             &= 5 mA \\
R_{exact}   &= 600.0 \Omega \\
\\
R_{e12}     &= 560.0 \Omega \\
I_{LED}     &= 5.36 m A
\end{align}
$$

#### LED D23

$$
\begin{align}
V_{forward} &= 2.0 V \\
I             &= 5 mA \\
R_{exact}   &= 600.0 \Omega \\
\\
R_{e12}     &= 560.0 \Omega \\
I_{LED}     &= 5.36 m A
\end{align}
$$

```python
components = components.join(resistances)
```

```python
components[components.ref_type == "R"][["value"]] \
    .sort_values("value")
```

      value

      ref

      R21
      100k

      R1
      10k

      R26
      10k

      R25
      10k

      R24
      10k

      R20
      10k

      R19
      10k

      R18
      10k

      R17
      10k

      R16
      10k

      R15
      10k

      R13
      10k

      R14
      10k

      R11
      10k

      R9
      10k

      R7
      10k

      R5
      10k

      R3
      10k

      R22
      13.3k

      R10
      270

      R8
      270

      R6
      270

      R4
      270

      R2
      270

      R12
      270

      R27
      560

      R23
      560

      R28
      560