# Find worst cases

\begin{equation}
\begin{array}{rl}
    \mathcal{F}_L =& \dfrac{4 K I H r}{Q_{in}(1+f)}\\
    u_{c} =& \dfrac{-KI\mathcal{F}_L}{\theta \left(\mathcal{F}_L+1\right)}\\
    \tau =& -\dfrac{r}{|u_{c}|}\\
    C_{\tau,{\rm decay}}=& C_0 \exp{\left(-\lambda \tau \right)}\\
    C_{\tau,{\rm filtr}}=& C_0 \exp{\left(-k_{\rm att} \tau \right)}\\
    C_{\tau,{\rm dilut}} =& C_{in} \left( \dfrac{Q_{in}}{u_c \Delta y \Delta z} \right)\\
    C_{\tau,{\rm both}} =& \dfrac{C_{\rm in}Q_{\rm in}}{u_c \Delta y H \theta} \exp{\left(-\lambda\dfrac{r}{|u_c|}\right)}
\end{array}
\end{equation}

```python
%reset -f
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
from os import system
import os
from matplotlib.gridspec import GridSpec
from drawStuff import *

import jupypft.attachmentRateCFT as CFT
import jupypft.plotBTC as BTC

''' GLOBAL CONSTANTS '''
PI = 3.141592
THETA = 0.35
```

```python
def flowNumber():
    return (4.0*K*I*H*r) / (Qin*(1+f))

def uChar():
    '''Interstitial water velocity'''
    return -(K*I*flowNumber())/(THETA*(flowNumber() + 1))

def tChar():
    return -r/uChar()

def cDecay():
    return C0 * np.exp(-decayRate * tChar())

def cAttach():
    return C0 * np.exp(-attchRate * tChar())

def cDilut():
    return (C0 * Qin) / (-uChar() * delY * delZ * THETA)

def cBoth():
    return (C0 * Qin) / (-uChar() * delY * delZ * THETA) * np.exp(-decayRate * tChar())

def cTrice():
    return (C0 * Qin) / (-uChar() * delY * delZ * THETA) * np.exp(-(decayRate+attchRate) * tChar())

def findSweet():
    deltaConc = np.abs(cBoth() - np.max(cBoth()))
    return np.argmin(deltaConc)
```

```python
K = 10**-2
Qin = 0.24/86400
f = 10
H = 20
r = 40
I = 0.001
C0 = 1.0
qabs = np.abs(uChar()*THETA)

kattDict = dict(
    dp = 1.0E-7,
    dc = 2.0E-3,
    q = qabs,
    theta = THETA,
    visco = 0.0008891,
    rho_f = 999.79,
    rho_p = 1050.0,
    A = 5.0E-21,
    T = 10. + 273.15,
    alpha = 0.01)

decayRate = 3.5353E-06
attchRate,_ = CFT.attachmentRate(**kattDict)
delY,delZ = 1.35,H
```

```python
print("Nondim Flow  = {:.2E}".format(flowNumber()))
print("Charac. Vel  = {:.2E} m/s".format(uChar()))
print("Charac. time = {:.2E} s".format(tChar()))
```

    Nondim Flow  = 1.05E+03
    Charac. Vel  = -2.85E-05 m/s
    Charac. time = 1.40E+06 s

```python
print("Rel concenc. due decay = {:.2E}".format(cDecay()))
print("Rel conc. due dilution = {:.2E}".format(cDilut()))
print("Rel conc. due attachmt = {:.2E}".format(cAttach()))
print("Rel conc. due both eff = {:.2E}".format(cBoth()))
print("Rel conc. due three ef = {:.2E}".format(cTrice()))
```

    Rel concenc. due decay = 7.05E-03
    Rel conc. due dilution = 1.03E-02
    Rel conc. due attachmt = 4.42E-05
    Rel conc. due both eff = 7.26E-05
    Rel conc. due three ef = 3.21E-09

# Plot v. 1
I = 10**np.linspace(-5,0,num=100)

cDec = cDecay()
cDil = cDilut()
cAtt = cAttach()
cBot = cBoth()
cAll = cTrice()

i = findSweet()

worstC = cBot[i]
worstI = I[i]

fig, axs = plt.subplots(2,2,sharex=True, sharey=False,\
              figsize=(12,8),gridspec_kw={"height_ratios":[1,4],"hspace":0.04,"wspace":0.35})

bbox = dict(boxstyle='round', facecolor='mintcream', alpha=0.90)

arrowprops = dict(
    arrowstyle="->",
    connectionstyle="angle,angleA=90,angleB=40,rad=5")

fontdict = dict(size=12)

annotation = \
    r"$\bf{-\log(C/C_0)} = $" + "{:.1f}".format(-np.log10(worstC)) + \
    "\n@" + r" $\bf{I} = $" + "{:.1E}".format(worstI)

information = \
    r"$\bf{K}$" + " = {:.1E} m/s".format(K) + "\n"\
    r"$\bf{H}$" + " = {:.1f} m".format(H) + "\n"\
    r"$\bf{r}$" + " = {:.1f} m".format(r) + "\n"\
    r"$\bf{Q_{in}}$" + " = {:.2f} m³/d".format(Qin*86400) + "\n"\
    r"$\bf{f}$" + " = {:.1f}".format(f) + "\n"\
    r"$\bf{\lambda}$" + " = {:.2E} 1/s".format(decayRate)

#########################################
# Ax1 - Relative concentration
ax = axs[1,0]
ax.plot(I,cDec,label="Due decay",lw=3,ls="dashed",alpha=0.8)
ax.plot(I,cDil,label="Due dilution",lw=3,ls="dashed",alpha=0.8)
ax.plot(I,cAtt,label="Due attachment",lw=2,ls="dashed",alpha=0.6)
ax.plot(I,cBot,label="Decay + dilution",lw=3,c='k',alpha=0.9)
ax.plot(I,cAll,label="Overall effect",lw=3,c='gray',alpha=0.9)

ax.set(xscale="log",yscale="log")
ax.set(xlim=(1.0E-4,1.0E-1),ylim=(1.0E-10,1))
ax.legend(loc="lower left",shadow=True)

ax.annotate(annotation,(worstI,worstC),
    xytext=(0.05,0.85), textcoords='axes fraction',
    bbox=bbox, arrowprops=arrowprops)

ax.text(0.65,0.05,information,bbox=bbox,transform=ax.transAxes)

ax.set_xlabel("Water table gradient\n$I$ [m/m]",fontdict=fontdict)
ax.set_ylabel("Relative Concentration\n$C/C_0$ [-]",fontdict=fontdict)
####################################
# Ax2 - log-removals
ax = axs[1,1]
ax.plot(I,-np.log10(cDec),label="Due decay",lw=3,ls="dashed",alpha=0.8)
ax.plot(I,-np.log10(cDil),label="Due dilution",lw=3,ls="dashed",alpha=0.8)
ax.plot(I,-np.log10(cAtt),label="Due attachment",lw=2,ls="dashed",alpha=0.6)
ax.plot(I,-np.log10(cBot),label="Decay + dilution",lw=3,c='k',alpha=0.9)
ax.plot(I,-np.log10(cAll),label="Overall effect",lw=3,c='gray',alpha=0.9)

ax.set(xscale="log")
ax.set(xlim=(1.0E-4,1.0E-1),ylim=(0,10))
ax.legend(loc="upper left",shadow=True)

ax.annotate(annotation,(worstI,-np.log10(worstC)),
    xytext=(0.65,0.55), textcoords='axes fraction',
    bbox=bbox, arrowprops=arrowprops)

ax.text(0.65,0.70,information,bbox=bbox,transform=ax.transAxes)

ax.set_xlabel("Water table gradient\n$I$ [m/m]",fontdict=fontdict)
ax.set_ylabel("log-reductions\n$-\log(C/C_0)$ [-]",fontdict=fontdict)

####################################
#Flow number
for ax in axs[0,:]:
    ax.plot(I,flowNumber(),label="flowNumber",lw=3,c="gray")
    ax.axhline(y=1.0)
    ax.set_xscale("log")
    ax.set_yscale("log")
    ax.xaxis.set_tick_params(which="both",labeltop='on',top=True,bottom=False)
    ax.set_ylabel("Nondim. flow number\n$\mathcal{F}_L$ [-]")

####################################<
#Line worst case scenario
for axc in axs:
    for ax in axc:
        ax.axvline(x=I[i], lw=1, ls="dashed", c="red",alpha=0.5)

plt.show()
# v.2 With PFLOTRAN result

```python
listOfFiles = os.listdir("LittleValidation_MASSBALANCES")
listOfFiles.sort()

IPFLO = [float(s[9:16]) for s in listOfFiles]
CPFLO = BTC.get_endConcentrations(
            "LittleValidation_MASSBALANCES",
            indices={'t':"Time [d]",\
                     'q':"ExtractWell Water Mass [kg/d]",\
                     'm':"ExtractWell Vaq [mol/d]"
                    },
            normalizeWith=dict(t=1.0,q=kattDict['rho_f']/1000.,m=1.0))
```

```python
listOfFiles = os.listdir("LittleValidation_MASSBALANCES_Att")
listOfFiles.sort()

IPFLO2 = [float(s[8:15]) for s in listOfFiles]
CPFLO2 = BTC.get_endConcentrations(
            "LittleValidation_MASSBALANCES_Att",
            indices={'t':"Time [d]",\
                     'q':"ExtractWell Water Mass [kg/d]",\
                     'm':"ExtractWell Vaq [mol/d]"
                    },
            normalizeWith=dict(t=1.0,q=kattDict['rho_f']/1000.,m=1.0))
```

```python
# Theoretical stuff
I = 10**np.linspace(-5,0,num=100)
cDec = cDecay()
cDil = cDilut()
cAtt = cAttach()
cBot = cBoth()
cAll = cTrice()
i = findSweet()
worstC = cBot[i]
worstI = I[i]

fig, axs = plt.subplots(2,2,sharex=True, sharey=False,\
              figsize=(10,8),gridspec_kw={"height_ratios":[1,10],"hspace":0.04,"wspace":0.02})

bbox = dict(boxstyle='round', facecolor='mintcream', alpha=0.90)

arrowprops = dict(
    arrowstyle="->",
    connectionstyle="angle,angleA=90,angleB=40,rad=5")

fontdict = dict(size=12)

annotation = \
    r"$\bf{-\log(C/C_0)} = $" + "{:.1f}".format(-np.log10(worstC)) + \
    "\n@" + r" $\bf{I} = $" + BTC.sci_notation(worstI)

information = \
    r"$\bf{K} = $" + BTC.sci_notation(K) + " m/s\n"\
    r"$\bf{H}$" + " = {:.1f} m".format(H) + "\n"\
    r"$\bf{r}$" + " = {:.1f} m".format(r) + "\n"\
    r"$\bf{Q_{in}}$" + " = {:.2f} m³/d".format(Qin*86400) + "\n"\
    r"$\bf{f}$" + " = {:.1f}".format(f) + "\n"\
    r"$\bf{\lambda_{\rm aq}} = $" + BTC.sci_notation(decayRate) + " s⁻¹\n"\
    r"$\bf{k_{\rm att}} = $" + BTC.sci_notation(attchRate) + " s⁻¹"

####################################
# Ax2 - log-removals
ax = axs[1,0]

symbols=dict(dil="\u25A2",dec="\u25B3",att="\u25CE")

ax.plot(I,-np.log10(cDil),\
        label="Due dilution  " + symbols['dil'],\
        lw=3,ls="dashed",alpha=0.6,c='crimson')
ax.plot(I,-np.log10(cDec),\
        label="Due decay  " + symbols['dec'],\
        lw=3,ls="dashed",alpha=0.6,c='indigo')
ax.plot(I,-np.log10(cAtt),\
        label="Due attachment " + symbols['att'],\
        lw=2,ls="dashed",alpha=0.5,c='olive')
ax.plot(I,-np.log10(cBot),\
        label=symbols['dil'] + " + " + symbols['dec'],\
        lw=3,c='k',alpha=0.9,zorder=2)
ax.plot(I,-np.log10(cAll),\
        label=symbols['dil'] + " + " + symbols['dec'] + " + " + symbols['att'],\
        lw=2,c='gray',alpha=0.9,zorder=2)

ax.set(xscale="log")
ax.set(xlim=(1.0E-4,1.0E-1),ylim=(0,9.9))
ax.legend(loc="upper right",shadow=True,ncol=1,\
          title="Potential flow prediction",title_fontsize=11)

ax.annotate(annotation,(worstI,-np.log10(worstC)),
    xytext=(0.65,0.55), textcoords='axes fraction',
    bbox=bbox, arrowprops=arrowprops)

ax.set_xlabel("Water table gradient\n$I$ [m/m]",fontdict=fontdict)
ax.set_ylabel("log-reductions\n$-\log(C/C_0)$ [-]",fontdict=fontdict)

####################################
# Ax2 - log-removals
ax = axs[1,1]
ax.plot(I,-np.log10(cBot),
        lw=3,c='k',alpha=0.9)
ax.plot(IPFLO,-np.log10(CPFLO),zorder=2,\
        label= symbols['dil'] + " + " + symbols['dec'],\
        lw=0,ls='dotted',c='k',alpha=0.9,\
        marker="$\u25CA$",mec='k',mfc='k',ms=10)

ax.plot(I,-np.log10(cAll),\
        lw=2,c='gray',alpha=0.9)
ax.plot(IPFLO2,-np.log10(CPFLO2),zorder=2,\
        label= symbols['dil'] + " + " + symbols['dec'] + " + " + symbols['att'],\
        lw=0,ls='dotted',c='gray',alpha=0.9,\
        marker="$\u2217$",mec='gray',mfc='gray',ms=10)
#####

ax.set(xscale="log")
ax.set(xlim=(1.0E-4,9.0E-2),ylim=(0,9.9))
ax.legend(loc="lower left",shadow=True,ncol=1,\
          labelspacing=0.4,mode=None,\
          title="PFLOTRAN run",title_fontsize=11)
ax.set_xlabel("Water table gradient\n$I$ [m/m]",fontdict=fontdict)
ax.yaxis.tick_right()

ax.text(0.60,0.70,information,bbox=bbox,transform=ax.transAxes)
ax.text(I[i],0.5,"Worst\ncase",\
        ha='center',va='center',weight='semibold',\
        bbox=dict(boxstyle='square', fc='white', ec='red'))

####################################
#Flow number
ax = axs[0,0]
ax.plot(I,flowNumber(),label="flowNumber",lw=3,c="gray")
#ax.axhline(y=1.0)
ax.set_xscale("log")
ax.set_yscale("log")
ax.xaxis.set_tick_params(which="both",labeltop='on',top=False,bottom=False)
#ax.set_ylabel("Nondim. flow number\n$\mathcal{F}_L$ [-]")

####################################<
#Line worst case scenario
axs[0,0].axvline(x=I[i], lw=2, ls="dotted", c="red",alpha=0.5)
axs[1,0].axvline(x=I[i], lw=2, ls="dotted", c="red",alpha=0.5)
axs[1,1].axvline(x=I[i], lw=2, ls="dotted", c="red",alpha=0.5)

###############################
# Information box
ax = axs[0,1]
ax.axis('off')

plt.show()
```

```python
fig, axs = plt.subplots(1,1,figsize=(6,5))
bbox = dict(boxstyle='round,pad=0.5,rounding_size=0.3', facecolor='mintcream', alpha=0.90)
fontdict = dict(size=12)
lines = {}
information = \
    "Parameters:\n" + \
    r"$\bf{K} = $" + BTC.sci_notation(K) + " m/s\n"\
    r"$\bf{H}$" + " = {:.1f} m".format(H) + "\n"\
    r"$\bf{r}$" + " = {:.1f} m".format(r) + "\n"\
    r"$\bf{Q_{in}}$" + " = {:.2f} m³/d".format(Qin*86400) + "\n"\
    r"$\bf{f}$" + " = {:.1f}".format(f) + "\n"\
    r"$\bf{\lambda_{\rm aq}} = $" + BTC.sci_notation(decayRate) + " s⁻¹"\

####################################
# Ax2 - log-removals
ax = axs

lines['Dilution'] = ax.plot(I,-np.log10(cDil),\
        label="Due dilution",\
        lw=3,ls="dashed",alpha=0.6,c='crimson')
lines['Decay'] = ax.plot(I,-np.log10(cDec),\
        label="Due decay",\
        lw=3,ls="dashed",alpha=0.6,c='indigo')
#ax.plot(I,-np.log10(cAtt),\
#        label="Due attachment " + symbols['att'],\
#        lw=2,ls="dashed",alpha=0.5,c='olive')
lines['Both'] = ax.plot(I,-np.log10(cBot),\
        label="Combined effect",\
        lw=3,c='k',alpha=0.9,zorder=2)
#ax.plot(I,-np.log10(cAll),\
#        label=symbols['dil'] + " + " + symbols['dec'] + " + " + symbols['att'],\
#        lw=2,c='gray',alpha=0.9,zorder=2)
lines['PFLOT'] = ax.plot(IPFLO,-np.log10(CPFLO),zorder=2,\
        label= "PFLOTRAN results",\
        lw=0,ls='dotted',c='k',alpha=0.9,\
        marker="$\u25CA$",mec='k',mfc='k',ms=10)

ax.set(xscale="log")
ax.set(xlim=(5.0E-5,5.0E-1),ylim=(0,9.9))
ax.text(1.04,0.55,information,bbox=bbox,transform=ax.transAxes)

ax.set_xlabel("Water table gradient\n$I$ [m/m]",fontdict=fontdict)
ax.set_ylabel("log-reductions\n$-\log(C/C_0)$ [-]",fontdict=fontdict)

## Legend
whitebox = ax.scatter([1],[1],c="white",marker="o",s=1,alpha=0)

handles = [whitebox,
           lines['Dilution'][0],
           lines['Decay'][0],
           lines['Both'][0],
           whitebox,
           lines['PFLOT'][0]]

labels = ['PF model',
          lines['Dilution'][0].get_label(),
          lines['Decay'][0].get_label(),
          lines['Both'][0].get_label(),
          '',
          lines['PFLOT'][0].get_label(),]

plt.legend(handles, labels,
           loc="center left",ncol=1,bbox_to_anchor=(1.01,0.25),\
          edgecolor='k',facecolor='mintcream',labelspacing=1)

plt.show()
```

```python
fig, axs = plt.subplots(1,1,figsize=(6,5))
bbox = dict(boxstyle='round,pad=0.5,rounding_size=0.3', facecolor='mintcream', alpha=0.90)
fontdict = dict(size=12)
lines = {}
information = \
    "Parameters:\n" + \
    r"$\bf{K} = $" + BTC.sci_notation(K) + " m/s\n"\
    r"$\bf{H}$" + " = {:.1f} m".format(H) + "\n"\
    r"$\bf{r}$" + " = {:.1f} m".format(r) + "\n"\
    r"$\bf{Q_{in}}$" + " = {:.2f} m³/d".format(Qin*86400) + "\n"\
    r"$\bf{f}$" + " = {:.1f}".format(f) + "\n"\
    r"$\bf{\lambda_{\rm aq}} = $" + BTC.sci_notation(decayRate) + " s⁻¹\n"\
    r"$\bf{k_{\rm att}} = $" + BTC.sci_notation(attchRate) + " s⁻¹"

####################################
# Ax2 - log-removals
ax = axs

lines['Dilution'] = ax.plot(I,-np.log10(cDil),\
        label="Due dilution",\
        lw=3,ls="dashed",alpha=0.6,c='crimson')
lines['Decay'] = ax.plot(I,-np.log10(cDec),\
        label="Due decay",\
        lw=3,ls="dashed",alpha=0.6,c='indigo')
lines['Attach'] = ax.plot(I,-np.log10(cAtt),\
        label="Due attachment",\
        lw=3,ls="dashed",alpha=0.6,c='olive')
#lines['Both'] = ax.plot(I,-np.log10(cBot),\
#        label="Combined effect",\
#        lw=3,c='k',alpha=0.9,zorder=2)
lines['Both'] = ax.plot(I,-np.log10(cAll),\
        label="Combined effect",\
        lw=3,c='k',alpha=0.9,zorder=2)

lines['PFLOT'] = ax.plot(IPFLO2,-np.log10(CPFLO2),zorder=2,\
        label= "PFLOTRAN results",\
        lw=0,ls='dotted',c='k',alpha=0.9,\
        marker="$\u2217$",mec='gray',mfc='k',ms=10)

ax.set(xscale="log")
ax.set(xlim=(5.0E-5,5.0E-1),ylim=(0,9.9))
ax.text(1.04,0.55,information,bbox=bbox,transform=ax.transAxes)

ax.set_xlabel("Water table gradient\n$I$ [m/m]",fontdict=fontdict)
ax.set_ylabel("log-reductions\n$-\log(C/C_0)$ [-]",fontdict=fontdict)

## Legend
whitebox = ax.scatter([1],[1],c="white",marker="o",s=1,alpha=0)

handles = [whitebox,
           lines['Dilution'][0],
           lines['Decay'][0],
           lines['Attach'][0],
           lines['Both'][0],
           whitebox,
           lines['PFLOT'][0]]

labels = ['PF model',
          lines['Dilution'][0].get_label(),
          lines['Decay'][0].get_label(),
          lines['Attach'][0].get_label(),
          lines['Both'][0].get_label(),
          '',
          lines['PFLOT'][0].get_label(),]

plt.legend(handles, labels,
           loc="center left",ncol=1,bbox_to_anchor=(1.01,0.25),\
          edgecolor='k',facecolor='mintcream',labelspacing=1)

plt.show()
```

____
# Find the worst case
## >> Geometric parameters $H$ and $r$
K = 10**-2
Qin = 0.24/86400
f = 10
C0 = 1.0
decayRate = 3.5353E-06

Harray = np.array([2.,5.,10.,20.,50.])
rarray = np.array([5.,10.,40.,100.])
Iarray = 10**np.linspace(-5,0,num=100)

Ci = np.zeros([len(rarray),len(Harray)])
Ii = np.zeros([len(rarray),len(Harray)])
FLi = np.zeros([len(rarray),len(Harray)])

for hi,H in enumerate(Harray):
    for ri,r in enumerate(rarray):
        i = findSweet()

        worstC = -np.log10(cBoth()[i])
        worstGradient = Iarray[i]
        worstFlowNumber = flowNumber()[i]

        Ci[ri,hi]  = worstC
        Ii[ri,hi]  = worstGradient
        FLi[ri,hi] = worstFlowNumbermyLabels={"Title": { 0: r"$\bf{-\log (C_{\tau}/C_0)}$",
                     1: r"$\bf{I}$ (%)",
                     2: r"$\log(\mathcal{F}_L)$"},
          "Y": "Aquifer thickness\n$\\bf{H}$ (m)",
          "X": "Setback distance\n$\\bf{r}$ (m)"}

threeHeatplots(data={"I":Ii.T,"C":Ci.T,"FL":FLi.T},\
               xlabel=Harray,ylabel=rarray,myLabels=myLabels);
## >>Well parameters
K = 10**-2
H = 20
r = 40
C0 = 1.0
decayRate = 3.5353E-06

Qin_array = np.array([0.24,1.0,10.0,100.])/86400.
f_array = np.array([1,10.,100.,1000.,10000.])
Iarray = 10**np.linspace(-5,0,num=100)

Ci = np.zeros([len(Qin_array),len(f_array)])
Ii = np.zeros([len(Qin_array),len(f_array)])
FLi = np.zeros([len(Qin_array),len(f_array)])

for fi,f in enumerate(f_array):
    for qi,Qin in enumerate(Qin_array):
        i = findSweet()
        worstC = -np.log10(cBoth()[i])
        worstGradient = Iarray[i]
        worstFlowNumber = flowNumber()[i]

        Ci[qi,fi] = worstC
        Ii[qi,fi] = worstGradient
        FLi[qi,fi] = worstFlowNumber

### Plot heatmap
myLabels={"Title": { 0: r"$\bf{-\log (C_{\tau}/C_0)}$",
                     1: r"$\bf{I}$ (%)",
                     2: r"$\log(\mathcal{F}_L)$"},
          "Y": "Extraction to injection ratio\n$\\bf{f}$ (-)",
          "X": "Injection flow rate\n$\\bf{Q_{in}}$ (m³/d)"}

threeHeatplots(data={"I":Ii.T,"C":Ci.T,"FL":FLi.T},\
               xlabel=f_array,ylabel=np.round(Qin_array*86400,decimals=2),myLabels=myLabels);
## Hydraulic conductivity
Qin = 0.24/86400
f = 10
C0 = 1.0
decayRate = 3.5353E-06

Karray = 10.**np.array([-1.,-2.,-3.,-4.,-5.])
rarray = np.array([5.,10.,40.,100.])
Iarray = 10**np.linspace(-5,0,num=100)

Ci = np.zeros([len(rarray),len(Karray)])
Ii = np.zeros([len(rarray),len(Karray)])
FLi = np.zeros([len(rarray),len(Karray)])

for ki,K in enumerate(Karray):
    for ri,r in enumerate(rarray):
        i = findSweet()

        worstC = -np.log10(cBoth()[i])
        worstGradient = Iarray[i]
        worstFlowNumber = flowNumber()[i]

        Ci[ri,ki] = worstC
        Ii[ri,ki] = worstGradient
        FLi[ri,ki] = worstFlowNumber

### Plot heatmap
myLabels={"Title": { 0: r"$\bf{-\log (C_{\tau}/C_0)}$",
                     1: r"$\bf{I}$ (%)",
                     2: r"$\log(\mathcal{F}_L)$"},
          "Y": "Hydraulic conductivity\n$\\bf{K}$ (m/s)",
          "X": "Setback distance\n$\\bf{r}$ (m)"}

threeHeatplots(data={"I":Ii.T,"C":Ci.T,"FL":FLi.T},\
               xlabel=Karray,ylabel=rarray,myLabels=myLabels);K = 10**-2
H = 20
f = 10
C0 = 1.0
decayRate = 3.5353E-06

Qin_array = np.array([0.24,1.0,10.0,100.])/86400.
rarray = np.array([5.,10.,40.,100.])
Iarray = 10**np.linspace(-5,0,num=100)

Ci = np.zeros([len(rarray),len(Qin_array)])
Ii = np.zeros([len(rarray),len(Qin_array)])
FLi = np.zeros([len(rarray),len(Qin_array)])

for qi,Qin in enumerate(Qin_array):
    for ri,r in enumerate(rarray):
        i = findSweet()

        worstC = -np.log10(cBoth()[i])
        worstGradient = Iarray[i]
        worstFlowNumber = flowNumber()[i]

        Ci[ri,qi] = worstC
        Ii[ri,qi] = worstGradient
        FLi[ri,qi] = worstFlowNumber

myLabels={"Title": { 0: r"$\bf{-\log (C_{\tau}/C_0)}$",
                     1: r"$\bf{I}$ (%)",
                     2: r"$\log(\mathcal{F}_L)$"},
          "Y": "Injection flow rate\n$\\bf{Q_{in}}$ (m³/d)",
          "X": "Setback distance\n$\\bf{r}$ (m)"}

threeHeatplots(data={"I":Ii.T,"C":Ci.T,"FL":FLi.T},\
               ylabel=rarray,xlabel=np.round(Qin_array*86400,decimals=2),myLabels=myLabels);K = 10**-2
H = 20
f = 10
C0 = 1.0
decayRate = 1.119E-5

Qin_array = np.array([0.24,1.0,10.0,100.])/86400.
rarray = np.array([5.,10.,40.,100.])
Iarray = 10**np.linspace(-5,0,num=100)

Ci = np.zeros([len(rarray),len(Qin_array)])
Ii = np.zeros([len(rarray),len(Qin_array)])
FLi = np.zeros([len(rarray),len(Qin_array)])

for qi,Qin in enumerate(Qin_array):
    for ri,r in enumerate(rarray):
        i = findSweet()

        worstC = -np.log10(cBoth()[i])
        worstGradient = Iarray[i]
        worstFlowNumber = flowNumber()[i]

        Ci[ri,qi] = worstC
        Ii[ri,qi] = worstGradient
        FLi[ri,qi] = worstFlowNumber

myLabels={"Title": { 0: r"$\bf{-\log (C_{\tau}/C_0)}$",
                     1: r"$\bf{I}$ (%)",
                     2: r"$\log(\mathcal{F}_L)$"},
          "Y": "Injection flow rate\n$\\bf{Q_{in}}$ (m³/d)",
          "X": "Setback distance\n$\\bf{r}$ (m)"}

threeHeatplots(data={"I":Ii.T,"C":Ci.T,"FL":FLi.T},\
               ylabel=rarray,xlabel=np.round(Qin_array*86400,decimals=2),myLabels=myLabels);
## PLOTRAN SIMULATION RESULTS
minI = np.array([0.00046667, 0.0013    , 0.0048    , 0.012     , 0.00046667,
       0.0013    , 0.0048    , 0.012     , 0.00053333, 0.0013    ,
       0.0048    , 0.012     , 0.00056667, 0.0021    , 0.0053    ,
       0.012     ])

minC = np.array([2.2514572 , 2.62298917, 3.14213329, 3.51421485, 1.64182175,
       2.00913676, 2.52461269, 2.89537637, 0.74130696, 1.0754177 ,
       1.55071976, 1.90646243, 0.18705258, 0.39222131, 0.73428991,
       1.00387133])Qin_array = np.array([0.24,1.0,10.0,100.])/86400.
rarray = np.array([5.,10.,40.,100.])
Iarray = 10**np.linspace(-5,0,num=100)

Ci = np.zeros([len(rarray),len(Qin_array)])
Ii = np.zeros([len(rarray),len(Qin_array)])
FLi = np.zeros([len(rarray),len(Qin_array)])

i = 0
for qi,Qin in enumerate(Qin_array):
    for ri,r in enumerate(rarray):
        worstC = minC[i]
        worstGradient = minI[i]

        Ci[ri,qi] = worstC
        Ii[ri,qi] = worstGradient

        i += 1

myLabels={"Title": { 0: r"$\bf{-\log (C_{\tau}/C_0)}$",
                     1: r"$\bf{I}$ (%)",
                     2: r"$\log(\mathcal{F}_L)$"},
          "Y": "Injection flow rate\n$\\bf{Q_{in}}$ (m³/d)",
          "X": "Setback distance\n$\\bf{r}$ (m)"}

threeHeatplots(data={"I":Ii.T,"C":Ci.T,"FL":Ii.T},\
               ylabel=rarray,xlabel=np.round(Qin_array*86400,decimals=2),myLabels=myLabels);

```python

```

notebooks/Concepts/.ipynb_checkpoints/Find worst case (1)-checkpoint.ipynb


```python
import matplotlib.pyplot as plt
import scipy.stats as st
import seaborn as sns
import pandas as pd
from scipy.stats import norm, uniform, expon, t, binom, binom_test, wilcoxon
from scipy.integrate import quad
from sympy.solvers import solve
from sympy import Symbol
import numpy as np
from pandas import Series, DataFrame
```

```python
import matplotlib.pyplot as plt
import pandas as pd
from pandas import DataFrame
import numpy as np
sp2012 = pd.read_table('sp2012.txt')
df = DataFrame(sp2012)
plt.plot(df)
plt.xlabel("Zeit (Tage)")
plt.ylabel("Wert")
plt.title('S&P 500 - Aktienkurs 2012')
plt.show()

```

```python
n = 250
loc = 0.483
scale = 11
from scipy.stats import norm
steps = np.array(norm.rvs(size = n, loc = loc, scale = scale))
sp_simulated = np.empty([250])
sp_simulated[0] =  1257.6
print(sp_simulated[0])
for i in range(249):
    sp_simulated[i+1] = sp_simulated[i]+ steps[i]
plt.plot(sp_simulated)
plt.xlabel("Simulierte Zeit")
plt.ylabel("Simulierte Werte")
plt.title('Weitere werte')
plt.show()

```

```python

```

    1277.06

```python
norm.ppf(0.9)*2+2
```

    4.563103131089201

```python
# Generieren von 100 gleichmaessig verteilten
# Zufallszahlen im Intervall [-1,1]
from scipy.stats import uniform
import numpy as np
x = uniform.rvs(size=100, loc=-1, scale=2)
# Bestimmen der Anzahl Zahlen kleiner als eins; Beispiel:
# Anzahl von 100 zufaellig im Intervall [0,10] generierten
# Zahlen, die kleiner als 1 sind:
np.sum(x<1)
```

    100

```python

```


# 六軸史都華平台模擬

```python
import numpy as np
import pandas as pd
from sympy import *
init_printing(use_unicode=True)
import matplotlib as mpl
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
import seaborn as sns
sns.set()
%matplotlib inline
```

### Stewart Func

```python
α, β, γ = symbols('α β γ')
x, y, z = symbols('x y z')
r, R, w, W, t = symbols('r R w W t')

# x, y, z三軸固定軸旋轉矩陣
rotx = lambda θ : Matrix([[1, 0, 0],
                          [0, cos(θ), -sin(θ)],
                          [0, sin(θ), cos(θ)]])
roty = lambda θ : Matrix([[cos(θ), 0, sin(θ)],
                          [0, 1, 0],
                          [-sin(θ), 0, cos(θ)]])
rotz = lambda θ : Matrix([[cos(θ), -sin(θ), 0],
                          [sin(θ), cos(θ), 0],
                          [0, 0, 1]])

# 姿勢產生器 固定座標旋轉
def poses(α, β, γ):
    return rotz(γ)*roty(β)*rotx(α)

# 質心位置產生器
def posit(x, y, z):
    return Matrix([x, y, z])

# Basic 6 軸點設定
def basic(r, w):
    b1 = Matrix([w/2, r, 0])
    b2 = Matrix([-w/2, r, 0])
    b3 = rotz(pi*2/3)*b1
    b4 = rotz(pi*2/3)*b2
    b5 = rotz(pi*2/3)*b3
    b6 = rotz(pi*2/3)*b4
    return [b1, b2, b3, b4, b5, b6]

# 平台 6 軸點設定
def plat(r, w, pos=poses(0, 0, 0), pit=posit(0, 0, 5)):
    p1 = Matrix([-w/2, r, 0])
    p1 = rotz(-pi/3)*p1
    p2 = Matrix([[-1, 0, 0], [0, 1, 0], [0, 0, 1]])*p1
    p3 = rotz(pi*2/3)*p1
    p4 = rotz(pi*2/3)*p2
    p5 = rotz(pi*2/3)*p3
    p6 = rotz(pi*2/3)*p4
    lst = [p1, p2, p3, p4, p5, p6]
    for n in range(6):
        lst[n] = (pos*lst[n]) + pit
    return lst

# 六軸長度
def leng(a, b):
    if a.ndim == 1:
        return (((a - b)**2).sum())**0.5
    else:
        return (((a - b)**2).sum(1))**0.5
```

### Basic & plane

```python
basic(R, W)
```

$$\left [ \left[\begin{matrix}\frac{W}{2}\\R\\0\end{matrix}\right], \quad \left[\begin{matrix}- \frac{W}{2}\\R\\0\end{matrix}\right], \quad \left[\begin{matrix}- \frac{\sqrt{3} R}{2} - \frac{W}{4}\\- \frac{R}{2} + \frac{\sqrt{3} W}{4}\\0\end{matrix}\right], \quad \left[\begin{matrix}- \frac{\sqrt{3} R}{2} + \frac{W}{4}\\- \frac{R}{2} - \frac{\sqrt{3} W}{4}\\0\end{matrix}\right], \quad \left[\begin{matrix}\frac{\sqrt{3} R}{4} + \frac{W}{8} - \frac{\sqrt{3} \left(- \frac{R}{2} + \frac{\sqrt{3} W}{4}\right)}{2}\\\frac{R}{4} - \frac{\sqrt{3} W}{8} + \frac{\sqrt{3} \left(- \frac{\sqrt{3} R}{2} - \frac{W}{4}\right)}{2}\\0\end{matrix}\right], \quad \left[\begin{matrix}\frac{\sqrt{3} R}{4} - \frac{W}{8} - \frac{\sqrt{3} \left(- \frac{R}{2} - \frac{\sqrt{3} W}{4}\right)}{2}\\\frac{R}{4} + \frac{\sqrt{3} W}{8} + \frac{\sqrt{3} \left(- \frac{\sqrt{3} R}{2} + \frac{W}{4}\right)}{2}\\0\end{matrix}\right]\right ]$$

```python
plat(r, w, poses(α, β, γ), posit(x, y, z))
```

$$\left [ \left[\begin{matrix}x + \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right) \left(\sin{\left (α \right )} \sin{\left (β \right )} \cos{\left (γ \right )} - \sin{\left (γ \right )} \cos{\left (α \right )}\right) + \left(\frac{\sqrt{3} r}{2} - \frac{w}{4}\right) \cos{\left (β \right )} \cos{\left (γ \right )}\\y + \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right) \left(\sin{\left (α \right )} \sin{\left (β \right )} \sin{\left (γ \right )} + \cos{\left (α \right )} \cos{\left (γ \right )}\right) + \left(\frac{\sqrt{3} r}{2} - \frac{w}{4}\right) \sin{\left (γ \right )} \cos{\left (β \right )}\\z + \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right) \sin{\left (α \right )} \cos{\left (β \right )} - \left(\frac{\sqrt{3} r}{2} - \frac{w}{4}\right) \sin{\left (β \right )}\end{matrix}\right], \quad \left[\begin{matrix}x + \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right) \left(\sin{\left (α \right )} \sin{\left (β \right )} \cos{\left (γ \right )} - \sin{\left (γ \right )} \cos{\left (α \right )}\right) + \left(- \frac{\sqrt{3} r}{2} + \frac{w}{4}\right) \cos{\left (β \right )} \cos{\left (γ \right )}\\y + \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right) \left(\sin{\left (α \right )} \sin{\left (β \right )} \sin{\left (γ \right )} + \cos{\left (α \right )} \cos{\left (γ \right )}\right) + \left(- \frac{\sqrt{3} r}{2} + \frac{w}{4}\right) \sin{\left (γ \right )} \cos{\left (β \right )}\\z + \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right) \sin{\left (α \right )} \cos{\left (β \right )} - \left(- \frac{\sqrt{3} r}{2} + \frac{w}{4}\right) \sin{\left (β \right )}\end{matrix}\right], \quad \left[\begin{matrix}x + \left(\sin{\left (α \right )} \sin{\left (β \right )} \cos{\left (γ \right )} - \sin{\left (γ \right )} \cos{\left (α \right )}\right) \left(- \frac{r}{4} - \frac{\sqrt{3} w}{8} + \frac{\sqrt{3} \left(\frac{\sqrt{3} r}{2} - \frac{w}{4}\right)}{2}\right) + \left(- \frac{\sqrt{3} r}{4} + \frac{w}{8} - \frac{\sqrt{3} \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right)}{2}\right) \cos{\left (β \right )} \cos{\left (γ \right )}\\y + \left(\sin{\left (α \right )} \sin{\left (β \right )} \sin{\left (γ \right )} + \cos{\left (α \right )} \cos{\left (γ \right )}\right) \left(- \frac{r}{4} - \frac{\sqrt{3} w}{8} + \frac{\sqrt{3} \left(\frac{\sqrt{3} r}{2} - \frac{w}{4}\right)}{2}\right) + \left(- \frac{\sqrt{3} r}{4} + \frac{w}{8} - \frac{\sqrt{3} \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right)}{2}\right) \sin{\left (γ \right )} \cos{\left (β \right )}\\z + \left(- \frac{r}{4} - \frac{\sqrt{3} w}{8} + \frac{\sqrt{3} \left(\frac{\sqrt{3} r}{2} - \frac{w}{4}\right)}{2}\right) \sin{\left (α \right )} \cos{\left (β \right )} - \left(- \frac{\sqrt{3} r}{4} + \frac{w}{8} - \frac{\sqrt{3} \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right)}{2}\right) \sin{\left (β \right )}\end{matrix}\right], \quad \left[\begin{matrix}x + \left(\sin{\left (α \right )} \sin{\left (β \right )} \cos{\left (γ \right )} - \sin{\left (γ \right )} \cos{\left (α \right )}\right) \left(- \frac{r}{4} - \frac{\sqrt{3} w}{8} + \frac{\sqrt{3} \left(- \frac{\sqrt{3} r}{2} + \frac{w}{4}\right)}{2}\right) + \left(\frac{\sqrt{3} r}{4} - \frac{w}{8} - \frac{\sqrt{3} \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right)}{2}\right) \cos{\left (β \right )} \cos{\left (γ \right )}\\y + \left(\sin{\left (α \right )} \sin{\left (β \right )} \sin{\left (γ \right )} + \cos{\left (α \right )} \cos{\left (γ \right )}\right) \left(- \frac{r}{4} - \frac{\sqrt{3} w}{8} + \frac{\sqrt{3} \left(- \frac{\sqrt{3} r}{2} + \frac{w}{4}\right)}{2}\right) + \left(\frac{\sqrt{3} r}{4} - \frac{w}{8} - \frac{\sqrt{3} \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right)}{2}\right) \sin{\left (γ \right )} \cos{\left (β \right )}\\z + \left(- \frac{r}{4} - \frac{\sqrt{3} w}{8} + \frac{\sqrt{3} \left(- \frac{\sqrt{3} r}{2} + \frac{w}{4}\right)}{2}\right) \sin{\left (α \right )} \cos{\left (β \right )} - \left(\frac{\sqrt{3} r}{4} - \frac{w}{8} - \frac{\sqrt{3} \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right)}{2}\right) \sin{\left (β \right )}\end{matrix}\right], \quad \left[\begin{matrix}x + \left(\sin{\left (α \right )} \sin{\left (β \right )} \cos{\left (γ \right )} - \sin{\left (γ \right )} \cos{\left (α \right )}\right) \left(\frac{r}{8} + \frac{\sqrt{3} w}{16} - \frac{\sqrt{3} \left(\frac{\sqrt{3} r}{2} - \frac{w}{4}\right)}{4} + \frac{\sqrt{3} \left(- \frac{\sqrt{3} r}{4} + \frac{w}{8} - \frac{\sqrt{3} \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right)}{2}\right)}{2}\right) + \left(\frac{\sqrt{3} r}{8} - \frac{w}{16} + \frac{\sqrt{3} \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right)}{4} - \frac{\sqrt{3} \left(- \frac{r}{4} - \frac{\sqrt{3} w}{8} + \frac{\sqrt{3} \left(\frac{\sqrt{3} r}{2} - \frac{w}{4}\right)}{2}\right)}{2}\right) \cos{\left (β \right )} \cos{\left (γ \right )}\\y + \left(\sin{\left (α \right )} \sin{\left (β \right )} \sin{\left (γ \right )} + \cos{\left (α \right )} \cos{\left (γ \right )}\right) \left(\frac{r}{8} + \frac{\sqrt{3} w}{16} - \frac{\sqrt{3} \left(\frac{\sqrt{3} r}{2} - \frac{w}{4}\right)}{4} + \frac{\sqrt{3} \left(- \frac{\sqrt{3} r}{4} + \frac{w}{8} - \frac{\sqrt{3} \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right)}{2}\right)}{2}\right) + \left(\frac{\sqrt{3} r}{8} - \frac{w}{16} + \frac{\sqrt{3} \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right)}{4} - \frac{\sqrt{3} \left(- \frac{r}{4} - \frac{\sqrt{3} w}{8} + \frac{\sqrt{3} \left(\frac{\sqrt{3} r}{2} - \frac{w}{4}\right)}{2}\right)}{2}\right) \sin{\left (γ \right )} \cos{\left (β \right )}\\z + \left(\frac{r}{8} + \frac{\sqrt{3} w}{16} - \frac{\sqrt{3} \left(\frac{\sqrt{3} r}{2} - \frac{w}{4}\right)}{4} + \frac{\sqrt{3} \left(- \frac{\sqrt{3} r}{4} + \frac{w}{8} - \frac{\sqrt{3} \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right)}{2}\right)}{2}\right) \sin{\left (α \right )} \cos{\left (β \right )} - \left(\frac{\sqrt{3} r}{8} - \frac{w}{16} + \frac{\sqrt{3} \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right)}{4} - \frac{\sqrt{3} \left(- \frac{r}{4} - \frac{\sqrt{3} w}{8} + \frac{\sqrt{3} \left(\frac{\sqrt{3} r}{2} - \frac{w}{4}\right)}{2}\right)}{2}\right) \sin{\left (β \right )}\end{matrix}\right], \quad \left[\begin{matrix}x + \left(\sin{\left (α \right )} \sin{\left (β \right )} \cos{\left (γ \right )} - \sin{\left (γ \right )} \cos{\left (α \right )}\right) \left(\frac{r}{8} + \frac{\sqrt{3} w}{16} - \frac{\sqrt{3} \left(- \frac{\sqrt{3} r}{2} + \frac{w}{4}\right)}{4} + \frac{\sqrt{3} \left(\frac{\sqrt{3} r}{4} - \frac{w}{8} - \frac{\sqrt{3} \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right)}{2}\right)}{2}\right) + \left(- \frac{\sqrt{3} r}{8} + \frac{w}{16} + \frac{\sqrt{3} \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right)}{4} - \frac{\sqrt{3} \left(- \frac{r}{4} - \frac{\sqrt{3} w}{8} + \frac{\sqrt{3} \left(- \frac{\sqrt{3} r}{2} + \frac{w}{4}\right)}{2}\right)}{2}\right) \cos{\left (β \right )} \cos{\left (γ \right )}\\y + \left(\sin{\left (α \right )} \sin{\left (β \right )} \sin{\left (γ \right )} + \cos{\left (α \right )} \cos{\left (γ \right )}\right) \left(\frac{r}{8} + \frac{\sqrt{3} w}{16} - \frac{\sqrt{3} \left(- \frac{\sqrt{3} r}{2} + \frac{w}{4}\right)}{4} + \frac{\sqrt{3} \left(\frac{\sqrt{3} r}{4} - \frac{w}{8} - \frac{\sqrt{3} \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right)}{2}\right)}{2}\right) + \left(- \frac{\sqrt{3} r}{8} + \frac{w}{16} + \frac{\sqrt{3} \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right)}{4} - \frac{\sqrt{3} \left(- \frac{r}{4} - \frac{\sqrt{3} w}{8} + \frac{\sqrt{3} \left(- \frac{\sqrt{3} r}{2} + \frac{w}{4}\right)}{2}\right)}{2}\right) \sin{\left (γ \right )} \cos{\left (β \right )}\\z + \left(\frac{r}{8} + \frac{\sqrt{3} w}{16} - \frac{\sqrt{3} \left(- \frac{\sqrt{3} r}{2} + \frac{w}{4}\right)}{4} + \frac{\sqrt{3} \left(\frac{\sqrt{3} r}{4} - \frac{w}{8} - \frac{\sqrt{3} \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right)}{2}\right)}{2}\right) \sin{\left (α \right )} \cos{\left (β \right )} - \left(- \frac{\sqrt{3} r}{8} + \frac{w}{16} + \frac{\sqrt{3} \left(\frac{r}{2} + \frac{\sqrt{3} w}{4}\right)}{4} - \frac{\sqrt{3} \left(- \frac{r}{4} - \frac{\sqrt{3} w}{8} + \frac{\sqrt{3} \left(- \frac{\sqrt{3} r}{2} + \frac{w}{4}\right)}{2}\right)}{2}\right) \sin{\left (β \right )}\end{matrix}\right]\right ]$$

### 設定α, β, γ, x, y, z 可設定為時間t的函數

```python
pos = poses(sin(2*t), cos(t), 0)
pit = posit(x, y, z)

baspt = basic(R, W)
baspt = np.array(baspt)
pltpt = plat(r, w, pos, pit)
pltpt = np.array(pltpt)
```

### 6軸的長度 (示範軸1)

```python
length = leng(baspt ,pltpt)
```

### 設定參數 r = 10, R = 5, w = 2, W = 2, x = 0, y = 0, z = 10

```python
x1 = length[0].subs([(r, 10), (R, 5), (w, 2), (W, 2), (x, 0), (y, 0), (z, 10)])
```

```python
x1
```

### 微分一次獲得速度變化

```python
dx1 = diff(x1, t)
dx1
```

### 微分兩次獲得加速度變化

```python
ddx1 = diff(dx1, t)
ddx1
```

### 畫圖

```python
tline = np.linspace(0, 2*np.pi, 361)

xlst = lambdify(t, x1, 'numpy')
dxlst = lambdify(t, dx1, 'numpy')
ddxlst = lambdify(t, ddx1, 'numpy')

plt.rcParams['figure.figsize'] = [16, 6]
plt.plot(tline, xlst(tline), label = 'x')
plt.plot(tline, dxlst(tline), label = 'v')
plt.plot(tline, ddxlst(tline), label = 'a')
plt.ylabel('Length')
plt.xlabel('Time')
plt.legend()
```


# $p_r$, the radial component of the momentum vector, up to and including 3.5 post-Newtonian order

## This notebook constructs the radial component of the momentum vector, up to and including 3.5 PN order

**Notebook Status:**  Validated

**Validation Notes:** All PN expressions in this notebook were transcribed twice by hand on separate occasions, and expressions were corrected as needed to ensure consistency with published work. Published work was cross-validated and typo(s) in published work were corrected. In addition, all expressions in this notebook were validated against those in the Mathematica notebook used by [Ramos-Buades, Husa, and Pratten (2018)](https://arxiv.org/abs/1810.00036) (thanks to Toni Ramos-Buades for sharing this!) Finally, this tutorial notebook has been confirmed to be self-consistent with its corresponding NRPy+ module, as documented [below](#code_validation). **Additional validation tests may have been performed, but are as yet, undocumented.**

## Author: Zach Etienne

### This notebook exists as the following Python module:
1. [PN_p_r.py](../../edit/NRPyPN/PN_p_r.py)

### This notebook & corresponding Python module depend on the following NRPy+/NRPyPN Python modules:
1. [indexedexp.py](../../edit/indexedexp.py): [**documentation+tutorial**](../Tutorial-Indexed_Expressions.ipynb)
1. [NRPyPN_shortcuts.py](../../edit/NRPyPN/NRPyPN_shortcuts.py): [**documentation**](NRPyPN_shortcuts.ipynb)

# Table of Contents
$$\label{toc}$$

1. [Part 0](#imports): Import needed Python modules
1. [Part 1](#strategy): Basic strategy for computing $p_r$
    1. [Part 1.a](#fullhamiltonian): Construct the full Hamiltonian consistent with a binary orbiting instantaneously in the $x$-$y$ plane
    1. [Part 1.b](#dr_dt) Computing $\frac{dr}{dt}$
    1. [Part 1.c](#p_r) Computing $p_r\left(\frac{dr}{dt},...\right)$ using two approaches
        1. [Part 1.c.i](#approach1_fullham) Approach 1: Use the full Hamiltonian
        1. [Part 1.c.ii](#approach2_fullham) Approach 2 (default approach): use the full Hamiltonian, with higher-order terms over the course of derivation removed
1. [Part 2](#code_validation): Validation Tests
    1. [Part 2.a](#code_validation_trans): Validation of transcribed versions for $p_r$
    1. [Part 2.b](#code_validation_2approaches): Comparison of two approaches for computing $p_r$
    1. [Part 2.c](#code_validationpub): Validation against trusted numerical values (i.e., in Table V of [Ramos-Buades, Husa, and Pratten (2018)](https://arxiv.org/abs/1810.00036))
    1. [Part 2.d](#code_validationpython): Validation against corresponding Python module
1. Part 2: [LaTeX PDF output](#latex_pdf_output): $\LaTeX$ PDF Output

# Part 0: Import needed Python modules \[Back to [top](#toc)\]
$$\label{imports}$$

```python
# Step 0: Add NRPy's directory to the path
# https://stackoverflow.com/questions/16780014/import-file-from-parent-directory
import sympy as sp                        # SymPy: The Python computer algebra package upon which NRPy+ depends
from NRPyPN_shortcuts import Pt,Pr,nU,div # NRPyPN: shortcuts for e.g., vector operations
```

# Part 1: Basic strategy for computing $p_r$ \[Back to [top](#toc)\]
$$\label{strategy}$$

## Part 1.a: Construct the full Hamiltonian consistent with a binary orbiting instantaneously in the $x$-$y$ plane \[Back to [top](#toc)\]
$$\label{fullhamiltonian}$$

As detailed in [Ramos-Buades, Husa, and Pratten (2018)](https://arxiv.org/abs/1810.00036), the basic strategy for computing $p_r$ first involves constructing the full Hamiltonian expression assuming that, consistent with a binary system initially on the $x$-axis and orbiting instantaneously on the $x$-$y$ plane, the momenta and normal vectors are given by:

\begin{align}
P_1^x &= -p_r\\
P_2^x &= +p_r\\
P_1^y &= +p_t\\
P_2^y &= -p_t\\
P_1^z = P_2^z &= 0\\
\mathbf{n} &= (1,0,0)
\end{align}

Now let's construct the full Hamiltonian, applying these assumptions as we go:

```python
# Step 1: Construct full Hamiltonian
#         expression for a binary instantaneously
#         orbiting on the xy plane, store
#         result to Htot_xyplane_binary
def f_Htot_xyplane_binary(m1, m2, n12U, n21U, S1U, S2U, p1U, p2U, r):
    def make_replacements(expr):
        zero = sp.sympify(0)
        one = sp.sympify(1)
        return expr.subs(p1U[1], Pt).subs(p2U[1], -Pt).subs(p1U[2], zero).subs(p2U[2], zero).subs(p1U[0], -Pr).subs(
            p2U[0], Pr) \
            .subs(nU[0], one).subs(nU[1], zero).subs(nU[2], zero)

    import PN_Hamiltonian_NS as H_NS
    H_NS.f_H_Newt__H_NS_1PN__H_NS_2PN(m1, m2, p1U, n12U, r)
    H_NS.f_H_NS_3PN(m1, m2, p1U, n12U, r)

    global Htot_xyplane_binary
    Htot_xyplane_binary = make_replacements(+H_NS.H_Newt
                                            + H_NS.H_NS_1PN
                                            + H_NS.H_NS_2PN
                                            + H_NS.H_NS_3PN)

    import PN_Hamiltonian_SO as H_SO
    H_SO.f_H_SO_1p5PN(m1, m2, n12U, n21U, S1U, S2U, p1U, p2U, r)
    H_SO.f_H_SO_2p5PN(m1, m2, n12U, n21U, S1U, S2U, p1U, p2U, r)
    H_SO.f_H_SO_3p5PN(m1, m2, n12U, n21U, S1U, S2U, p1U, p2U, r)
    Htot_xyplane_binary += make_replacements(+H_SO.H_SO_1p5PN
                                             + H_SO.H_SO_2p5PN
                                             + H_SO.H_SO_3p5PN)

    import PN_Hamiltonian_SS as H_SS
    H_SS.f_H_SS_2PN(m1, m2, S1U, S2U, nU, r)
    H_SS.f_H_SS_S1S2_3PN(m1, m2, n12U, S1U, S2U, p1U, p2U, r)
    H_SS.f_H_SS_S1sq_S2sq_3PN(m1, m2, n12U, n21U, S1U, S2U, p1U, p2U, r)
    Htot_xyplane_binary += make_replacements(+H_SS.H_SS_2PN
                                             + H_SS.H_SS_S1S2_3PN
                                             + H_SS.H_SS_S1sq_S2sq_3PN)

    import PN_Hamiltonian_SSS as H_SSS
    H_SSS.f_H_SSS_3PN(m1, m2, n12U, n21U, S1U, S2U, p1U, p2U, r)
    Htot_xyplane_binary += make_replacements(+H_SSS.H_SSS_3PN)
```

## Part 1.b: Computing $\frac{dr}{dt}$ \[Back to [top](#toc)\]
$$\label{dr_dt}$$

Hamilton's equations of motion imply that

$$
\frac{dr}{dt} = \frac{\partial H}{\partial p_r}.
$$

Next we Taylor expand $\frac{\partial H}{\partial p_r}$ in powers of $p_r$, about $p_r=0$:

\begin{align}
\frac{dr}{dt} = \frac{\partial H}{\partial p_r} = \left.\frac{\partial H}{\partial p_r}\right|_{p_r=0}
+ \frac{1}{1!} p_r \left.\frac{\partial^2 H}{\partial p_r^2}\right|_{p_r=0}
+ \frac{1}{2!} p_r^2 \left.\frac{\partial^3 H}{\partial p_r^3}\right|_{p_r=0}
+ \frac{1}{3!} p_r^3 \left.\frac{\partial^4 H}{\partial p_r^4}\right|_{p_r=0}
+ \mathcal{O}(p_r^4)
\end{align}
so to first order we get

$$
p_r \approx \left(\frac{dr}{dt} - \left.\frac{\partial H}{\partial p_r}\right|_{p_r=0} \right) \left( \left.\frac{\partial^2 H}{\partial p_r^2}\right|_{p_r=0} \right)^{-1}.
$$

Given the input masses and spins, we can compute $p_t$ using the formula given in [this NRPyPN notebook](PN-p_t.ipynb) (from [Ramos-Buades, Husa, and Pratten (2018)](https://arxiv.org/abs/1810.00036)), and the above will lead us with one equation and two unknowns: $p_r$ and $\frac{dr}{dt}$. This is an equation we can derive directly from our Hamiltonian expression, and compare the output to the expression derived to 3.5PN order in [Ramos-Buades, Husa, and Pratten (2018)](https://arxiv.org/abs/1810.00036).

Let's next construct an expression for $\frac{dr}{dt}$ in terms of known quantities, via

$$
\frac{dr}{dt}=\left(\frac{dE_{\rm GW}}{dt}+\frac{dM}{dt}\right)\left[\frac{dH_{\rm circ}}{dr}\right]^{-1},
$$
where

$$
\frac{dH_{\rm circ}(r,p_t(r))}{dr} = \frac{\partial H(p_r=0)}{\partial r}
+ \frac{\partial H(p_r=0)}{\partial p_t} \frac{\partial p_t}{\partial r},
$$

and the total energy flux
$$
\mathcal{E}(M\Omega,...) = \left(\frac{dE_{\rm GW}}{dt}+\frac{dM}{dt}\right)
$$

is given in terms of input parameters (e.g., masses and spins), plus $M\Omega$, which we also constructed in terms of the input masses, spins, and orbital separation $r$.

```python
# Function for computing dr/dt
def f_dr_dt(Htot_xyplane_binary, m1,m2, n12U, chi1U,chi2U, S1U,S2U, r):
    # First compute p_t
    import PN_p_t as pt
    pt.f_p_t(m1,m2, chi1U,chi2U, r)

    # Then compute dH_{circ}/dr = partial_H(p_r=0)/partial_r
    #                                  + partial_H(p_r=0)/partial_{p_t} partial_{p_t}/partial_r
    dHcirc_dr = (+sp.diff(Htot_xyplane_binary.subs(Pr,sp.sympify(0)),r)
                 +sp.diff(Htot_xyplane_binary.subs(Pr,sp.sympify(0)),Pt)*sp.diff(pt.p_t,r))

    # Then compute M\Omega
    import PN_MOmega as MOm
    MOm.f_MOmega(m1,m2, chi1U,chi2U, r)

    # Next compute dE_GW_dt_plus_dM_dt
    import PN_dE_GW_dt_and_dM_dt as dEdt
    dEdt.f_dE_GW_dt_and_dM_dt(MOm.MOmega, m1,m2, n12U, S1U,S2U)

    # Finally, compute dr/dt
    global dr_dt
    dr_dt = dEdt.dE_GW_dt_plus_dM_dt / dHcirc_dr
```

## Part 1.c: Computing $p_r\left(\frac{dr}{dt},...\right)$ using two approaches \[Back to [top](#toc)\]
$$\label{p_r}$$

### Part 1.c.i: Approach 1: Use the full Hamiltonian \[Back to [top](#toc)\]
$$\label{approach1_fullham}$$

First we use the approximation based on the Hamiltonian computed above:

$$
p_r \approx \left(\frac{dr}{dt} - \left.\frac{\partial H}{\partial p_r}\right|_{p_r=0} \right) \left[ \left.\frac{\partial^2 H}{\partial p_r^2}\right|_{p_r=0} \right]^{-1}
$$

```python
# Next we compute p_r as a function of dr_dt (unknown) and known quantities using
# p_r \approx [dr/dt - (partial_H/partial_{p_r})|_{p_r=0}] * [(partial^2_{H}/partial_{p_r^2})|_{p_r=0}]^{-1}
def f_p_r_fullHam(m1,m2, n12U,n21U, chi1U,chi2U, S1U,S2U, p1U,p2U, r):
    f_Htot_xyplane_binary(m1, m2, n12U, n21U, S1U, S2U, p1U, p2U, r)
    f_dr_dt(Htot_xyplane_binary, m1,m2, n12U, chi1U,chi2U, S1U,S2U, r)

    dHdpr_przero   = sp.diff(Htot_xyplane_binary,Pr).subs(Pr,sp.sympify(0))
    d2Hdpr2_przero = sp.diff(sp.diff(Htot_xyplane_binary,Pr),Pr).subs(Pr,sp.sympify(0))
    global p_r_fullHam
    p_r_fullHam = (dr_dt - dHdpr_przero)/(d2Hdpr2_przero)
```

### Part 1.c.ii: Approach 2 (default approach): use the full Hamiltonian, with higher-order terms over the course of derivation removed \[Back to [top](#toc)\]
$$\label{approach2_default}$$

This approach (Eq 2.16 of [Ramos-Buades, Husa, and Pratten (2018)](https://arxiv.org/abs/1810.00036)) uses the same approximation as Approach 1
$$
p_r \approx \left(\frac{dr}{dt} - \left.\frac{\partial H}{\partial p_r}\right|_{p_r=0} \right) \left[ \left.\frac{\partial^2 H}{\partial p_r^2}\right|_{p_r=0} \right]^{-1},
$$
except it throws away higher-order terms.

To reduce the possibility of copying error, the equation for $p_r$ is taken directly from the arXiv LaTeX source code of Eq 2.16 in [Ramos-Buades, Husa, and Pratten (2018)](https://arxiv.org/abs/1810.00036), and only mildly formatted to (1) improve presentation in Jupyter notebooks and (2) to ensure some degree of consistency in notation across different terms in other NRPyPN notebooks:

\begin{equation}
\begin{split}
p_r &= \left[-\frac{dr}{dt} \right.\\
&\quad\quad \left. +\frac{1}{r^{7/2}} \left( -\frac{(6 q+13) q^2 \chi _{1x} \chi _{2 y}}{4 (q+1)^4}-\frac{(6 q+1) q^2 \chi _{2 x} \chi _{2 y}}{4 (q+1)^4}+\chi _{1y} \left(-\frac{q (q+6) \chi _{1x}}{4 (q+1)^4}-\frac{q (13 q+6) \chi _{2 x}}{4 (q+1)^4}\right)\right) \right.\\
&\quad\quad \left. +\frac{1}{r^4} \left( \chi _{1z} \left(\frac{3 q (5 q+2) \chi _{1x} \chi _{2 y}}{2 (q+1)^4}   -\frac{3 q^2 (2 q+5) \chi _{2 x} \chi _{2 y}}{2 (q+1)^4}\right)+\chi _{1y} \chi _{2 z} \left(\frac{3 q^2 (2 q+5) \chi _{2 x}}{2 (q+1)^4}-\frac{3 q (5 q+2) \chi _{1x}}{2 (q+1)^4}\right)\right)\right] \\
& \times \left[ -\frac{(q+1)^2}{q}-\frac{1 \left(-7 q^2-15 q-7\right)}{2 q r}  \right.
 \\
&\quad\quad \left.  -\frac{47 q^4+229 q^3+363 q^2+229 q+47}{8 q (q+1)^2 r^2} -\frac{1}{r^{5/2}}\left( \frac{\left(4 q^2+11 q+12\right) \chi _{1z}}{4 q (q+1)}+\frac{\left(12 q^2+11 q+4\right) \chi _{2 z}}{4 (q+1)} \right)    \right. \\
&\quad\quad \left. \left.- \frac{1}{r^{7/2}}  \left( \frac{\left(-53 q^5-357 q^4-1097 q^3-1486 q^2-842 q-144\right) \chi _{1z}}{16 q (q+1)^4}+\frac{\left(-144 q^5-842 q^4-1486 q^3-1097 q^2-357 q-53\right) \chi _{2 z}}{16 (q+1)^4} \right)\right. \right. \\
&\quad\quad \left. -\frac{1}{r^3} \left(\frac{\left(q^2+9 q+9\right) \chi _{1x}^2}{2 q (q+1)^2}+\frac{\left(3 q^2+5 q+3\right) \chi _{2 x} \chi _{1x}}{(q+1)^2}+\frac{\left(3 q^2+8 q+3\right) \chi _{1y} \chi _{2 y}}{2 (q+1)^2}-\frac{9 q^2 \chi _{2 y}^2}{4 (q+1)}+\frac{\left(3 q^2+8 q+3\right) \chi _{1z} \chi _{2 z}}{2 (q+1)^2}-\frac{9 q^2 \chi _{2 z}^2}{4 (q+1)} \right. \right.
\\
&\quad\quad\quad \left. \left. +\frac{\left(9 q^3+9 q^2+q\right) \chi _{2 x}^2}{2 (q+1)^2}+\frac{-363 q^6-2608 q^5-7324 q^4-10161 q^3-7324 q^2-2608 q-363}{48 q (q+1)^4}-\frac{9 \chi _{1y}^2}{4 q (q+1)}-\frac{9 \chi _{1z}^2}{4 q (q+1)}-\frac{\pi ^2}{16} \right) \right]^{-1}.
\end{split}
\end{equation}

```python
# Here's the Ramos-Buades, Husa, and Pratten (2018)
#  approach for computing p_r.
# Transcribed from Eq 2.18 of
# Ramos-Buades, Husa, and Pratten (2018),
#   https://arxiv.org/abs/1810.00036
def f_p_r(m1,m2, n12U,n21U, chi1U,chi2U, S1U,S2U, p1U,p2U, r):
    q = m2/m1 # It is assumed that q >= 1, so m2 >= m1.
    f_Htot_xyplane_binary(m1, m2, n12U, n21U, S1U, S2U, p1U, p2U, r)
    f_dr_dt(Htot_xyplane_binary, m1,m2, n12U, chi1U,chi2U, S1U,S2U, r)
    chi1x = chi1U[0]
    chi1y = chi1U[1]
    chi1z = chi1U[2]
    chi2x = chi2U[0]
    chi2y = chi2U[1]
    chi2z = chi2U[2]
    p_r_num = (-dr_dt
               +(-(6*q+13)*q**2*chi1x*chi2y/(4*(q+1)**4)
                 -(6*q+ 1)*q**2*chi2x*chi2y/(4*(q+1)**4)
                 +chi1y*(-q*(   q+6)*chi1x/(4*(q+1)**4)
                         -q*(13*q+6)*chi2x/(4*(q+1)**4)))/r**div(7,2)
               +(+chi1z*(+3*q   *(5*q+2)*chi1x*chi2y/(2*(q+1)**4)
                         -3*q**2*(2*q+5)*chi2x*chi2y/(2*(q+1)**4))
                 +chi1y*chi2z*(+3*q**2*(2*q+5)*chi2x/(2*(q+1)**4)
                               -3*q   *(5*q+2)*chi1x/(2*(q+1)**4)))/r**4)
    p_r_den = (-(q+1)**2/q - (-7*q**2-15*q-7)/(2*q*r)
               -(47*q**4 + 229*q**3 + 363*q**2 + 229*q + 47)/(8*q*(q+1)**2*r**2)
               -(+( 4*q**2 + 11*q + 12)*chi1z/(4*q*(q+1))
                 +(12*q**2 + 11*q +  4)*chi2z/(4*  (q+1)))/r**div(5,2)
               -(+(- 53*q**5 - 357*q**4 - 1097*q**3 - 1486*q**2 - 842*q - 144)*chi1z/(16*q*(q+1)**4)
                 +(-144*q**5 - 842*q**4 - 1486*q**3 - 1097*q**2 - 357*q -  53)*chi2z/(16  *(q+1)**4))/r**div(7,2)
               -(+(  q**2 + 9*q + 9)*chi1x**2/(2*q*(q+1)**2)
                 +(3*q**2 + 5*q + 3)*chi2x*chi1x/((q+1)**2)
                 +(3*q**2 + 8*q + 3)*chi1y*chi2y/(2*(q+1)**2)
                 -9*q**2*chi2y**2/(4*(q+1))
                 +(3*q**2 + 8*q + 3)*chi1z*chi2z/(2*(q+1)**2)
                 -9*q**2*chi2z**2/(4*(q+1))
                 +(9*q**3 + 9*q**2 + q)*chi2x**2/(2*(q+1)**2)
                 +(-363*q**6 - 2608*q**5 - 7324*q**4 - 10161*q**3 - 7324*q**2 - 2608*q - 363)/(48*q*(q+1)**4)
                 -9*chi1y**2/(4*q*(q+1))
                 -9*chi1z**2/(4*q*(q+1)) - sp.pi**2/16)/r**3)
    global p_r
    p_r = p_r_num/p_r_den
```

```python
# Second version, used for validation purposes only.
def f_p_r_RHP2018v2(m1,m2, n12U,n21U, chi1U,chi2U, S1U,S2U, p1U,p2U, r):
    q = m2/m1
    f_Htot_xyplane_binary(m1, m2, n12U, n21U, S1U, S2U, p1U, p2U, r)
    f_dr_dt(Htot_xyplane_binary, m1,m2, n12U, chi1U,chi2U, S1U,S2U, r)
    chi1x = chi1U[0]
    chi1y = chi1U[1]
    chi1z = chi1U[2]
    chi2x = chi2U[0]
    chi2y = chi2U[1]
    chi2z = chi2U[2]
    p_r_num = (-dr_dt
               +(-(6*q+13)*q**2*chi1x*chi2y/(4*(q+1)**4)
                 -(6*q+1)*q**2*chi2x*chi2y/(4*(q+1)**4)
                 +chi1y*(-q*(q+6)*chi1x/(4*(q+1)**4) - q*(13*q+6)*chi2x/(4*(q+1)**4)))/r**div(7,2)
               +(+chi1z*(+div(3,2)*(q*   (5*q+2)*chi1x*chi2y)/(q+1)**4
                         -div(3,2)*(q**2*(2*q+5)*chi2x*chi2y)/(q+1)**4)
                 +chi1y*chi2z*(+div(3,2)*(q**2*(2*q+5)*chi2x)/(q+1)**4
                               -div(3,2)*(q*   (5*q+2)*chi1x)/(q+1)**4))/r**4)
    p_r_den = (-(q+1)**2/q
               -(-7*q**2-15*q-7)/(2*q*r)
               -(47*q**4 + 229*q**3 + 363*q**2 + 229*q + 47)/(8*q*(q+1)**2*r**2)
               -( (4*q**2+11*q+12)*chi1z/(4*q*(q+1)) + (12*q**2+11*q+4)*chi2z/(4*(q+1)) )/r**div(5,2)
               -(+(-53 *q**5 - 357*q**4 - 1097*q**3 - 1486*q**2 - 842*q - 144)*chi1z/(16*q*(q+1)**4)
                 +(-144*q**5 - 842*q**4 - 1486*q**3 - 1097*q**2 - 357*q - 53 )*chi2z/(16*  (q+1)**4) )/r**div(7,2)
               -(+(  q**2+9*q+9)*chi1x**2/(2*q*(q+1)**2)
                 +(3*q**2+5*q+3)*chi2x*chi1x/((q+1)**2)
                 +(3*q**2+8*q+3)*chi1y*chi2y/(2*(q+1)**2)
                 -(9*q**2*chi2y**2/(4*(q+1)))
                 +(3*q**2+8*q+3)*chi1z*chi2z/(2*(q+1)**2)
                 -(9*q**2*chi2z**2/(4*(q+1)))
                 +(9*q**3+9*q**2+q)*chi2x**2/(2*(q+1)**2)
                 +(-363*q**6 - 2608*q**5 - 7324*q**4 - 10161*q**3 - 7324*q**2 - 2608*q - 363)/(48*q*(q+1)**4)
                 -(9*chi1y**2)/(4*q*(q+1))
                 -(9*chi1z**2)/(4*q*(q+1))
                 -sp.pi**2/16) / r**3)
    global p_r_RHP2018v2
    p_r_RHP2018v2 = p_r_num/p_r_den
```

```python
# Third version, directly from Toni Ramos-Buades' Mathematica notebook (thanks Toni!)
def f_p_r_RHP2018v3(m1,m2, n12U,n21U, chi1U,chi2U, S1U,S2U, p1U,p2U, r):
    q = m2/m1
    f_Htot_xyplane_binary(m1, m2, n12U, n21U, S1U, S2U, p1U, p2U, r)
    f_dr_dt(Htot_xyplane_binary, m1,m2, n12U, chi1U,chi2U, S1U,S2U, r)
    chi1x = chi1U[0]
    chi1y = chi1U[1]
    chi1z = chi1U[2]
    chi2x = chi2U[0]
    chi2y = chi2U[1]
    chi2z = chi2U[2]
    Pi = sp.pi
    p_r_num = (-dr_dt
               +((chi1y*chi2z*((3*chi2x*q**2*(5 + 2*q))/(2*(1 + q)**4) - (3*chi1x*q*(2 + 5*q))/(2*(1 + q)**4)) + chi1z*((-3*chi2x*chi2y*q**2*(5 + 2*q))/(2*(1 + q)**4) + (3*chi1x*chi2y*q*(2 + 5*q))/(2*(1 + q)**4)))/
        r**4 + (-(chi2x*chi2y*q**2*(1 + 6*q))/(4*(1 + q)**4) - (chi1x*chi2y*q**2*(13 + 6*q))/(4*(1 + q)**4) + chi1y*(-(chi1x*q*(6 + q))/(4*(1 + q)**4) - (chi2x*q*(6 + 13*q))/(4*(1 + q)**4)))/r**(sp.Rational(7,2))))
    p_r_den = (-((1 + q)**2/q) - ((chi2z*(-53 - 357*q - 1097*q**2 - 1486*q**3 - 842*q**4 - 144*q**5))/(16*(1 + q)**4) + (chi1z*(-144 - 842*q - 1486*q**2 - 1097*q**3 - 357*q**4 - 53*q**5))/(16*q*(1 + q)**4))/r**(sp.Rational(7,2)) -
       (-Pi**2/16 - (9*chi1y**2)/(4*q*(1 + q)) - (9*chi1z**2)/(4*q*(1 + q)) - (9*chi2y**2*q**2)/(4*(1 + q)) - (9*chi2z**2*q**2)/(4*(1 + q)) + (chi1x**2*(9 + 9*q + q**2))/(2*q*(1 + q)**2) +
          (chi1x*chi2x*(3 + 5*q + 3*q**2))/(1 + q)**2 + (chi1y*chi2y*(3 + 8*q + 3*q**2))/(2*(1 + q)**2) + (chi1z*chi2z*(3 + 8*q + 3*q**2))/(2*(1 + q)**2) + (chi2x**2*(q + 9*q**2 + 9*q**3))/(2*(1 + q)**2) +
          (-363 - 2608*q - 7324*q**2 - 10161*q**3 - 7324*q**4 - 2608*q**5 - 363*q**6)/(48*q*(1 + q)**4))/r**3 - ((chi1z*(12 + 11*q + 4*q**2))/(4*q*(1 + q)) + (chi2z*(4 + 11*q + 12*q**2))/(4*(1 + q)))/r**(sp.Rational(5,2)) -
       (47 + 229*q + 363*q**2 + 229*q**3 + 47*q**4)/(8*q*(1 + q)**2*r**2) - (-7 - 15*q - 7*q**2)/(2*q*r))
    global p_r_RHP2018v3
    p_r_RHP2018v3 = p_r_num/p_r_den
```

# Part 2: Validation Tests \[Back to [top](#toc)\]
$$\label{code_validation}$$

## Part 2.a: Validation of transcribed versions for $p_r$ \[Back to [top](#toc)\]
$$\label{code_validation_trans}$$

As a code validation check, we verify agreement between
* the SymPy expressions transcribed from the cited published work on two separate occasions, and

```python
from NRPyPN_shortcuts import m1,m2, n12U,n21U, chi1U,chi2U, S1U,S2U, p1U,p2U, r # NRPyPN: import needed input variables

def error(varname):
    print("ERROR: When comparing Python module & notebook, "+varname+" was found not to match.")
    sys.exit(1)

# Validation against second transcription of the expressions:
f_p_r(          m1,m2, n12U,n21U, chi1U,chi2U, S1U,S2U, p1U,p2U, r)
f_p_r_RHP2018v2(m1,m2, n12U,n21U, chi1U,chi2U, S1U,S2U, p1U,p2U, r)
f_p_r_RHP2018v3(m1,m2, n12U,n21U, chi1U,chi2U, S1U,S2U, p1U,p2U, r)

if sp.simplify(p_r - p_r_RHP2018v2)           != 0: error("p_r_RHP2018v2")
if sp.simplify(p_r_RHP2018v2 - p_r_RHP2018v3) != 0: error("p_r_RHP2018v3")
print("TRANSCRIPTION TEST PASSES")
```

    TRANSCRIPTION TEST PASSES

## Part 2.b: Comparison of two approaches for computing $p_r$ \[Back to [top](#toc)\]
$$\label{code_validation_2approaches}$$

As a validation check, we inject random inputs into the two approaches for computing $p_r$ outlined above and compute the errors.

```python
import random # Standard Python module: provides random number generation functionality
from NRPyPN_shortcuts import num_eval, gamma_EulerMascheroni # NRPyPN: import numerical evaluation routine & gamma constant

def eval_random(i,trusted, other, p_t):
    random.seed(i)

    qmassratio = 1.0 + 7*random.random()  # must be >= 1
    nr         = 10. + 3*random.random()  # Orbital separation
    # Choose spins so that the total spin magnitude does not exceed 1.
    nchi1x     = -0.55 + 1.1*random.random()
    nchi1y     = -0.55 + 1.1*random.random()
    nchi1z     = -0.55 + 1.1*random.random()
    nchi2x     = -0.55 + 1.1*random.random()
    nchi2y     = -0.55 + 1.1*random.random()
    nchi2z     = -0.55 + 1.1*random.random()

    nPt        = num_eval(p_t,
                          qmassratio=qmassratio, nr=nr,
                          nchi1x=nchi1x,nchi1y=nchi1y,nchi1z=nchi1z,
                          nchi2x=nchi2x,nchi2y=nchi2y,nchi2z=nchi2z)
    trusted_result = num_eval(trusted,qmassratio=qmassratio, nr=nr,
                              nchi1x=nchi1x,nchi1y=nchi1y,nchi1z=nchi1z,
                              nchi2x=nchi2x,nchi2y=nchi2y,nchi2z=nchi2z,
                              nPt=nPt)
    other_result= num_eval(other,qmassratio=qmassratio, nr=nr,
                           nchi1x=nchi1x,nchi1y=nchi1y,nchi1z=nchi1z,
                           nchi2x=nchi2x,nchi2y=nchi2y,nchi2z=nchi2z,
                           nPt=nPt)

    relerror = abs((trusted_result-other_result)/trusted_result)
    print("%04d" % (i+1), "|", #"%.2f" % int(count+1)/int(i+1),
          "%.4e" % (relerror),"|","%.4e" % trusted_result,"%.4e" % other_result,"|",
          "%.2f" % (qmassratio),"%.2f" % nr,
          "%.2f" % (nchi1x),"%.2f" % (nchi1y),"%.2f" % (nchi1z),
          "%.2f" % (nchi2x),"%.2f" % (nchi2y),"%.2f" % (nchi2z))
    return relerror

import PN_p_t as pt
pt.f_p_t(m1,m2, chi1U,chi2U, r)

f_p_r_fullHam(m1,m2, n12U,n21U, chi1U,chi2U, S1U,S2U, p1U,p2U, r)
f_p_r(        m1,m2, n12U,n21U, chi1U,chi2U, S1U,S2U, p1U,p2U, r)

num_trials = 1
relerror_array = []
if num_trials > 100:
    def master_func(i):
        return eval_random(i,p_r_RHP2018,p_r,pt.p_t)

    import multiprocessing
    # ixp.zerorank1(DIM=1000) #0.0
    pool = multiprocessing.Pool()
    relerror_array = pool.map(master_func,range(1000))
    pool.terminate()
    pool.join()
else:
    for i in range(num_trials):
        relerror_array.append(eval_random(i,p_r,p_r_fullHam,pt.p_t))

avg_relerror = 0
for i in range(len(relerror_array)):
    avg_relerror += relerror_array[i]
avg_relerror /= len(relerror_array)
print("Average relative error: ","%.5e" % (avg_relerror))

# Without 3.5PN term in p_r denominator (comparing
#      Eq 2.16 of https://arxiv.org/pdf/1810.00036.pdf with
#      Eq 16 of https://arxiv.org/pdf/1702.00872.pdf):
#  average relative error in p_r over 1000 random inputs = 8.24534e-03

# With 3.5PN term in p_r:
#  average relative error in p_r over 1000 random inputs = 8.19325e-03
```

    0001 | 3.9019e-03 | 1.0678e-04 1.0720e-04 | 6.91 12.27 -0.09 -0.27 0.01 -0.10 0.31 -0.22
    Average relative error:  3.90192e-03

## Part 2.c: Validation against trusted numerical values (i.e., in Table V of [Ramos-Buades, Husa, and Pratten (2018)](https://arxiv.org/abs/1810.00036)) \[Back to [top](#toc)\]
$$\label{code_validationpub}$$

We will measure success in this validation to be within the error bar of the two values of $p_r$ given for each iteration, preferably being closer to the second iteration's value than the first.

```python
import PN_p_t as pt
pt.f_p_t(m1,m2, chi1U,chi2U, r)
f_p_r_fullHam(m1,m2, n12U,n21U, chi1U,chi2U, S1U,S2U, p1U,p2U, r)
f_p_r(        m1,m2, n12U,n21U, chi1U,chi2U, S1U,S2U, p1U,p2U, r)
import PN_p_r as pr
pr.f_p_r(m1,m2, n12U,n21U, chi1U,chi2U, S1U,S2U, p1U,p2U, r)
p_r = pr.p_r
```

```python
# Useful function for comparing published & NRPyPN results
def compare_pub_NPN(desc,pub0,pub1,  qmassratio, nr, nchi1x,nchi1y,nchi1z, nchi2x,nchi2y,nchi2z):
    nPt        = num_eval(pt.p_t,
                          qmassratio=qmassratio, nr=nr,
                          nchi1x=nchi1x,nchi1y=nchi1y,nchi1z=nchi1z,
                          nchi2x=nchi2x,nchi2y=nchi2y,nchi2z=nchi2z)

    p_r_approach1 = num_eval(p_r_fullHam, qmassratio = qmassratio, nr=nr,
                             nchi1x=nchi1x, nchi1y=nchi1y, nchi1z=nchi1z,
                             nchi2x=nchi2x, nchi2y=nchi2y, nchi2z=nchi2z,
                             nPt=nPt)

    p_r_approach2 = num_eval(p_r, qmassratio = qmassratio, nr=nr,
                             nchi1x=nchi1x, nchi1y=nchi1y, nchi1z=nchi1z,
                             nchi2x=nchi2x, nchi2y=nchi2y, nchi2z=nchi2z,
                             nPt=nPt)

    print("##################################################")
    print("  "+desc)
    print("##################################################")
    print("p_r = %.9f" % pub0, " <- Expected result, from Table V of Ramos-Buades, Husa, and Pratten (2018)")
    print("p_r = %.9f" % p_r_approach1," <- Result from NRPyPN, approach 1")
    print("p_r = %.9f" % p_r_approach2," <- Result from NRPyPN, approach 2")
    print("p_r = %.9f" % pub1, " <- Result at 2nd iteration, from Table V of Ramos-Buades, Husa, and Pratten (2018)")
    relerrorpct1  = abs((pub0-p_r_approach1)/pub0)*100
    relerrorpct2  = abs((pub0-p_r_approach2)/pub0)*100
    relerror01pct = abs((pub0-pub1)/pub1)*100
    strrelerror1pct  = "%.4f" % (relerrorpct1)
    strrelerror2pct  = "%.4f" % (relerrorpct2)
    strrelerror01pct = "%.4f" % (relerror01pct)
    print("Relative error between published iteration 0 vs it. 1: "+strrelerror01pct+"%")
    resultstring1 = "Relative error between NRPyPN & published, approach 1: "+strrelerror1pct+"%"
    resultstring2 = "Relative error between NRPyPN & published, approach 2: "+strrelerror2pct+"%"
    if relerrorpct1 > relerror01pct:
        resultstring1 += " < "+strrelerror01pct+"%  <--- NOT GOOD!"
        resultstring2 += " < "+strrelerror01pct+"%  <--- NOT GOOD!"
    else:
        resultstring1 += " < "+strrelerror01pct+"%  <--- GOOD AGREEMENT!"
        resultstring2 += " < "+strrelerror01pct+"%  <--- GOOD AGREEMENT!"
    print(resultstring1)
    print(resultstring2+"\n")
```

```python
# 1. Let's consider the case:
# * Mass ratio q=1, chi1=chi2=(0,0,0), radial separation r=12
pub_result0 = 0.53833e-3 # Expected result, from Table V of Ramos-Buades, Husa, and Pratten (2018) https://arxiv.org/abs/1810.00036
pub_result1 = 0.468113e-3 # Expected result 2nd iteration, from Table V of Ramos-Buades, Husa, and Pratten (2018) https://arxiv.org/abs/1810.00036
qmassratio = 1.0  # must be >= 1
nr         = 12.  # Orbital separation
# Choose spins so that the total spin magnitude does not exceed 1.
nchi1x     = +0.
nchi1y     = +0.
nchi1z     = +0.
nchi2x     = +0.
nchi2y     = +0.
nchi2z     = +0.

compare_pub_NPN("Case: q=1, nonspinning, initial separation 12",pub_result0,pub_result1,
                qmassratio, nr, nchi1x,nchi1y,nchi1z, nchi2x,nchi2y,nchi2z)
```

    ##################################################
      Case: q=1, nonspinning, initial separation 12
    ##################################################
    p_r = 0.000538330  <- Expected result, from Table V of Ramos-Buades, Husa, and Pratten (2018)
    p_r = 0.000539171  <- Result from NRPyPN, approach 1
    p_r = 0.000539860  <- Result from NRPyPN, approach 2
    p_r = 0.000468113  <- Result at 2nd iteration, from Table V of Ramos-Buades, Husa, and Pratten (2018)
    Relative error between published iteration 0 vs it. 1: 15.0000%
    Relative error between NRPyPN & published, approach 1: 0.1562% < 15.0000%  <--- GOOD AGREEMENT!
    Relative error between NRPyPN & published, approach 2: 0.2843% < 15.0000%  <--- GOOD AGREEMENT!

```python
# 2. Let's consider the case:
# * Mass ratio q=1.5, chi1= (0,0,-0.6); chi2=(0,0,0.6), radial separation r=10.8
pub_result0 = 0.699185e-3 # Expected result, from Table V of Ramos-Buades, Husa, and Pratten (2018) https://arxiv.org/abs/1810.00036
pub_result1 = 0.641051e-3 # Expected result, from Table V of Ramos-Buades, Husa, and Pratten (2018) https://arxiv.org/abs/1810.00036
qmassratio =  1.5  # must be >= 1
nr         = 10.8  # Orbital separation
nchi1x     = +0.
nchi1y     = +0.
nchi1z     = -0.6
nchi2x     = +0.
nchi2y     = +0.
nchi2z     = +0.6
compare_pub_NPN("Case: q=1.5, chi1z=-0.6, chi2z=0.6, initial separation 10.8",pub_result0,pub_result1,
                qmassratio, nr, nchi1x,nchi1y,nchi1z, nchi2x,nchi2y,nchi2z)
```

    ##################################################
      Case: q=1.5, chi1z=-0.6, chi2z=0.6, initial separation 10.8
    ##################################################
    p_r = 0.000699185  <- Expected result, from Table V of Ramos-Buades, Husa, and Pratten (2018)
    p_r = 0.000675944  <- Result from NRPyPN, approach 1
    p_r = 0.000677020  <- Result from NRPyPN, approach 2
    p_r = 0.000641051  <- Result at 2nd iteration, from Table V of Ramos-Buades, Husa, and Pratten (2018)
    Relative error between published iteration 0 vs it. 1: 9.0685%
    Relative error between NRPyPN & published, approach 1: 3.3240% < 9.0685%  <--- GOOD AGREEMENT!
    Relative error between NRPyPN & published, approach 2: 3.1702% < 9.0685%  <--- GOOD AGREEMENT!

```python
# 3. Let's consider the case:
# * Mass ratio q=4, chi1= (0,0,-0.8); chi2=(0,0,0.8), radial separation r=11
pub_result0 = 0.336564e-3 # Expected result, from Table V of Ramos-Buades, Husa, and Pratten (2018) https://arxiv.org/abs/1810.00036
pub_result1 = 0.24708e-3 # Expected result, from Table V of Ramos-Buades, Husa, and Pratten (2018) https://arxiv.org/abs/1810.00036
qmassratio =  4.0  # must be >= 1
nr         = 11.0  # Orbital separation
nchi1x     = +0.
nchi1y     = +0.
nchi1z     = -0.8
nchi2x     = +0.
nchi2y     = +0.
nchi2z     = +0.8
compare_pub_NPN("Case: q=4.0, chi1z=-0.8, chi2z=0.8, initial separation 11.0",pub_result0,pub_result1,
                qmassratio, nr, nchi1x,nchi1y,nchi1z, nchi2x,nchi2y,nchi2z)
```

    ##################################################
      Case: q=4.0, chi1z=-0.8, chi2z=0.8, initial separation 11.0
    ##################################################
    p_r = 0.000336564  <- Expected result, from Table V of Ramos-Buades, Husa, and Pratten (2018)
    p_r = 0.000251240  <- Result from NRPyPN, approach 1
    p_r = 0.000251143  <- Result from NRPyPN, approach 2
    p_r = 0.000247080  <- Result at 2nd iteration, from Table V of Ramos-Buades, Husa, and Pratten (2018)
    Relative error between published iteration 0 vs it. 1: 36.2166%
    Relative error between NRPyPN & published, approach 1: 25.3515% < 36.2166%  <--- GOOD AGREEMENT!
    Relative error between NRPyPN & published, approach 2: 25.3804% < 36.2166%  <--- GOOD AGREEMENT!

```python
# 4. Let's consider the case:
# * Mass ratio q=2, chi1= (0,0,0); chi2=(−0.3535, 0.3535, 0.5), radial separation r=10.8
pub_result0 = 0.531374e-3 # Expected result, from Table V of Ramos-Buades, Husa, and Pratten (2018) https://arxiv.org/abs/1810.00036
pub_result1 = 0.448148e-3 # Expected result, from Table V of Ramos-Buades, Husa, and Pratten (2018) https://arxiv.org/abs/1810.00036
qmassratio =  2.0  # must be >= 1
nr         = 10.8  # Orbital separation
nchi1x     = +0.
nchi1y     = +0.
nchi1z     = +0.
nchi2x     = -0.3535
nchi2y     = +0.3535
nchi2z     = +0.5
compare_pub_NPN("Case: q=2.0, chi2x=-0.3535, chi2y=+0.3535, chi2z=+0.5, initial separation 10.8",pub_result0,pub_result1,
                qmassratio, nr, nchi1x,nchi1y,nchi1z, nchi2x,nchi2y,nchi2z)
```

    ##################################################
      Case: q=2.0, chi2x=-0.3535, chi2y=+0.3535, chi2z=+0.5, initial separation 10.8
    ##################################################
    p_r = 0.000531374  <- Expected result, from Table V of Ramos-Buades, Husa, and Pratten (2018)
    p_r = 0.000545357  <- Result from NRPyPN, approach 1
    p_r = 0.000542626  <- Result from NRPyPN, approach 2
    p_r = 0.000448148  <- Result at 2nd iteration, from Table V of Ramos-Buades, Husa, and Pratten (2018)
    Relative error between published iteration 0 vs it. 1: 18.5711%
    Relative error between NRPyPN & published, approach 1: 2.6314% < 18.5711%  <--- GOOD AGREEMENT!
    Relative error between NRPyPN & published, approach 2: 2.1175% < 18.5711%  <--- GOOD AGREEMENT!

```python
# 5. Let's consider the case:
# * Mass ratio q=8, chi1= (0, 0, 0.5); chi2=(0, 0, 0.5), radial separation r=11
pub_result0 = 0.102969e-3 # Expected result, from Table V of Ramos-Buades, Husa, and Pratten (2018) https://arxiv.org/abs/1810.00036
pub_result1 = 0.139132e-3 # Expected result, from Table V of Ramos-Buades, Husa, and Pratten (2018) https://arxiv.org/abs/1810.00036
qmassratio =  8.0  # must be >= 1
nr         = 11.0  # Orbital separation
nchi1x     = +0.
nchi1y     = +0.
nchi1z     = +0.5
nchi2x     = +0.
nchi2y     = +0.
nchi2z     = +0.5
compare_pub_NPN("""
Case: q=8.0, chi1z=chi2z=+0.5, initial separation 11

Note: This one is weird. Clearly the value in the table
      has a typo, such that the p_r and p_t values
      should be interchanged; p_t is about 20% the
      next smallest value in the table, and the
      parameters aren't that different. We therefore
      assume that this is the case, and nonetheless
      find agreement for p_t with the published result
      to about 0.07%, and p_r to about 5%. Aiven that
      the table values seem to be clearly wrong, this
      level of agreement is an encouraging sign.
""",pub_result0,pub_result1,
                qmassratio, nr, nchi1x,nchi1y,nchi1z, nchi2x,nchi2y,nchi2z)
```

    ##################################################

    Case: q=8.0, chi1z=chi2z=+0.5, initial separation 11

    Note: This one is weird. Clearly the value in the table
          has a typo, such that the p_r and p_t values
          should be interchanged; p_t is about 20% the
          next smallest value in the table, and the
          parameters aren't that different. We therefore
          assume that this is the case, and nonetheless
          find agreement for p_t with the published result
          to about 0.07%, and p_r to about 5%. Aiven that
          the table values seem to be clearly wrong, this
          level of agreement is an encouraging sign.

    ##################################################
    p_r = 0.000102969  <- Expected result, from Table V of Ramos-Buades, Husa, and Pratten (2018)
    p_r = 0.000097694  <- Result from NRPyPN, approach 1
    p_r = 0.000097574  <- Result from NRPyPN, approach 2
    p_r = 0.000139132  <- Result at 2nd iteration, from Table V of Ramos-Buades, Husa, and Pratten (2018)
    Relative error between published iteration 0 vs it. 1: 25.9919%
    Relative error between NRPyPN & published, approach 1: 5.1225% < 25.9919%  <--- GOOD AGREEMENT!
    Relative error between NRPyPN & published, approach 2: 5.2399% < 25.9919%  <--- GOOD AGREEMENT!

## Part 2.d: Validation against corresponding Python module \[Back to [top](#toc)\]
$$\label{code_validationpython}$$

Here we verify agreement between

* the SymPy expressions generated in this notebook, and the corresponding Python module.

```python
def error(varname):
    print("ERROR: When comparing Python module & notebook, "+varname+" was found not to match.")
    sys.exit(1)

# Validation against Python module
import PN_p_r as pr
f_p_r(   m1,m2, n12U,n21U, chi1U,chi2U, S1U,S2U, p1U,p2U, r)
pr.f_p_r(m1,m2, n12U,n21U, chi1U,chi2U, S1U,S2U, p1U,p2U, r)

if sp.simplify(p_r - pr.p_r) != 0: error("pr.p_r")
print("PYTHON MODULE TEST PASSES")
```

    PYTHON MODULE TEST PASSES

# Part 3: Output this notebook to $\LaTeX$-formatted PDF file \[Back to [top](#toc)\]
$$\label{latex_pdf_output}$$

The following code cell converts this Jupyter notebook into a proper, clickable $\LaTeX$-formatted PDF file. After the cell is successfully run, the generated PDF may be found in the root NRPy+ tutorial directory, with filename
[PN-p_r.pdf](PN-p_r.pdf) (Note that clicking on this link may not work; you may need to open the PDF file through another means.)

```python
import os,sys                    # Standard Python modules for multiplatform OS-level functions
import cmdline_helperNRPyPN as cmd    # NRPy+: Multi-platform Python command-line interface
cmd.output_Jupyter_notebook_to_LaTeXed_PDF("PN-p_r",location_of_template_file=os.path.join(".."))
```

    Created PN-p_r.tex, and compiled LaTeX file to PDF file PN-p_r.pdf


# A toy example for modeling complex drug-protein interactions using RAF kinases and RAF inhibitors

Here, we provide the step-by-step construction code for a toy example to model complex drug-protein interactions using PySB with energy formulation through support for energy BioNetGen (Sekar JAP et al, 2016). This example describes RAF kinases as the drug target and RAF inhibitors as the drug (as developed in Kholodenko B., 2015). To run this code you'll need to have Pysb with BNG installed, please follow instructions at: http://pysb.org/ .

### Manual definition of the biochemical reaction system

To start, we import all required Pysb classes and instantiate the model:

```python
from pysb import Model, Monomer, Parameter, Expression,  Rule, Observable, Initial, Annotation, EnergyPattern, ANY
from pysb.bng import generate_equations
from pysb.export import export
from pysb.core import as_complex_pattern, ComplexPattern
from sympy import exp, log

Model();
model.name='toy_example_RAF_RAFi';
```

Next, we define the two basic components of the model, RAF kinases (R) and RAF inhibitors (I):

```python
#define a monomer R that represents a RAF kinase with a binding site for RAF (r) and another for the drug (i)
Monomer('R', ['r', 'i']);
#define a monomer I that represents a RAF inhibitor with a binding site for RAF (r)
Monomer('I',['r']);
```

We define the parameters for initializing abundance of components:

```python
#define the initial conditions for R and I
Parameter('R_0',0.01);  # uM
Parameter('I_0',0.0);  # uM
Initial(R(r=None, i=None), R_0);
Initial(I(r=None), I_0);
```

Then, we define the kinetic parameters and thermodynamic factors:

```python
#define dissociation constant (kD), forward rate (kf) and distributionr rate (phi) for RAF dimerization
Parameter('kr_RR',10);  #/s
Parameter('kf_RR',1.0);  #/s/uM
Parameter('phi_RR',1.0); #unitless

#define dissociation constant (kD), forward rate (kf) and distributionr rate (phi) for drug binding to RAF
Parameter('kr_RI',0.1);  #/s
Parameter('kf_RI',1.0);  #/s/uM
Parameter('phi_RI',1.0); #unitless

#define thermodynamic factors f and g
Parameter('f',1.0);  #unitless
Parameter('g',1.0);  #unitless
```

We convert the kinetic parameters into corresponding energy parameters:

```python
#convert kinetic parameters into energies for RAF dimerization
Expression('Gf_RR', log(kr_RR/kf_RR)); #unitless
Expression('Ea0_RR',-phi_RR*log(kr_RR/kf_RR)-log(kf_RR)); #unitless

#convert kinetic parameters into energies for drug binding to RAF
Expression('Gf_RI', log(kr_RI/kf_RI)); #unitless
Expression('Ea0_RI',-phi_RI*log(kr_RI/kf_RI)-log(kf_RI)); #unitless

#convert thermodynamic factors into energies
Expression('Gf_f',log(f)); #unitless
Expression('Gf_g',log(g)); #unitless
```

We define the energy patterns to assign energies within biochemical species:

```python
# define energy in bond between R and R
EnergyPattern('ep_RR',R(r=1)%R(r=1),Gf_RR);

# define energy in bond between R and I
EnergyPattern('ep_RI',R(i=1)%I(r=1),Gf_RI);

# define additional energy in bond betwee RAF dimer and a single drug molecule
Expression('Gf_RRI', Gf_f);
EnergyPattern('ep_RRI',R(r=1,i=None)%R(r=1,i=2)%I(r=2), Gf_RRI);

# define additional energy in bond betwee RAF dimer and two drug molecules
Expression('Gf_IRRI', Gf_f + Gf_g);
EnergyPattern('ep_IRRI',I(r=2)%R(r=1,i=2)%R(r=1,i=3)%I(r=3), Gf_IRRI);
```

We define observables that are used later to visualize results from model simulations:

```python
# define observable for total RAF and total drug
Observable('Rtot_obs', R());
Observable('Itot_obs', I());

#define an observable that counts the amount of active RAF when RAF represents a BRAF V600E/K mutant
#that is active independently of dimerization stauts (i.e. both as a monomer or as a dimer) as long as it is not drug bound
Observable('R_BRAFmut_active_obs', R(i=None));

#define an observable that counts the amount of active RAF when RAF here represents a wild type version of BRAF or CRAF
#that is active only when dimerized and not drug bound
Observable('R_RAFwt_active_obs', R(r=1,i=None)%R(r=1));

# define observable for drug unbound RAF monomer
Observable('R_obs', R(i=None,r=None), match='species');

# define observable for RAF dimer unbound by drug
Observable('RR_obs', R(r=1,i=None)%R(r=1,i=None), match='species');

# define observable for RAF dimer bound by single drug
Observable('RRI_obs', R(r=1,i=None)%R(r=1,i=2)%I(r=2), match='species');

# define observable for RAF dimer bound by double drug
Observable('IRRI_obs', I(r=2)%R(r=1,i=2)%R(r=1,i=3)%I(r=3), match='species');
```

As the last step in the model construction, we define the reactions for RAF dimerization and drug binding:

```python
#define RAF dimerization reaction
Rule('RR', R(r=None)+R(r=None) | R(r=1)%R(r=1) , phi_RR, Ea0_RR, energy=True);

#define drug binding to RAF reaction
Rule('RI', R(i=None)+I(r=None) | R(i=1)%I(r=1) , phi_RI, Ea0_RI, energy=True);
```

### Automatic generation of the kinetic model

We generate the kinetic model by passing the information build via PySB to BNG, parse the returned reaction network and list the properties of the resulting kinetic model:

```python
from util_display import display_model_info
from pysb.export.sbml import SbmlExporter as smblexport

# generate the model equations
generate_equations(model)

#display model informations
display_model_info(model)

#save the generated model in PySB and BNG format
generated_model_code = export(model, 'pysb_flat')
with open(model.name+'.py', 'wt') as f:
    f.write(generated_model_code);
generated_model_code = export(model, 'bngl')
with open(model.name+'.bngl', 'wt') as f:
    f.write(generated_model_code);
generated_model_code = export(model, 'sbml')
with open(model.name+'.sbml', 'wt') as f:
    f.write(generated_model_code)
```

    Model information
    Species: 6
    Parameters: 12
    Expressions: 8
    Observables: 8
    Total Rules: 2
    Energy Rules: 2
    Non-energy Rules: 0
    Energy Patterns: 4
    Reactions: 12

Now, we visualize the species and the forward and backward rates and dissociation constants in the model to check that thermodynamic factors indeed control cooperative reaction rates:

```python
from util_display import format_species_reactions, display_table
import pandas as pd
# prevent pandas from truncating long LaTeX expressions when rendering.
pd.options.display.max_colwidth=None

#obtain dataframe with math latex expression visualization for species and reactions
(speciesdisp, reactionsdisp)=format_species_reactions(model);
display_table(speciesdisp, caption='SPECIES');
display_table(reactionsdisp, caption='REACTIONS');
```

SPECIES

        ID	        Pattern

                                \[s_{0}\]
                        R(r=None, i=None)

                                \[s_{1}\]
                        I(r=None)

                                \[s_{2}\]
                        R(r=1, i=None) % R(r=1, i=None)

                                \[s_{3}\]
                        I(r=1) % R(r=None, i=1)

                                \[s_{4}\]
                        I(r=1) % R(r=2, i=1) % R(r=2, i=None)

                                \[s_{5}\]
                        I(r=1) % I(r=2) % R(r=3, i=1) % R(r=3, i=2)

REACTIONS

        ID	        Base rule	        Reaction	        Forward rate ($k$ )	        Backward rate ($kr$ )	        Dissociation constant ($K_d$ )

                                \[R_{0}\]
                        RR
                        $s_{0}$ + $s_{0}$ ⇄ $s_{2}$
                        \[0.5 \cdot k_{RR}\]
                        \[kr_{RR}\]
                        \[\frac{2.0 \cdot kr_{RR}}{k_{RR}}\]

                                \[R_{1}\]
                        RI
                        $s_{0}$ + $s_{1}$ ⇄ $s_{3}$
                        \[k_{RI}\]
                        \[kr_{RI}\]
                        \[\frac{kr_{RI}}{k_{RI}}\]

                                \[R_{2}\]
                        RR
                        $s_{0}$ + $s_{3}$ ⇄ $s_{4}$
                        \[f^{- \phi_{RR}} \cdot k_{RR}\]
                        \[f^{1 - \phi_{RR}} \cdot kr_{RR}\]
                        \[\frac{f \cdot kr_{RR}}{k_{RR}}\]

                                \[R_{3}\]
                        RR
                        $s_{3}$ + $s_{3}$ ⇄ $s_{5}$
                        \[0.5 \cdot k_{RR} \cdot \left(\frac{1}{f \cdot g}\right)^{\phi_{RR}}\]
                        \[kr_{RR} \cdot \left(f \cdot g\right)^{1 - \phi_{RR}}\]
                        \[\frac{2.0 \cdot f \cdot g \cdot kr_{RR}}{k_{RR}}\]

                                \[R_{4}\]
                        RI
                        $s_{1}$ + $s_{2}$ ⇄ $s_{4}$
                        \[2.0 \cdot f^{- \phi_{RI}} \cdot k_{RI}\]
                        \[f^{1 - \phi_{RI}} \cdot kr_{RI}\]
                        \[\frac{0.5 \cdot f \cdot kr_{RI}}{k_{RI}}\]

                                \[R_{5}\]
                        RI
                        $s_{1}$ + $s_{4}$ ⇄ $s_{5}$
                        \[g^{- \phi_{RI}} \cdot k_{RI}\]
                        \[2.0 \cdot g^{1 - \phi_{RI}} \cdot kr_{RI}\]
                        \[\frac{2.0 \cdot g \cdot kr_{RI}}{k_{RI}}\]

### Model simulation of drug-dose response at steady state

We use the generated model to simulate the response of RAF kinases to three classes of RAF inhibitors: 1st generation (e.g. Vemurafenib, Dabrafenib and Encorafenib), paradox breakers (e.g. PLX8349) and panRAF (e.g. LY3009120, AZ628) inhibitors. We compare the results to a hypothetical RAF inhibitor that has no cooperative effect with RAF dimerization (f and g termodynamic parameters are unity so they do not impose any extra energy). We observe the effect of the drugs in situations with low and high propensity for RAF dimerization (controlled by setting the Kd values of the RAF dimerization reactions) to study the effect that the drugs have in absence or presence of the dimerization status of RAFs, for example as induced by Ras-GTP signal that induces RAF dimerization. This analysis is run to steady state, meaning that the drug-dose response represent the inhibitory response achieved when all reactions are let to run until they equilibrate. First, we set up the model with the right parameter values and with a drug-dose response concentrations of the RAF inhibitor:

```python
#import ODE simulator
from pysb.simulator import ScipyOdeSimulator
#import various utility packages
import numpy as np

#set the dilution range for the RAF inhibitor
RAFi_dil=np.logspace(-4, 1, 20, base=10.0); #uM
#set the values of f and g to model RAF inhibitors with different complex drug-protein interactions
#independent: f=1, g=1 , 1st generation: f= 0.001, g=1000; paradox breaker: f= 1.0, g=1000; panRAF: f= 0.001, g=1
ff=[1.0, 0.001, 1.0, 0.001];
gg=[1.0, 1000, 1000, 1];
fgtitle=['non-cooperative', '1st_gen', 'paradox_breaker', 'panRAF'];
#set the Kd values to use for baseline RAF dimerization to simulate Ras-GTP signaling that induced dimerization
RR_kfs_exp=np.linspace(-1, 5, 13);
RR_kfs=10**RR_kfs_exp;

#set up the ODE simulator for the model
sim = ScipyOdeSimulator(model);
```

Then, we perform multiple simulations of the systems at each defined combination of thermodynamic parameters (f,g), RAF inhibitor concentration (RAFi) and RAF dimerization baseline (RR_Kd):

```python
%matplotlib notebook
import matplotlib.pyplot as plt
import math
from tqdm.notebook import tqdm, trange
from util_simulation import equilibrate

#create a bar to keep track of simulation progress
p_bar_sim = tqdm(desc='Simulation progress', total=len(ff)*len(RR_kfs)*len(RAFi_dil))

#define observables to plot
plt_obs=['R_BRAFmut_active_obs', 'R_RAFwt_active_obs', 'R_obs', 'RR_obs', 'RRI_obs', 'IRRI_obs'];
plot_obs_names=['RAF_mut', 'RAF_wt', 'R', 'RR', 'RRI', 'IRRI'];

#define figure
fig, ax = plt.subplots(len(plt_obs),len(gg), sharey=True);
fig.suptitle("Simulations of RAF inhibitor dose-response effect on RAF activity");

#define plot colors
cmap=plt.get_cmap('copper');
col=cmap(np.linspace(0.0, 1.0, len(RR_kfs_exp)))[::-1];

#simulate the different parameter combinations
ss_v = np.empty([len(RAFi_dil), len(plt_obs)]);
for i in range (len(ff)):
    for j in range(len(RR_kfs)):
        for k in range(len(RAFi_dil)):
            #run simulation with modified parameters
            param_values={'f': ff[i] ,'g': gg[i], 'kf_RR': RR_kfs[j], 'I_0': RAFi_dil[k]};
            #run this to assure model is run to steady_ state,
            res=equilibrate(sim, param_values=param_values)
            #update progress
            p_bar_sim.update(1);
            #extract end of simulation for each osbervables from the dataframe of simulation results
            ss_v[k,:]=res.dataframe[plt_obs].iloc[-1];

        #plot the results for a given RR_KD and f,g combination
        for z in range(len(plt_obs)):
             #plot simualtion
             h=ax[z,i].plot(RAFi_dil, ss_v[:,z], color = col[j,:]);
             ax[z,i].set_xscale('log');
             #set axis names
             if (i==0):
                ax[z,i].set_ylabel(plot_obs_names[z]);
             if (z==0):
                ax[z,i].title.set_text(fgtitle[i]);
             if (z==(len(plt_obs)-1)):
                ax[z,i].set_xlabel('RAFi (uM)');
             else:
                ax[z,i].set_xticklabels([]);

#add legend
fig.legend(ax, labels= list(map(str, math.log(model.parameters['kr_RR'].value,10)-RR_kfs_exp)) ,
           bbox_to_anchor=(1.04,1), loc="upper right", borderaxespad=0.1,
           title="Kd_RR (log10)");
```

    Simulation progress:   0%|          | 0/1040 [00:00<?, ?it/s]

    <IPython.core.display.Javascript object>

The resulting simulations show the expected behavior of these four different RAF inhibitor classes on both the situation of RAF representing a BRAF V600E/K mutant or wild-type BRAF/CRAF:

* **Non-cooperative RAF inhibitor** - In the first column, the independent RAF inhibitor does not change its affinity to RAF depending on dimerization status, nor does it influence dimerization itself. As such, BRAF mutant inhibition is effective (first row) and independent on RAF dimerization from upstream signal (RR_kD). Similarly, the effect on a wild-type RAF context is a straightforward dose-dependent inhibition of RAF signaling that depends on the baseline RAF dimerization due to upstream signal (RR_kD) (second row). Note that there is no difference in the potency of the drug between the oncogenic BRAF mutant and RAF wild-type signal (compare first and second panel).

* **1st generation RAF inhibitor** - In the second column, the 1st generation RAF inhibitor efficiently inhibits RAF signaling from monomers (light colored line), but RAF dimeric signal generated by either upstream RAF dimerization (RR_Kd) or by the propensity of the drug to cause RAF dimerization (f parameter less than one) creates a resistance mechanism (seen as the increased amount of active RAFs in the first row when comparing light and dark colored lines). This resistance mechanism is due to the low affinity of the drug for the second RAF protomer in a RAF dimer (g parameter more than one), as seen by the rise of single drug-bound RAF dimers in the 5th row. In addition, wild-type RAF signaling is potentiated by the drug, as can be seen by the increase in active RAF signaling (2nd row) that is drug-dependent at otherwise low levels of upstream RAF dimerization. This effect, known to induced toxicity through inducing proliferation of other malignant cells by drug-induced MAPK activation, is also known as paradoxical activation.

* **Paradox-breaker RAF inhibitor** - In the third column, a paradox breaker RAF inhibitor is seen reducing the extend of resistance due to RAF dimerization (1st row) in the BRAF-mutant case and of RAF dimerization potentiation by paradoxical activation (2nd row), since the drug binding does not induce dimerization (f is equal to one) and thus does not synergize with the upstream RAF dimerization signal. This can be seen as the reduced amount of single drug-bound RAF dimers in the 5th row.

* **panRAF inhibitor** - In the last column, a panRAF inhibitor is seen eventually binding both protomers in RAF dimers (g is equal to one), thus completely ablating any resistance mechanism caused by RAF dimerization. This can be seen as the eventual reduction in the amount of single drug-bound RAF dimers in the 5th row and dose-dependent rise of double drug-bound RAF dimers in the 6th column. Note that in this case, pan RAF inhibitors have the same potency on the RAF-mutant and wild-type signal again (compare potencies on 1st and 2nd rows).

Thus, the model automatically generated using energy-based rule-based modelling properly describes and simulates the complex drug-protein interactions that are supposed to govern drug efficacy and toxicity in this simplified scenario for RAF kinases and RAF inhibitors.

### Model simulation of temporal dynamics during drug treatment

The previous simulations analyzed the drug-dose response of RAF to RAF inhibitors applied for long-term drug treatment at fixed conditions (e.g. fixed level of RAF dimerization baseline). However, drugs operate on proteins that often experience temporal  perturbations, as for example from the activation of an upstream signal. In this second example, we simulate the behaviour of RAF inhibitors having different energetic properties during temporal dynamic perturbations in RAF dimerization.

First, we generate the dynamic profile for the addtion of a RAF inhibitor and for a subsequent series of square pulses of increased RAF dimerization (through the k forward rate Kf_RR):

```python
#define the train of square pulses that define temporal dynamic control over RAF dimerization (Kf_RR)
ncycle=6;
ton=40;
toff=50;
toset=10;
Kf_RR_min=10**-2;
Kf_RR_max=10**2.5;

#set the concentration and time of addition of the RAFi inhibitor
RAFi_conc_init=0.0;
RAFi_conc_add=10.0;
RAFi_time_add=20;

#generate list of events that dynamically change RAFi
t_events=[0.0, RAFi_time_add];
events=['I_0', 'I_0'];
events_value=[RAFi_conc_init, RAFi_conc_add];

#generate list of events that dynamically change Kf_RR
for i in range(ncycle):
    t_events= t_events + [t_events[-1] + toff + toset] + [t_events[-1] + ton + toff + toset] ;
    events= events + ['kf_RR'] + ['kf_RR'];
    events_value= events_value + [Kf_RR_max] + [Kf_RR_min];
    toset=0;

#generate dynamic signals for RAFi and Kf_RR
t_Kf_RR_dyn=[0.0];
Kf_RR_dyn=[Kf_RR_min];
t_RAFi=[0.0];
RAFi_dyn=[RAFi_conc_init];
for i in range(len(events)):
    if (events[i]=='kf_RR'):
        t_Kf_RR_dyn= t_Kf_RR_dyn + [t_events[i]] + [t_events[i]];
        Kf_RR_dyn= Kf_RR_dyn + [Kf_RR_dyn[-1]] + [events_value[i]];
    elif (events[i]=='I_0'):
        t_RAFi= t_RAFi + [t_events[i]] + [t_events[i]];
        RAFi_dyn= RAFi_dyn + [RAFi_dyn[-1]] + [events_value[i]];
t_RAFi= t_RAFi + [t_Kf_RR_dyn[-1]];
RAFi_dyn = RAFi_dyn + [RAFi_dyn[-1]];

```

Next, we define the energetic properties of two 1st generation RAF inhibitors which have the same kinetic rates but in which the cooperativity with RAF dimerization is assigned either to the forward (RR_phi=1.0) or backward rate (RR_phi=0.0). This will change how long lived are RAF dimers once induced by the dynamic pulses of increase RAF dimerization.

```python
#define energy parameters for the RAF inhibitors to be simulated
ff=[0.001, 0.001];
gg=[1000, 1000];
RI_phi=[1.0, 1.0];
RR_phi=[1.0, 0.0];
lgn_dyn=['RAFi','Kd_RR', 'phi_RI=1', 'phi_RR=0'];

#create figure for plotting
fig, ax = plt.subplots(2+len(plt_obs),1);
#plot dynamic RAFi concentration
ax[0].plot(t_RAFi, RAFi_dyn, color='k');
ax[0].set_ylabel('RAFi (I)');
ax[0].set_xticklabels([]);
#plot dynamic Kd_RR rate
ax[1].plot(t_Kf_RR_dyn, Kf_RR_dyn, color='r');
ax[1].set_ylabel('kf_RR');
ax[1].set_xticklabels([]);

#for each RAF inhibitor to be simulated
for i in range (len(ff)):
    #set up kinetic parameters and initial conditions
    param_values={'f': ff[i] ,'g': gg[i], 'phi_RI':RI_phi[i], 'phi_RR':RR_phi[i], 'kf_RR': Kf_RR_dyn[0], 'I_0': RAFi_dyn[0]};
    #run it to steady state before running drug addition and Kd_RR pulse train
    res=equilibrate(sim, param_values=param_values);
    #run consecutive simulations updating conditions according to events
    #(drug addition, changes in Kd_RR) pulse train to set KdRR values
    res_obs=res.dataframe[plt_obs].to_records(index=False)[-1];
    t=[0.0];

    for j in range(len(events)-1):
        #save the state of the previous simulation to restart at same point
        initials_pre = res.dataframe.iloc[-1, :len(model.species)].copy();
        #create the tspan for simulation from these event to the next
        tspan= np.linspace(t_events[j], t_events[j+1]);
        #update param values with the event value
        param_values[events[j]]=events_value[j];
        #if the drug changed, set it in the current species states
        if (events[j]=="I_0"):
            #get index of inhibitor specie
            i_I_0=model.get_species_index(as_complex_pattern(model.monomers.I(r=None)));
            initials_pre[i_I_0]=events_value[j];
        #run the simulation for the necessary time (until next event)
        res=sim.run(tspan=tspan, param_values=param_values, initials=np.array(initials_pre));
        #append the observables
        res_obs=np.append(res_obs[plt_obs],res.dataframe[plt_obs].to_records(index=False));
        #append simulation time
        t=np.append(t,tspan);

    #plot the results for a given setting of parameters
    for z in range(len(plt_obs)):
             #set same yaxes
             if (z>0):
                ax[z].get_shared_y_axes().join(ax[2], ax[z+2]);
             #plot simualtion
             h=ax[z+2].plot(t, res_obs[plt_obs[z]]);
             #set axis names
             ax[z+2].set_ylabel(plot_obs_names[z]);
             if (z==(len(plt_obs)-1)):
                ax[z+2].set_xlabel('Time (s)');
             else:
                ax[z+2].set_xticklabels([]);
#add legend
fig.legend(ax, labels=lgn_dyn , loc="upper right", borderaxespad=0.1);
```

    <IPython.core.display.Javascript object>

The simulation shows that distribution rates, which define how changes in cooperativity affect forward or backward rates, can greatly influce drug efficacy during dynamic perturations. In this case, two 1st generation RAF inhibitors with different distribution rates, but having the exact same forward and backward rates, behave very differently when a dynamic perturbation in RAF dimerization is applied the system. By changing the distribution rate from RR_phi=0 to RR_phi=1, the cooperativity imposed between drug binding and RAF dimerization shifts from controlling the rate of RAF dissasembly to control the rate of RAF assembly. In the case of RR_phi=0, the drug binding to RAF dimers slows their disassembly after the dimerization signal disappears, those creating a continuous RAF signal that can cause resistance (RAF_mut, second row) or toxicity (RAF_wt). Instead, in the case of RR_ph=1, the drug binding to RAF dimers increases the speed of their assembly when the dimerization signal appears. This causes a slightly faster induction of RAF dimers, but removes the continous activation seen in the previous case. The beahviour of drugs with complex interactions with targeted proteins is thus influenced not just by their forward and backward rates, but also by the way in which cooperativity affects assembly/dissasembly rates of multiprotein-drug complexes.