# Aim of this notebook

* To construct the singular curve of universal type to finalize the solution of the optimal control problem

# Preamble

```python
from sympy import *
init_printing(use_latex='mathjax')

# Plotting
%matplotlib inline
## Make inline plots raster graphics
from IPython.display import set_matplotlib_formats
## Import modules for plotting and data analysis
import matplotlib.pyplot as plt
from matplotlib import gridspec,rc,colors
import matplotlib.ticker as plticker
## Parameters for seaborn plots
import seaborn as sns
sns.set(style='white',font_scale=1.25,
        rc={"xtick.major.size": 6, "ytick.major.size": 6,
            'text.usetex': False, 'font.family': 'serif', 'font.serif': ['Times']})

import pandas as pd
pd.set_option('mode.chained_assignment',None)

import numpy as np
from scipy.optimize import fsolve, root
from scipy.integrate import ode
backend = 'dopri5'
import warnings

# Timer
import time

from copy import deepcopy

from itertools import cycle
palette_size = 10;
clrs = sns.color_palette("Reds",palette_size)
iclrs = cycle(clrs) # iterated colors

# Suppress warnings
import warnings
warnings.filterwarnings("ignore")
```

# Parameter values

* Birth rate and const of downregulation are defined below in order to fit some experim. data

```python
d = .13 # death rate
α = .3 # low equilibrium point at expression of the main pathway (high equilibrium is at one)
θ = .45 # threshold value for the expression of the main pathway
κ = 40 # robustness parameter
```

* Symbolic variables - the list insludes μ & μbar, because they will be varied later

```python
σ, φ0, φ, x, μ, μbar = symbols('sigma, phi0, phi, x, mu, mubar')
```

* Main functions

```python
A = 1-σ*(1-θ)
Eminus = (α*A-θ)**2/2
ΔE = A*(1-α)*((1+α)*A/2-θ)
ΔEf = lambdify(σ,ΔE)
```

* Birth rate and cost of downregulation

```python
b = (0.1*(exp(κ*(ΔEf(1)))+1)-0.14*(exp(κ*ΔEf(0))+1))/(exp(κ*ΔEf(1))-exp(κ*ΔEf(0))) # birth rate
χ = 1-(0.14*(exp(κ*ΔEf(0))+1)-b*exp(κ*ΔEf(0)))/b
b, χ
```

$$\left ( 0.140168330860362, \quad 0.325961223954473\right )$$

```python
c_relative = 0.2
c = c_relative*(b-d)/b+(1-c_relative)*χ/(exp(κ*ΔEf(0))+1) # cost of resistance
c
```

$$0.0154694776420055$$

* Hamiltonian *H* and a part of it ρ that includes the control variable σ

```python
h = b*(χ/(exp(κ*ΔE)+1)*(1-x)+c*x)
H = -φ0 + φ*(b*(χ/(exp(κ*ΔE)+1)-c)*x*(1-x)+μ*(1-x)/(exp(κ*ΔE)+1)-μbar*exp(-κ*Eminus)*x) + h
ρ = (φ*(b*χ*x+μ)+b*χ)/(exp(κ*ΔE)+1)*(1-x)-φ*μbar*exp(-κ*Eminus)*x
H, ρ
```

$$\left ( \phi \left(\frac{\mu \left(- x + 1\right)}{e^{40 \left(- 0.385 \sigma + 0.7\right) \left(- 0.3575 \sigma + 0.2\right)} + 1} - \bar{\mu} x e^{- 20 \left(- 0.165 \sigma - 0.15\right)^{2}} + x \left(-0.00216833086036159 + \frac{0.045689440686899}{e^{40 \left(- 0.385 \sigma + 0.7\right) \left(- 0.3575 \sigma + 0.2\right)} + 1}\right) \left(- x + 1\right)\right) - \phi_{0} + 0.00216833086036159 x + \frac{0.045689440686899 \left(- x + 1\right)}{e^{40 \left(- 0.385 \sigma + 0.7\right) \left(- 0.3575 \sigma + 0.2\right)} + 1}, \quad - \bar{\mu} \phi x e^{- 20 \left(- 0.165 \sigma - 0.15\right)^{2}} + \frac{\left(- x + 1\right) \left(\phi \left(\mu + 0.045689440686899 x\right) + 0.045689440686899\right)}{e^{40 \left(- 0.385 \sigma + 0.7\right) \left(- 0.3575 \sigma + 0.2\right)} + 1}\right )$$

* Same but for no treatment (σ = 0)

```python
h0 = h.subs(σ,0)
H0 = H.subs(σ,0)
ρ0 = ρ.subs(σ,0)
H0, ρ0
```

$$\left ( \phi \left(0.00368423989943599 \mu \left(- x + 1\right) - 0.637628151621773 \bar{\mu} x - 0.002 x \left(- x + 1\right)\right) - \phi_{0} + 0.002 x + 0.000168330860361587, \quad - 0.637628151621773 \bar{\mu} \phi x + 0.00368423989943599 \left(- x + 1\right) \left(\phi \left(\mu + 0.045689440686899 x\right) + 0.045689440686899\right)\right )$$

* Machinery: definition of the Poisson brackets

```python
PoissonBrackets = lambda H1, H2: diff(H1,x)*diff(H2,φ)-diff(H1,φ)*diff(H2,x)
```

* Necessary functions and defining the right hand side of dynamical equations

```python
ρf = lambdify((x,φ,σ,μ,μbar),ρ)
ρ0f = lambdify((x,φ,μ,μbar),ρ0)
dxdτ = lambdify((x,φ,σ,μ,μbar),-diff(H,φ))
dφdτ = lambdify((x,φ,σ,μ,μbar),diff(H,x))
dVdτ = lambdify((x,σ),h)
dρdσ = lambdify((σ,x,φ,μ,μbar),diff(ρ,σ))
dδρdτ = lambdify((x,φ,σ,μ,μbar),-PoissonBrackets(ρ0-ρ,H))
def ode_rhs(t,state,μ,μbar):
    x, φ, V, δρ = state
    σs = [0,1]
    if (dρdσ(1.,x,φ,μ,μbar)<0) and (dρdσ(θ,x,φ,μ,μbar)>0):
        σstar = fsolve(dρdσ,.8,args=(x,φ,μ,μbar,))[0]
    else:
        σstar = 1.;
    if ρf(x,φ,σstar,μ,μbar) < ρ0f(x,φ,μ,μbar):
        sgm = 0
    else:
        sgm = σstar
    return [dxdτ(x,φ,sgm,μ,μbar),dφdτ(x,φ,sgm,μ,μbar),dVdτ(x,sgm),dδρdτ(x,φ,σstar,μ,μbar)]
```

```python
def get_primary_field(name, experiment,μ,μbar):
    solutions = {}
    solver = ode(ode_rhs).set_integrator(backend)
    τ0 = experiment['τ0']
    tms = np.linspace(τ0,experiment['T_end'],1e3+1)
    for x0 in experiment['x0']:
        δρ0 = ρ0.subs(x,x0).subs(φ,0)-ρ.subs(x,x0).subs(φ,0).subs(σ,1.)
        solver.set_initial_value([x0,0,0,δρ0],0.).set_f_params(μ,μbar)
        sol = []; k = 0;
        while (solver.t < experiment['T_end']) and (solver.y[0]<=1.) and (solver.y[0]>=0.):
            solver.integrate(tms[k])
            sol.append([solver.t]+list(solver.y))
            k += 1
        solutions[x0] = {'solution': sol}
    for x0, entry in solutions.items():
        entry['τ'] = [entry['solution'][j][0] for j in range(len(entry['solution']))]
        entry['x'] = [entry['solution'][j][1] for j in range(len(entry['solution']))]
        entry['φ'] = [entry['solution'][j][2] for j in range(len(entry['solution']))]
        entry['V'] = [entry['solution'][j][3] for j in range(len(entry['solution']))]
        entry['δρ'] = [entry['solution'][j][4] for j in range(len(entry['solution']))]
    return solutions
def get_δρ_value(tme,x0,μ,μbar):
    solver = ode(ode_rhs).set_integrator(backend)
    δρ0 = ρ0.subs(x,x0).subs(φ,0)-ρ.subs(x,x0).subs(φ,0).subs(σ,1.)
    solver.set_initial_value([x0,0,0,δρ0],0.).set_f_params(μ,μbar)
    while (solver.t < tme) and (solver.y[0]<=1.) and (solver.y[0]>=0.):
        solver.integrate(tme)
        sol = [solver.t]+list(solver.y)
    return solver.y[3]
def get_δρ_ending(params,μ,μbar):
    tme, x0 = params
    solver = ode(ode_rhs).set_integrator(backend)
    δρ0 = ρ0.subs(x,x0).subs(φ,0)-ρ.subs(x,x0).subs(φ,0).subs(σ,1.)
    solver.set_initial_value([x0,0,0,δρ0],0.).set_f_params(μ,μbar)
    δτ = 1.0e-8; tms = [tme,tme+δτ]
    _k = 0; sol = []
    while (_k<len(tms)):# and (solver.y[0]<=1.) and (solver.y[0]>=0.):
        solver.integrate(tms[_k])
        sol.append(solver.y)
        _k += 1
    #print(sol)
    return(sol[0][3],(sol[1][3]-sol[0][3])/δτ)
def get_state(tme,x0,μ,μbar):
    solver = ode(ode_rhs).set_integrator(backend)
    δρ0 = ρ0.subs(x,x0).subs(φ,0)-ρ.subs(x,x0).subs(φ,0).subs(σ,1.)
    solver.set_initial_value([x0,0,0,δρ0],0.).set_f_params(μ,μbar)
    δτ = 1.0e-8; tms = [tme,tme+δτ]
    _k = 0; sol = []
    while (solver.t < tms[-1]) and (solver.y[0]<=1.) and (solver.y[0]>=0.):
        solver.integrate(tms[_k])
        sol.append(solver.y)
        _k += 1
    return(list(sol[0])+[(sol[1][3]-sol[0][3])/δτ])
```

# Machinery for the universal line

* To find the universal singular curve we need to define two parameters

```python
γ0 = PoissonBrackets(PoissonBrackets(H,H0),H)
γ1 = PoissonBrackets(PoissonBrackets(H0,H),H0)
```

* The dynamics

```python
dxdτSingExpr = -(γ0*diff(H0,φ)+γ1*diff(H,φ))/(γ0+γ1)
dφdτSingExpr = (γ0*diff(H0,x)+γ1*diff(H,x))/(γ0+γ1)
dVdτSingExpr = (γ0*h0+γ1*h)/(γ0+γ1)
σSingExpr = γ1*σ/(γ0+γ1)
```

* Machinery for Python: lambdify the functions above

```python
dxdτSing = lambdify((x,φ,σ,μ,μbar),dxdτSingExpr)
dφdτSing = lambdify((x,φ,σ,μ,μbar),dφdτSingExpr)
dVdτSing = lambdify((x,φ,σ,μ,μbar),dVdτSingExpr)
σSing = lambdify((x,φ,σ,μ,μbar),σSingExpr)
```

```python
def ode_rhs_Sing(t,state,μ,μbar):
    x, φ, V = state
    if (dρdσ(1.,x,φ,μ,μbar)<0) and (dρdσ(θ,x,φ,μ,μbar)>0):
        σstar = fsolve(dρdσ,.8,args=(x,φ,μ,μbar,))[0]
    else:
        σstar = 1.;
    #print([σstar,σSing(x,φ,σstar,μ,μbar)])
    return [dxdτSing(x,φ,σstar,μ,μbar),dφdτSing(x,φ,σstar,μ,μbar),dVdτSing(x,φ,σstar,μ,μbar)]
# def ode_rhs_Sing(t,state,μ,μbar):
#     x, φ, V = state
#     if (dρdσ(1.,x,φ,μ,μbar)<0) and (dρdσ(θ,x,φ,μ,μbar)>0):
#         σstar = fsolve(dρdσ,.8,args=(x,φ,μ,μbar,))[0]
#     else:
#         σstar = 1.;
#     σTrav = fsolve(lambda σ: dxdτ(x,φ,σ,μ,μbar)-dxdτSing(x,φ,σstar,μ,μbar),.6)[0]
#     print([σstar,σTrav])
#     return [dxdτSing(x,φ,σstar,μ,μbar),dφdτSing(x,φ,σstar,μ,μbar),dVdτ(x,σTrav)]
def get_universal_curve(end_point,tmax,Nsteps,μ,μbar):
    tms =  np.linspace(end_point[0],tmax,Nsteps);
    solver = ode(ode_rhs_Sing).set_integrator(backend)
    solver.set_initial_value(end_point[1:4],end_point[0]).set_f_params(μ,μbar)
    _k = 0; sol = []
    while (solver.t < tms[-1]):
        solver.integrate(tms[_k])
        sol.append([solver.t]+list(solver.y))
        _k += 1
    return sol
def get_σ_universal(tme,end_point,μ,μbar):
    δτ = 1.0e-8; tms = [tme,tme+δτ]
    solver = ode(ode_rhs_Sing).set_integrator(backend)
    solver.set_initial_value(end_point[1:4],end_point[0]).set_f_params(μ,μbar)
    _k = 0; sol = []
    while (solver.t < tme+δτ):
        solver.integrate(tms[_k])
        sol.append([solver.t]+list(solver.y))
        _k += 1
    x, φ = sol[0][:2]
    sgm = fsolve(lambda σ: dxdτ(x,φ,σ,μ,μbar)-(sol[1][0]-sol[0][0])/δτ,θ/2)[0]
    return sgm
def get_state_universal(tme,end_point,μ,μbar):
    solver = ode(ode_rhs_Sing).set_integrator(backend)
    solver.set_initial_value(end_point[1:4],end_point[0]).set_f_params(μ,μbar)
    solver.integrate(tme)
    return [solver.t]+list(solver.y)
```

```python
def ode_rhs_with_σstar(t,state,μ,μbar):
    x, φ, V = state
    if (dρdσ(1.,x,φ,μ,μbar)<0) and (dρdσ(θ,x,φ,μ,μbar)>0):
        σ = fsolve(dρdσ,.8,args=(x,φ,μ,μbar,))[0]
    else:
        σ = 1.;
    return [dxdτ(x,φ,σ,μ,μbar),dφdτ(x,φ,σ,μ,μbar),dVdτ(x,σ)]
def ode_rhs_with_given_σ(t,state,σ,μ,μbar):
    x, φ, V = state
    return [dxdτ(x,φ,σ,μ,μbar),dφdτ(x,φ,σ,μ,μbar),dVdτ(x,σ)]
def get_trajectory_with_σstar(starting_point,tmax,Nsteps,μ,μbar):
    tms = np.linspace(starting_point[0],tmax,Nsteps)
    solver = ode(ode_rhs_with_σstar).set_integrator(backend)
    solver.set_initial_value(starting_point[1:],starting_point[0]).set_f_params(μ,μbar)
    sol = []; _k = 0;
    while solver.t < max(tms) and (solver.y[0]<=1.) and (solver.y[0]>=0.):
        solver.integrate(tms[_k])
        sol.append([solver.t]+list(solver.y))
        _k += 1
    return sol
def get_trajectory_with_given_σ(starting_point,tmax,Nsteps,σ,μ,μbar):
    tms = np.linspace(starting_point[0],tmax,100)
    solver = ode(ode_rhs_with_given_σ).set_integrator(backend)
    solver.set_initial_value(starting_point[1:],starting_point[0]).set_f_params(σ,μ,μbar)
    sol = []; _k = 0;
    while solver.t < max(tms) and (solver.y[0]<=1.) and (solver.y[0]>=0.):
        solver.integrate(tms[_k])
        sol.append([solver.t]+list(solver.y))
        _k += 1
    return sol
def get_state_with_σstar(tme,starting_point,μ,μbar):
    solver = ode(ode_rhs_with_σstar).set_integrator(backend)
    solver.set_initial_value(starting_point[1:4],starting_point[0]).set_f_params(μ,μbar)
    solver.integrate(tme)
    return [solver.t]+list(solver.y)
def get_finalizing_point_from_universal_curve(tme,tmx,end_point,μ,μbar):
    unv_point = get_state_universal(tme,end_point,μ,μbar)
    return get_state_with_σstar(tmx,unv_point,μ,μbar)[1]
```

# Field of optimal trajectories as the solution of the Bellman equation

* μ & μbar are varied by *T* and *T*bar ($\mu=1/T$ and $\bar\mu=1/\bar{T}$)

```python
tmx = 180.
end_switching_curve = {'t': 24., 'x': .9/.8}
# for Τ, Τbar in zip([28]*5,[14,21,28,35,60]):
for Τ, Τbar in zip([28],[60]):
    μ = 1./Τ; μbar = 1./Τbar
    print("Parameters: μ = %.5f, μbar = %.5f"%(μ,μbar))
    end_switching_curve['t'], end_switching_curve['x'] = root(get_δρ_ending,(end_switching_curve['t'],end_switching_curve['x']),args=(μ,μbar)).x
    end_point = [end_switching_curve['t']]+get_state(end_switching_curve['t'],end_switching_curve['x'],μ,μbar)
    print("Ending point for the switching line: τ = %.1f days, x = %.1f%%" % (end_point[0], end_point[1]*100))
    print("Checking the solution - should give zero values: ")
    print(get_δρ_ending([end_switching_curve['t'],end_switching_curve['x']],μ,μbar))
    print("* Constructing the primary field")
    experiments = {
        'sol1': { 'T_end': tmx, 'τ0': 0., 'x0': list(np.linspace(0,end_switching_curve['x']-(1e-3),10))+list(np.linspace(end_switching_curve['x']+(1e-6),1.,10)) } }
    primary_field = []
    for name, values in experiments.items():
        primary_field.append(get_primary_field(name,values,μ,μbar))
    print("* Constructing the switching curve")
    switching_curve = []
    x0s = np.linspace(end_switching_curve['x'],1,21); _y = end_switching_curve['t']

    for x0 in x0s:
        tme = fsolve(get_δρ_value,_y,args=(x0,μ,μbar))[0]
        if (tme>0):
            switching_curve = switching_curve+[[tme,get_state(tme,x0,μ,μbar)[0]]]
            _y = tme
    print("* Constructing the universal curve")
    universal_curve = get_universal_curve(end_point,tmx,25,μ,μbar)
    print("* Finding the last characteristic")
    #time0 = time.time()
#     tuniv = fsolve(get_finalizing_point_from_universal_curve,tmx-40.,args=(tmx,end_point,μ,μbar,))[0]
    tuniv = root(get_finalizing_point_from_universal_curve,tmx-40,args=(tmx,end_point,μ,μbar)).x
    print(tuniv)
    #print("The proccess to find the last characteristic took %0.1f minutes" % ((time.time()-time0)/60.))
    univ_point = get_state_universal(tuniv,end_point,μ,μbar)
    print("The last point on the universal line:")
    print(univ_point)
    last_trajectory = get_trajectory_with_σstar(univ_point,tmx,50,μ,μbar)
    print("Final state:")
    final_state = get_state_with_σstar(tmx,univ_point,μ,μbar)
    print(final_state)
    print("Fold-change in tumor size: %.2f"%(exp((b-d)*tmx-final_state[-1])))
    # Plotting
    plt.rcParams['figure.figsize'] = (6.75, 4)
    _k = 0
    for solutions in primary_field:
        for x0, entry in solutions.items():
            plt.plot(entry['τ'], entry['x'], 'k-', linewidth=.9, color=clrs[_k%palette_size])
            _k += 1
    plt.plot([x[0] for x in switching_curve],[x[1] for x in switching_curve],linewidth=2,color="red")
    plt.plot([end_point[0]],[end_point[1]],marker='o',color="red")
    plt.plot([x[0] for x in universal_curve],[x[1] for x in universal_curve],linewidth=2,color="red")
    plt.plot([x[0] for x in last_trajectory],[x[1] for x in last_trajectory],linewidth=.9,color="black")
    plt.xlim([0,tmx]); plt.ylim([0,1]);
    plt.xlabel("time, days"); plt.ylabel("fraction of resistant cells")
    plt.show()
    print()
```

```python
import csv
from numpy.linalg import norm
File = open("../figures/draft/sensitivity_mu-high_cost.csv", 'w')
File.write("T,Tbar,mu,mubar,sw_start_x,sw_end_t,sw_end_x,univ_point_t,univ_point_x,outcome,err_sw_t,err_sw_x\n")
writer = csv.writer(File,lineterminator='\n')

end_switching_curve0 = {'t': 40.36, 'x': .92}
end_switching_curve_prev_t = end_switching_curve0['t']
tuniv = tmx-30.
Ts = np.arange(90,2,-2) #Τbars;
Τbars = np.arange(90,2,-2) #np.arange(120,1,-1) #need to change here if more
for Τ in Ts:
    μ = 1./Τ
    end_switching_curve = deepcopy(end_switching_curve0)
    for Τbar in Τbars:
        μbar = 1./Τbar
        print("* Parameters: T = %.1f, Tbar = %.1f (μ = %.5f, μbar = %.5f)"%(Τ,Τbar,μ,μbar))
        success = False; err = 1.
        while (not success)|(norm(err)>1e-6):
            end_switching_curve = {'t': 2*end_switching_curve['t']-end_switching_curve_prev_t-.001,
                                   'x': end_switching_curve['x']-0.002}
            sol = root(get_δρ_ending,(end_switching_curve['t'],end_switching_curve['x']),args=(μ,μbar))
            end_switching_curve_prev_t = end_switching_curve['t']
            end_switching_curve_prev_x = end_switching_curve['x']
            end_switching_curve['t'], end_switching_curve['x'] = sol.x
            success = sol.success
            err = get_δρ_ending([end_switching_curve['t'],end_switching_curve['x']],μ,μbar)
            if (not success):
                print("! Trying again...", sol.message)
            elif (norm(err)>1e-6):
                print("! Trying again... Convergence is not sufficient")
        else:
            end_point = [end_switching_curve['t']]+get_state(end_switching_curve['t'],end_switching_curve['x'],μ,μbar)
            print("Ending point: t = %.2f, x = %.2f%%"%(end_switching_curve['t'],100*end_switching_curve['x'])," Checking the solution:",err)
            universal_curve = get_universal_curve(end_point,tmx,25,μ,μbar)
            tuniv = root(get_finalizing_point_from_universal_curve,tuniv,args=(tmx,end_point,μ,μbar)).x
            err_tuniv = get_finalizing_point_from_universal_curve(tuniv,tmx,end_point,μ,μbar)
            univ_point = get_state_universal(tuniv,end_point,μ,μbar)
            print("tuniv = %.2f"%tuniv,"xuniv = %.2f%%"%(100*univ_point[1])," Checking the solution: ",err_tuniv)
            final_state = get_state_with_σstar(tmx,univ_point,μ,μbar)
            outcome = exp((b-d)*tmx-final_state[-1])
            print("Fold-change in tumor size: %.2f"%(outcome))
            output = [Τ,Τbar,μ,μbar,end_switching_curve['x'],end_point[0],end_point[1]]+list(univ_point[0:2])+[outcome]+list(err)+[err_tuniv]
            writer.writerow(output)
        if (Τbar==Τ):
            end_switching_curve0 = deepcopy(end_switching_curve)

File.close()
```

    * Parameters: T = 90.0, Tbar = 90.0 (μ = 0.01111, μbar = 0.01111)
    Ending point: t = 44.43, x = 90.20%  Checking the solution: (1.7549465824100814e-12, 2.097211490549016e-12)
    tuniv = 128.68 xuniv = 62.93%  Checking the solution:  8.739856061090734e-12
    Fold-change in tumor size: 0.61
    * Parameters: T = 90.0, Tbar = 88.0 (μ = 0.01111, μbar = 0.01136)
    Ending point: t = 44.30, x = 90.01%  Checking the solution: (3.0265297815926673e-14, 1.7069196824506932e-14)
    tuniv = 128.95 xuniv = 62.59%  Checking the solution:  6.094194593408986e-11
    Fold-change in tumor size: 0.61
    * Parameters: T = 90.0, Tbar = 86.0 (μ = 0.01111, μbar = 0.01163)
    Ending point: t = 44.16, x = 89.81%  Checking the solution: (-2.456427873865504e-08, 1.8281878045292558e-08)
    tuniv = 129.19 xuniv = 62.25%  Checking the solution:  -3.4822700278880347e-12
    Fold-change in tumor size: 0.60
    * Parameters: T = 90.0, Tbar = 84.0 (μ = 0.01111, μbar = 0.01190)
    Ending point: t = 44.03, x = 89.61%  Checking the solution: (-8.532586644040914e-13, -1.0130264978131968e-12)
    tuniv = 129.48 xuniv = 61.88%  Checking the solution:  1.4210008170145727e-11
    Fold-change in tumor size: 0.60
    * Parameters: T = 90.0, Tbar = 82.0 (μ = 0.01111, μbar = 0.01220)
    Ending point: t = 44.27, x = 89.40%  Checking the solution: (1.2091605713177505e-07, 6.550436441604289e-07)
    tuniv = 129.18 xuniv = 61.96%  Checking the solution:  1.9365065107024293e-13
    Fold-change in tumor size: 0.59
    * Parameters: T = 90.0, Tbar = 80.0 (μ = 0.01111, μbar = 0.01250)
    Ending point: t = 43.76, x = 89.18%  Checking the solution: (-2.8600631337302698e-08, -2.3541501337999554e-08)
    tuniv = 130.05 xuniv = 61.11%  Checking the solution:  1.0833070551719004e-11
    Fold-change in tumor size: 0.59
    * Parameters: T = 90.0, Tbar = 78.0 (μ = 0.01111, μbar = 0.01282)
    ! Trying again... The iteration is not making good progress, as measured by the
      improvement from the last ten iterations.
    Ending point: t = 43.63, x = 88.95%  Checking the solution: (-1.3246867333617485e-08, -9.322676360401242e-08)
    tuniv = 130.39 xuniv = 60.68%  Checking the solution:  -4.1609771184170086e-13
    Fold-change in tumor size: 0.58
    * Parameters: T = 90.0, Tbar = 76.0 (μ = 0.01111, μbar = 0.01316)
    Ending point: t = 43.45, x = 88.71%  Checking the solution: (-2.7046598996977646e-08, -1.4461042122148457e-08)
    tuniv = 130.62 xuniv = 60.34%  Checking the solution:  -2.1720708565098334e-11
    Fold-change in tumor size: 0.57
    * Parameters: T = 90.0, Tbar = 74.0 (μ = 0.01111, μbar = 0.01351)
    Ending point: t = 43.29, x = 88.46%  Checking the solution: (-5.351825372232051e-13, -1.7643305052648797e-13)
    tuniv = 130.90 xuniv = 59.94%  Checking the solution:  1.8773455012777163e-12
    Fold-change in tumor size: 0.57
    * Parameters: T = 90.0, Tbar = 72.0 (μ = 0.01111, μbar = 0.01389)
    Ending point: t = 43.15, x = 88.20%  Checking the solution: (-1.8388423760524352e-08, -5.8978566958499256e-08)
    tuniv = 131.24 xuniv = 59.48%  Checking the solution:  -1.093894419490482e-11
    Fold-change in tumor size: 0.56
    * Parameters: T = 90.0, Tbar = 70.0 (μ = 0.01111, μbar = 0.01429)
    Ending point: t = 42.96, x = 87.93%  Checking the solution: (-2.1884132268206407e-08, -4.01277061114172e-09)
    tuniv = 131.51 xuniv = 59.09%  Checking the solution:  1.947143835057119e-11
    Fold-change in tumor size: 0.56
    * Parameters: T = 90.0, Tbar = 68.0 (μ = 0.01111, μbar = 0.01471)
    Ending point: t = 42.79, x = 87.64%  Checking the solution: (-3.5088300328599026e-09, -1.7443087208379797e-09)
    tuniv = 131.82 xuniv = 58.65%  Checking the solution:  -3.4170402618549645e-11
    Fold-change in tumor size: 0.55
    * Parameters: T = 90.0, Tbar = 66.0 (μ = 0.01111, μbar = 0.01515)

```python

```

scripts/.ipynb_checkpoints/B3. (higher cost of resistance) Solution of the control problem including the universal line and Sensitivity analysis [Python]-checkpoint.ipynb


Non Linear Regression Analysis

If the data shows a curvy trend, then linear regression will not produce very accurate results when compared to a non-linear regression because, as the name implies, linear regression presumes that the data is linear.
Let's learn about non linear regressions and apply an example on python. In this notebook, we fit a non-linear model to the datapoints corrensponding to China's GDP from 1960 to 2014.

Importing required libraries

```python
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
```

Though Linear regression is very good to solve many problems, it cannot be used for all datasets. First recall how linear regression, could model a dataset. It models a linear relation between a dependent variable y and independent variable x. It had a simple equation, of degree 1, for example y = $2x$ + 3.

```python
x = np.arange(-5.0, 5.0, 0.1)

##You can adjust the slope and intercept to verify the changes in the graph
y = 2*(x) + 3
y_noise = 2 * np.random.normal(size=x.size)
ydata = y + y_noise
#plt.figure(figsize=(8,6))
plt.plot(x, ydata,  'bo')
plt.plot(x,y, 'r')
plt.ylabel('Dependent Variable')
plt.xlabel('Indepdendent Variable')
plt.show()
```

Non-linear regressions are a relationship between independent variables $x$ and a dependent variable $y$ which result in a non-linear function modeled data. Essentially any relationship that is not linear can be termed as non-linear, and is usually represented by the polynomial of $k$ degrees (maximum power of $x$).

$$ \ y = a x^3 + b x^2 + c x + d \ $$

Non-linear functions can have elements like exponentials, logarithms, fractions, and others. For example: $$ y = \log(x)$$

Or even, more complicated such as :
$$ y = \log(a x^3 + b x^2 + c x + d)$$

Let's take a look at a cubic function's graph.

```python
x = np.arange(-5.0, 5.0, 0.1)

##You can adjust the slope and intercept to verify the changes in the graph
y = 1*(x**3) + 1*(x**2) + 1*x + 3
y_noise = 20 * np.random.normal(size=x.size)
ydata = y + y_noise
plt.plot(x, ydata,  'bo')
plt.plot(x,y, 'r')
plt.ylabel('Dependent Variable')
plt.xlabel('Indepdendent Variable')
plt.show()
```

```python
y_noise
```

    array([ 1.39307931e+01,  1.81904083e+01,  1.65858529e+01,  9.12214011e+00,
           -1.00427185e+01,  8.27389865e+00, -3.17631910e+01, -3.84330189e+01,
            1.66034262e+01,  1.88019852e+01,  1.07895096e+01, -5.28427727e+01,
            2.14193637e+01, -3.76042017e+00, -3.58522580e-01,  3.25167261e+01,
           -1.25646900e+00, -9.20963020e+00, -6.69306400e+00, -3.31881777e+00,
            7.51275971e+00, -2.60122548e+01,  6.36674398e+00,  1.02805921e+01,
           -3.93683780e+01,  2.45362196e+01,  2.22695232e+01, -1.59653603e+01,
            5.43845903e+00, -1.70022436e+00,  6.19312224e+00, -2.10738372e+01,
           -1.07859316e+01,  6.02166494e+00,  1.17167799e+01, -1.50116667e+01,
           -1.80606022e+01,  4.70062396e+00,  4.26592363e+01, -8.59493924e+00,
            1.13968175e+01,  1.83682833e+01,  2.43493894e+00,  2.30911092e+01,
           -7.01627198e+00, -6.08943596e+00, -1.59788715e+01, -1.95123428e+00,
           -1.19095358e+01, -1.36986262e+01, -1.24463185e+00, -1.83487328e+01,
           -1.24072402e+00,  1.71174923e+01, -2.39917030e+01,  3.28529494e+01,
            2.39334665e+01,  1.48184003e+01, -1.50818310e+01,  2.99170658e+00,
            8.31319693e-01, -5.94534917e+00, -2.44989990e+01, -1.30651678e+01,
            1.35270969e+01,  2.32218151e+01,  3.63032012e+01, -1.01702649e+00,
           -1.30209249e+01,  1.37999426e+01, -1.58308183e+01,  1.66210581e+01,
            8.28552062e+00, -1.53940963e+01, -6.85201903e+00,  2.67183750e+01,
           -6.27675789e-01, -2.14617802e-01,  7.09334731e+00, -4.71482078e+00,
            3.63524211e+00,  2.83064522e+01,  7.27651264e+00,  1.16840824e+01,
           -1.11980474e+01, -1.29880109e+01, -2.06645577e+01, -5.19355238e-02,
           -2.54141106e+01, -2.50534199e+00,  3.56278879e+01,  1.90893523e+01,
           -2.52553555e+01,  1.38466705e+01,  9.67085086e+00, -1.72255858e+01,
            1.33366065e+01, -1.01653091e+00,  5.64343881e+01,  1.25838987e+01])

As you can see, this function has $x^3$ and $x^2$ as independent variables. Also, the graphic of this function is not a straight line over the 2D plane. So this is a non-linear function.

Some other types of non-linear functions are:

### Quadratic

$$ Y = X^2 $$

```python
x = np.arange(-5.0, 5.0, 0.1)

##You can adjust the slope and intercept to verify the changes in the graph

y = np.power(x,2)
y_noise = 2 * np.random.normal(size=x.size)
ydata = y + y_noise
plt.plot(x, ydata,  'bo')
plt.plot(x,y, 'r')
plt.ylabel('Dependent Variable')
plt.xlabel('Indepdendent Variable')
plt.show()
```

### Exponential

An exponential function with base c is defined by $$ Y = a + b c^X$$ where b ≠0, c > 0 , c ≠1, and x is any real number. The base, c, is constant and the exponent, x, is a variable.

```python
X = np.arange(-5.0, 5.0, 0.1)

##You can adjust the slope and intercept to verify the changes in the graph

Y= np.exp(X)

plt.plot(X,Y)
plt.ylabel('Dependent Variable')
plt.xlabel('Indepdendent Variable')
plt.show()
```

### Logarithmic

The response $y$ is a results of applying logarithmic map from input $x$'s to output variable $y$. It is one of the simplest form of __log()__: i.e. $$ y = \log(x)$$

Please consider that instead of $x$, we can use $X$, which can be polynomial representation of the $x$'s. In general form it would be written as
\begin{equation}
y = \log(X)
\end{equation}

```python
X = np.arange(-5.0, 5.0, 0.1)

Y = np.log(X)

plt.plot(X,Y)
plt.ylabel('Dependent Variable')
plt.xlabel('Indepdendent Variable')
plt.show()
```

### Sigmoidal/Logistic

$$ Y = a + \frac{b}{1+ c^{(X-d)}}$$

```python
X = np.arange(-5.0, 5.0, 0.1)

Y = 1-4/(1+np.power(3, X-2))

plt.plot(X,Y)
plt.ylabel('Dependent Variable')
plt.xlabel('Indepdendent Variable')
plt.show()
```


```python
from sympy import *
from sympy.abc import *
from sympy.galgebra.ga import *
import numpy as np
from numpy import linalg as LA
from __future__ import print_function
init_printing()
```

## PDE
The acoustic wave equation for the square slowness m and a source q is given in 3D by :

\begin{cases}
 &m \frac{d^2 u(x,t)}{dt^2} - \rho \nabla(\frac{1}{\rho} \text{grad}(u(x,t)) =q  \\
 &u(.,0) = 0 \\
 &\frac{d u(x,t)}{dt}|_{t=0} = 0
\end{cases}

with the zero initial conditons to guaranty unicity of the solution

```python
p=Function('p')
b=Function('b')
m,s,h,r = symbols('m s h r')
m=M(x,y)
q=Q(x,y,t)
d=D(x,y,t)
e=E(x,y)
r=rho(x,y)
```

### Time and space  discretization as a Taylor expansion.

The time discretization is define as a second order ( $ O (dt^2)) $) centered finite difference to get an explicit Euler scheme easy to solve by steping in time.

$ \frac{d^2 u(x,t)}{dt^2} \simeq \frac{u(x,t+dt) - 2 u(x,t) + u(x,t-dt)}{dt^2} + O(dt^2) $

And we define the space discretization also as a Taylor serie, with oder chosen by the user. This can either be a direct expansion of the second derivative bulding the laplacian, or a combination of first oder space derivative. The second option can be a better choice in case you would want to extand the method to more complex wave equations involving first order derivatives in chain only.

$ \frac{d^2 u(x,t)}{dt^2} \simeq \frac{1}{dx^2} \sum_k \alpha_k (u(x+k dx,t)+u(x-k dx,t)) + O(dx^k) $

```python
dtt=as_finite_diff(p(x,y,t).diff(t,t), [t-s,t, t+s])
dt=as_finite_diff(p(x,y,t).diff(t), [t-s, t+s])
# Spacial finite differences can easily be extended to higher order by increasing the list of sampling point in the next expression.
# Be sure to keep this stencil symmetric and everything else in the notebook will follow.
dxx=as_finite_diff(p(x,y,t).diff(x,x), [x-h,x, x+h])
dyy=as_finite_diff(p(x,y,t).diff(y,y), [y-h,y, y+h])
dy=as_finite_diff(p(x,y,t).diff(y), [y-h, y+h])
dx=as_finite_diff(p(x,y,t).diff(x), [x-h, x+h])
dyr=as_finite_diff(b(x,y).diff(y), [y-h, y+h])
dxr=as_finite_diff(b(x,y).diff(x), [x-h, x+h])
dtt,dxx,dyy,dt,dx,dy
```

```python
# In practice, compute rho. grad(rho) upfront (this is a constant) and just compute the following expression, it avoids
# the recomputation of the gradient at every time step. int the following X and Y are the component of rho.grad(rho) in X and Y respectively

gradgrad=X*dx+Y*dy
expand(gradgrad)
```

```python
lap=(dxx+dyy)
expand(lap + gradgrad)
```

### Solve forward in time

The wave equation with absorbing boundary conditions writes

$ \eta \frac{d u(x,t)}{dt} + m \frac{d^2 u(x,t)}{dt^2} - \rho \nabla(\frac{1}{\rho} \text{grad}(u(x,t)) =q  $

and the adjont wave equation

$ -\eta \frac{d u(x,t)}{dt} + m \frac{d^2 u(x,t)}{dt^2} - \rho \nabla(\frac{1}{\rho} \text{grad}(u(x,t)) =q  $

where $ \eta$  is a damping factor equal to zero inside the physical domain and decreasing inside the absorbing layer from the pysical domain to the border

And in order to simplify (kinda, I just like it that way) we will rewrite

$ \rho \nabla(\frac{1}{\rho} \text{grad}(u(x,t)) = \nabla^2 u(x,t) + \rho \text{grad}(\frac{1}{\rho}) . \text{grad}(u(x,t)) $

```python
# Forward wave equation
wave_equation = m*dtt- (dxx+dyy)- r*(dxr*dx+dyr*dy) - q  + e*dt
stencil = solve(wave_equation,p(x,y,t+s))[0]
ts=lambdify((p(x,y,t-s),p(x-h,y,t), p(x,y,t), p(x+h,y,t),p(x,y-h,t), p(x,y+h,t),b(x-h,y), b(x,y), b(x+h,y),b(x,y-h), b(x,y+h), q , m,r, s, h,e),stencil,"numpy")
stencil
```

```python
# Adjoint wave equation
wave_equationA =  m*dtt- (dxx+dyy) - r*(dxr*dx+dyr*dy)  - d  - e*dt
stencilA = solve(wave_equationA,p(x,y,t-s))[0]
tsA=lambdify((p(x,y,t+s),p(x-h,y,t), p(x,y,t), p(x+h,y,t),p(x,y-h,t), p(x,y+h,t),b(x-h,y), b(x,y), b(x+h,y),b(x,y-h), b(x,y+h), d, m,r, s, h,e),stencilA,"numpy")
stencilA
```

# Define the discrete model

```python
import matplotlib.pyplot as plt
from matplotlib import animation

hstep=25 #space increment d  = minv/(10*f0);
tstep=2 #time increment dt < .5 * hstep /maxv;
tmin=0.0 #initial time
tmax=600 #simulate until
xmin=-500.0 - 10*hstep #left bound
xmax=500.0 + 10*hstep #right bound...assume packet never reaches boundary
ymin=-500.0 - 10*hstep #left bound
ymax=500.0 + 10*hstep #right bound...assume packet never reaches boundary
f0=.010
t0=1/.010
nbpml=10
nx = int((xmax-xmin)/hstep) + 1 #number of points on x grid
ny = int((ymax-ymin)/hstep) + 1 #number of points on x grid
nt = int((tmax-tmin)/tstep) + 2 #number of points on t grid
xsrc=-400
ysrc=0.0
xrec = nbpml+4
#set source as Ricker wavelet for f0
def source(x,y,t):
    r = (np.pi*f0*(t-t0))
    val = (1-2.*r**2)*np.exp(-r**2)
    if abs(x-xsrc)<hstep/2 and abs(y-ysrc)<hstep/2:
        return val
    else:
        return 0.0

def dampx(x):
    dampcoeff=1.5*np.log(1.0/0.001)/(5.0*hstep);
    if x<nbpml:
        return dampcoeff*((nbpml-x)/nbpml)**2
    elif x>nx-nbpml-1:
        return dampcoeff*((x-nx+nbpml)/nbpml)**2
    else:
        return 0.0
def dampy(y):
    dampcoeff=1.5*np.log(1.0/0.001)/(5.0*hstep);
    if y<nbpml:
        return dampcoeff*((nbpml-y)/nbpml)**2
    elif y>ny-nbpml-1:
        return dampcoeff*((y-ny+nbpml)/nbpml)**2
    else:
        return 0.0
```

```python
# Velocity and density models
def smooth10(vel,nx,ny):
    out=np.ones((nx,ny))
    out[:,:]=vel[:,:]
    for a in range(5,nx-6):
        out[a,:]=np.sum(vel[a-5:a+5,:], axis=0) /10
    return out

# True velocity
vel=np.ones((nx,ny)) + 2.0
rho=np.ones((nx,ny))
vel[floor(nx/2):nx,:]=4.5
rho[floor(nx/2):nx,:]=2.0
rho=rho**-1
mt=vel**-2
# Smooth velocity
v0=smooth10(vel,nx,ny)
m0=v0**-2

dm=m0-mt
```

# Create functions for the PDE

The Gradient/Born are here so that everything is at the correct place, it is described later

```python
def Forward(nt,nx,ny,m,rho):
    u=np.zeros((nt,nx,ny))
    rec=np.zeros((nt,ny-2))
    for ti in range(0,nt):
        for a in range(1,nx-1):
            for b in range(1,ny-1):
                src = source(xmin+a*hstep,ymin+b*hstep,tstep*ti)
                damp=dampx(a)+dampy(b)
                if ti==0:
                    u[ti,a,b]=ts(0,0,0,0,0,0,rho[a-1,b],rho[a,b],rho[a+1,b],rho[a,b-1],rho[a,b+1],
src,m[a,b],rho[a,b]**-1,tstep,hstep,damp)
                elif ti==1:
                    u[ti,a,b]=ts(0,u[ti-1,a-1,b],u[ti-1,a,b],u[ti-1,a+1,b],u[ti-1,a,b-1],u[ti-1,a,b+1],rho[a-1,b],rho[a,b],rho[a+1,b],rho[a,b-1],rho[a,b+1],
src,m[a,b],rho[a,b]**-1,tstep,hstep,damp)
                else:
                    u[ti,a,b]=ts(u[ti-2,a,b],u[ti-1,a-1,b],u[ti-1,a,b],u[ti-1,a+1,b],u[ti-1,a,b-1],u[ti-1,a,b+1],rho[a-1,b],rho[a,b],rho[a+1,b],rho[a,b-1],rho[a,b+1],
src,m[a,b],rho[a,b]**-1,tstep,hstep,damp)
                if a==xrec :
                    rec[ti,b-1]=u[ti,a,b]
    return rec,u

def Adjoint(nt,nx,ny,m,rho,rec):
    v=np.zeros((nt,nx,ny))
    srca=np.zeros((nt))
    for ti in  range(nt-1, -1, -1):
        for a in range(1,nx-1):
            for b in range(1,ny-1):
                if a==xrec:
                    resid=rec[ti,b-1]
                else:
                    resid=0
                damp=dampx(a)+dampy(b)
                if ti==nt-1:
                    v[ti,a,b]=tsA(0,0,0,0,0,0,rho[a-1,b],rho[a,b],rho[a+1,b],rho[a,b-1],rho[a,b+1],
resid,m[a,b],rho[a,b]**-1,tstep,hstep,damp)
                elif ti==nt-2:
                    v[ti,a,b]=tsA(0,v[ti+1,a-1,b],v[ti+1,a,b],v[ti+1,a+1,b],v[ti+1,a,b-1],v[ti+1,a,b+1],rho[a-1,b],rho[a,b],rho[a+1,b],rho[a,b-1],rho[a,b+1],
resid,m[a,b],rho[a,b]**-1,tstep,hstep,damp)
                else:
                    v[ti,a,b]=tsA(v[ti+2,a,b],v[ti+1,a-1,b],v[ti+1,a,b],v[ti+1,a+1,b],v[ti+1,a,b-1],v[ti+1,a,b+1],rho[a-1,b],rho[a,b],rho[a+1,b],rho[a,b-1],rho[a,b+1],
resid,m[a,b],rho[a,b]**-1,tstep,hstep,damp)
                if abs(xmin+a*hstep-xsrc)<hstep/2 and abs(ymin+b*hstep-ysrc)<hstep/2:
                    srca[ti]=v[ti,a,b]
    return srca,v

def Gradient(nt,nx,ny,m,rho,rec,u):
    v1=np.zeros((nx,ny))
    v2=np.zeros((nx,ny))
    v3=np.zeros((nx,ny))
    grad=np.zeros((nx,ny))
    for ti in range(nt-1,-1,-1):
        for a in range(1,nx-1):
            for b in range(1,ny-1):
                if a==xrec:
                    resid=rec[ti,b-1]
                else:
                    resid=0
                damp=dampx(a)+dampy(b)
                v3[a,b]=tsA(v1[a,b],v2[a-1,b],v2[a,b],v2[a+1,b],v2[a,b-1],v2[a,b+1],rho[a-1,b],rho[a,b],rho[a+1,b],rho[a,b-1],rho[a,b+1],
resid,m[a,b],rho[a,b]**-1,tstep,hstep,damp)
                grad[a,b]=grad[a,b]-(v3[a,b]-2*v2[a,b]+v1[a,b])*(u[ti,a,b])
        v1,v2,v3=v2,v3,v1
    # No update inside the pml, only in the physical domain
    # grad[0:nbpml-1,:]=0
    # grad[nx-nbpml-1:nx-1,:]=0
    # grad[:,0:nbpml-1]=0
    # grad[:,ny-nbpml-1:ny-1]=0
    return tstep**-2*grad

def Born(nt,nx,ny,m,rho,dm):
    u1=np.zeros((nx,ny))
    U1=np.zeros((nx,ny))
    u2=np.zeros((nx,ny))
    U2=np.zeros((nx,ny))
    u3=np.zeros((nx,ny))
    U3=np.zeros((nx,ny))
    rec=np.zeros((nt,ny-2))
    src2=0
    for ti in range(0,nt):
        for a in range(1,nx-1):
            for b in range(1,ny-1):
                damp=dampx(a)+dampy(b)
                src = source(xmin+a*hstep,ymin+b*hstep,tstep*ti)
                u3[a,b]=ts(u1[a,b],u2[a-1,b],u2[a,b],u2[a+1,b],u2[a,b-1],u2[a,b+1],rho[a-1,b],rho[a,b],rho[a+1,b],rho[a,b-1],rho[a,b+1],
src,m[a,b],rho[a,b]**-1,tstep,hstep,damp)
                src2 = -tstep**-2*(u3[a,b]-2*u2[a,b]+u1[a,b])*dm[a,b]
                U3[a,b]=ts(U1[a,b],U2[a-1,b],U2[a,b],U2[a+1,b],U2[a,b-1],U2[a,b+1],rho[a-1,b],rho[a,b],rho[a+1,b],rho[a,b-1],rho[a,b+1],
src2,m[a,b],rho[a,b]**-1,tstep,hstep,damp)
                if a==xrec :
                    rec[ti,b-1]=U3[a,b]
        u1,u2,u3=u2,u3,u1
        U1,U2,U3=U2,U3,U1
    return rec

```

# A Forward propagation example

```python
(rect,ut)=Forward(nt,nx,ny,mt,rho)
```

```python

fig = plt.figure()
plts = []             # get ready to populate this list the Line artists to be plotted
plt.hold("off")
for i in range(nt):
    r = plt.imshow(ut[i,:,:])   # this is how you'd plot a single line...
    plts.append( [r] )
ani = animation.ArtistAnimation(fig, plts, interval=50,  repeat = False)   # run the animation
#plt.show()
```

```python
fig2 = plt.figure()
plt.hold("off")
shotrec = plt.imshow(rect,vmin=-10,vmax=10)   # this is how you'd plot a single line...
#plt.show()
```

# Adjoint test

In ordr to guaranty we have the gradient we need to make sure that the solution of the adjoint wave equation is indeed the true adjoint. Tod os so one should check that

$ <Ax,y> - <x,A^Ty> = 0$

where $A$ is the wave_equation,  $A^T$ is wave_equationA and $x,y$ are any random vectors in the range of each operator. This can however be expensive as this two vector would be of size $N * n_t$. To test our operator we will the relax this test by

$ <P_r A P_s^T x,y> - <x,P_SA^TP_r^Ty> = 0$

where $P_r , P_s^T$ are the source and recevier projection operator mapping the source and receiver locations and times onto the full domain. This allow to have only a random source of size $n_t$ at a random postion.

```python
(rec0,u0)=Forward(nt,nx,ny,m0,rho)
```

```python
(srca,v)=Adjoint(nt,nx,ny,m0,rho,rec0)
```

```python
plts = []             # get ready to populate this list the Line artists to be plotted
plt.hold("off")
for i in range(0,nt):
    r = plt.imshow(v[i,:,:])   # this is how you'd plot a single line...
    plts.append( [r] )
ani = animation.ArtistAnimation(fig, plts, interval=50,  repeat = False)   # run the animation
#plt.show()
```

```python

shotrec = plt.plot(srca)   # this is how you'd plot a single line...
plt.show()
```

```python
# Actual adjoint test
term1=0
for ti in range(0,nt):
    term1=term1+srca[ti]*source(xsrc,ysrc,(ti)*tstep)

term2=LA.norm(rec0)**2

term1,term2,term1-term2,term1/term2

#if abs(term11/term21-1)<1e-9
#    print('Adjoint test passed')
```

# Least square objective Gradient

We will consider here the least square objective, as this is the one in need of an adjoint. The test that will follow are however necessary for any objective and associated gradient in a optimization framework. The objective function can be written

$ min_m \Phi(m) := \frac{1}{2} \| P_r A^{-1}(m) q - d\|_2^2$

And it's gradient becomes

$ \nabla_m \Phi(m) = - (\frac{dA(m)u}{dm})^T v $

where v is the soltuion if the adjoint wave equation. For the simple acoustic case the  gradient can be rewritten as

$ \nabla_m \Phi(m) = - \sum_{t=1}^{nt} \frac{d^2u(t)}{dt^2} v(t) $

```python
# Misfit

F0=.5*LA.norm(rec0-rect)**2
F0
```

```python
Im1=Gradient(nt,nx,ny,m0,rho,rec0-rect,u0)
```

```python

shotrec = plt.imshow(rec0-rect,vmin=-100,vmax=100)   # this is how you'd plot a single line...
#plt.show()

shotrec = plt.imshow(Im1,vmin=-100000,vmax=100000)   # this is how you'd plot a single line...
#plt.show()
```

# Adjoint test for the gradient

The adjoint of the FWI Gradient is the Born modelling operator, implementing a double propagation forward in time with a wavefield scaled by the model perturbation for the second propagation

 $ J dm = - A^{-1}(\frac{d A^{-1}q}{dt^2}) dm $

```python
Im2=Gradient(nt,nx,ny,m0,rho,rec0-rect,u0)
```

```python
du1=Born(nt,nx,ny,m0,rho,dm)
```

```python

term11=np.dot((rec0-rect).reshape(-1),du1.reshape(-1))
term21=np.dot(Im2.reshape(-1),dm.reshape(-1))

term11,term21,term11-term21,term11/term21

#if abs(term11/term21-1)<1e-9
#    print('Adjoint test passed')
```

```python
shotrec = plt.imshow(Im2,vmin=-10000,vmax=10000)   # this is how you'd plot a single line...
#plt.show()
```

# Jacobian test
The last part is to check that the operators are consistent with the problem. There is then two properties to be satisfied

$ U(m + hdm) = U(m) +  \mathcal{O} (h) \\
   U(m + h dm) = U(m) + h J[m]dm + \mathcal{O} (h^2) $

which are the linearization conditions for the objective. This is a bit slow to run here but here is the way to test it.

1 - Genrate data for the true model m
2 - Define a smooth initial model $m_0$ and comput the data $d_0$ for this model
3 - You now have $U(m_0)$
4 - Define $ dm = m-m_0$ and $ h = {1,.1,.01,.001,...}$
5 - For each $h$ compute $U(m_0 + h dm)$ by generating data for $m_0 + h dm$ and compute $(J[m_0 + h dm]^T\delta |d) $
6 - Plot in Loglog the two lines of equation above

```python
H=[1,0.1,0.01,.001,0.0001,0.00001,0.000001]
(D1,u0)=Forward(nt,nx,ny,m0,rho)
dub=Born(nt,nx,ny,m0,rho,dm)
error1=np.zeros((7))
error2=np.zeros((7))
for i in range(0,7):
    mloc=m0+H[i]*dm
    (d,u)=Forward(nt,nx,ny,mloc,rho)
    error1[i]  = LA.norm(d - D1,ord=1)
    error2[i]  = LA.norm(d - D1 - H[i]*dub,ord=1)
```

```python
hh=np.zeros((7))
for i in range(0,7):
    hh[i]=H[i]*H[i]
shotrec = plt.loglog(H,error1,H,H)   # this is how you'd plot a single line...
plt.show()
shotrec = plt.loglog(H,error2,H,hh)   # this is howyou'd plot a single line...
plt.show()
```

# Gradient test
The last part is to check that the operators are consistent with the problem. There is then two properties to be satisfied

$ \Phi(m + hdm) = \Phi(m) +  \mathcal{O} (h) \\
   \Phi(m + h dm) = \Phi(m) + h (J[m]^T\delta |d)dm + \mathcal{O} (h^2) $

which are the linearization conditions for the objective. This is a bit slow to run here but here is the way to test it.

1 - Genrate data for the true model m
2 - Define a smooth initial model $m_0$ and comput the data $d_0$ for this model
3 - You now have $\Phi(m_0)$
4 - Define $ dm = m-m_0$ and $ h = {1,.1,.01,.001,...}$
5 - For each $h$ compute $\Phi(m_0 + h dm)$ by generating data for $m_0 + h dm$ and compute $(J[m_0 + h dm]^T\delta |d) $
6 - Plot in Loglog the two lines of equation above

```python
(DT,uT)=Forward(nt,nx,ny,mt,rho)
(D1,u0)=Forward(nt,nx,ny,m0,rho)
F0=.5*LA.norm(D1-DT)**2

g=Gradient(nt,nx,ny,m0,rho,D1-DT,u0)
G=np.dot(g.reshape(-1),dm.reshape(-1));
error21=np.zeros((7))
error22=np.zeros((7))
for i in range(0,7):
    mloc=m0+H[i]*dm
    (D,u)=Forward(nt,nx,ny,mloc,rho)
    error21[i]  = .5*LA.norm(D-DT)**2 -F0
    error22[i]  = .5*LA.norm(D-DT)**2 -F0 - H[i]*G
```

```python
shotrec = plt.loglog(H,error21,H,H)   # this is how you'd plot a single line...
plt.show()
shotrec = plt.loglog(H,abs(error22),H,hh) # this is how you'd plot a single line...
plt.show()
```

```python
error22
```

```python

```

```python

```


# Example 9.5: Modified Brayton Cycle (Air-Standard)

*John F. Maddox, Ph.D., P.E.

University of Kentucky - Paducah Campus

ME 321: Engineering Thermodynamics II
*

## Problem Statement

A Brayton cycle with intercooling, reheat, and regeneration has the following properties:
\begin{equation}\begin{aligned}
T_1&=300\ \mathrm{K} & T_3&=350\ \mathrm{K} & T_6&=T_8=1400\ \mathrm{K} \\
p_1 &= 1\ \mathrm{bar}  & \frac{p_2}{p_1}&=\frac{p_4}{p_3}=\sqrt{10} & p_7&=\sqrt{10}\ \mathrm{bar}\\
\eta_{c,1} &= \eta_{c,2} = 0.8 & \eta_{t,1}&=\eta_{t,2}=0.8 & \eta_{reg}&= 0.85
\end{aligned}\end{equation}
Determine:
* (a) $w_{net}$
* (b) $q_{in}$
* (c) $\eta_{th}$
* (d) $BWR$

## Solution

__[Video Explanation](https://uky.yuja.com/V/Video?v=3074254&node=10465182&a=1163985616&autoplay=1)__

### Python Initialization
We'll start by importing the libraries we will use for our analysis and initializing dictionaries to hold the properties we will be usings.

```
from numpy import sqrt
from kilojoule.templates.kSI import *
air = idealgas.Properties('Air')
```

### Given Parameters
We now define variables to hold our known values.

```
T[1] = Quantity(300,'K')      # Compressor Inlet Temperature
T[3] = Quantity(350,'K')
T[6] = T[8] = Quantity(1400,'K')
p[1] = Quantity(1,'bar')      # Compressor Inlet pressure
r_p = Quantity(sqrt(10),' ')   # Pressure ratio
p[7] = Quantity(sqrt(10),'bar')
eta_c = Quantity(0.8,'')      # Compressor Isentropioc Efficiency
eta_t = Quantity(0.8,'')      # Turbine Isentropic Efficiency
eta_reg = Quantity(0.85,'')   # Regenerator effectiveness

Summary();
```

\begin{aligned}{ \eta_{c} }&={ 0.8\ }&{ \eta_{reg} }&={ 0.85\ }&{ \eta_{t} }&={ 0.8\ }\\{ r_{p} }&={ 3.1623\ }&{  }\end{aligned}

      T
      p

      unit
      K
      kPa

      1
      300.0
      100.0

      3
      350.0
      -

      6
      1400.0
      -

      7
      -
      316.227766

      8
      1400.0
      -

### Assumptions
- Cold-air-standard Analysis
  - Ideal gas
  - Variable specific heat
- Isobaric heat exchagners: Intercooler, Combustors, Regeneator
- Negligible changes in kinetic energy
- Negligible changes in potential energy

```
# Ideal Gas
R = air.R

# Isobaric heat exchangers
p[2] = r_p*p[1]
p[3] = p[2]
p[4] = r_p*p[3]
p[5] = p[4]
p[6] = p[5]
p[8] = p[7]
p[9] = p[1]
p[10] = p[9]

Summary();
```

\begin{aligned}{ R }&={ 0.28705\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)} }&{ \eta_{c} }&={ 0.8\ }&{ \eta_{reg} }&={ 0.85\ }\\{ \eta_{t} }&={ 0.8\ }&{ r_{p} }&={ 3.1623\ }&{  }\end{aligned}

      T
      p

      unit
      K
      kPa

      1
      300.0
      100.000000

      2
      -
      316.227766

      3
      350.0
      316.227766

      4
      -
      1000.000000

      5
      -
      1000.000000

      6
      1400.0
      1000.000000

      7
      -
      316.227766

      8
      1400.0
      316.227766

      9
      -
      100.000000

      10
      -
      100.000000

#### (a) $w_{net}$

```
%%showcalc
##### 1$\to$2) Non-ideal compression
# Inlet properties
h[1] = air.h(T[1])
s[1] = air.s(T[1],p[1])
# Isentropic exit state: Isentropic Ideal Gas Relations
p['2s'] = p[2]
s['2s'] = s[1]
h['2s'] = air.h(p['2s'],s['2s'])
T['2s'] = air.T(p['2s'],s['2s'])

# Isentropic efficiency
h[2] = h[1] + (h['2s']-h[1])/eta_c
T[2] = air.T(h=h[2])
s[2] = air.s(p[2],h=h[2])

# First Law for compressor
w_1_to_2 = (h[1]-h[2])

##### 2$\to$3) Intercooling
h[3] = air.h(T[3])
s[3] = air.s(T[3],p[3])
q_2_to_3 = (h[3]-h[2])

##### 3$\to$4) Non-ideal compression
# Isentropic exit state
p['4s'] = p[4]
s['4s'] = s[3]
h['4s'] = air.h(p['4s'],s['4s'])
T['4s'] = air.T(p['4s'],s['4s'])

# Isentropic efficiency
h[4] = h[3] + (h['4s']-h[3])/eta_c
T[4] = air.T(h=h[4])
s[4] = air.s(p[4],h=h[4])

# First Law for compressor
w_3_to_4 = (h[3]-h[4])

##### 4$\to$5) Regenerator
# Skip for now.  Need $T_9$ to analyze

##### 5$\to$6) Combustor 1
# Skip for now.  Need $T_5$ from regenerator analysis

##### 6$\to$7) Non-ideal expansion
h[6] = air.h(T[6])
s[6] = air.s(T[6],p[6])

# Isentropic exit state
p['7s'] = p[7]
s['7s'] = s[6]
h['7s'] = air.h(p['7s'],s['7s'])
T['7s'] = air.T(h=h['7s'])

# Isentropic efficiency
h[7] = h[6] - eta_t*(h[6]-h['7s'])
T[7] = air.T(h=h[7])
s[7] = air.s(p[7],h=h[7])

# First law for turbine
w_6_to_7 = (h[6]-h[7])

##### 7$\to$8) Combustor 2
h[8] = air.h(T[8])
s[8] = air.s(T[8],p[8])
q_7_to_8 = h[8]-h[7]

##### 8$\to$9) Non-ideal expansion
# Isentropic exit state
p['9s'] = p[9]
s['9s'] = s[8]
h['9s'] = air.h(p['9s'],s['9s'])
T['9s'] = air.T(h=h['9s'])

# Isentropic efficiency
h[9] = h[8] - eta_t*(h[8]-h['9s'])
T[9] = air.T(h=h[9])
s[9] = air.s(p[9],h=h[9])

# First law for turbine
w_8_to_9 = (h[8]-h[9])

###### $w_{net}$
w_net = w_1_to_2 + w_3_to_4 + w_6_to_7 + w_8_to_9
```

#### 1$\to$2) Non-ideal compression

 Inlet properties

\begin{aligned}
  {h}_{ 1 } &=  \mathrm{ \underset{ air }{ h } }\left( {T}_{ 1 } \right)  = \mathrm{ \underset{ air }{ h } }\left( 300\ \mathrm{K} \right) = -2.4071\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

\begin{aligned}
  {s}_{ 1 } &=  \mathrm{ \underset{ air }{ s } }\left( {T}_{ 1 }, {p}_{ 1 } \right)  = \mathrm{ \underset{ air }{ s } }\left( 300\ \mathrm{K}, 100.0\ \mathrm{kPa} \right) = 6.7077\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)}
\end{aligned}

 Isentropic exit state: Isentropic Ideal Gas Relations

\begin{aligned}
  {p}_{ 2s } &=  {p}_{ 2 }  = 316.23\ \mathrm{kPa}
\end{aligned}

\begin{aligned}
  {s}_{ 2s } &=  {s}_{ 1 }  = 6.7077\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)}
\end{aligned}

\begin{aligned}
  {h}_{ 2s } &=  \mathrm{ \underset{ air }{ h } }\left( {p}_{ 2s }, {s}_{ 2s } \right)  = \mathrm{ \underset{ air }{ h } }\left( 316.23\ \mathrm{kPa}, 6.7077\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)} \right) = 114.93\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

\begin{aligned}
  {T}_{ 2s } &=  \mathrm{ \underset{ air }{ T } }\left( {p}_{ 2s }, {s}_{ 2s } \right)  = \mathrm{ \underset{ air }{ T } }\left( 316.23\ \mathrm{kPa}, 6.7077\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)} \right) = \begin{pmatrix}416.2\end{pmatrix}\ \mathrm{K}
\end{aligned}

 Isentropic efficiency

\begin{aligned}
  {h}_{ 2 } &=  {h}_{ 1 } + \frac{  {h}_{ 2s } - {h}_{ 1 }  }{ \eta_{c} } \\
    &= -2.4071\ \frac{\mathrm{kJ}}{\mathrm{kg}} + \frac{  114.93\ \frac{\mathrm{kJ}}{\mathrm{kg}} - -2.4071\ \frac{\mathrm{kJ}}{\mathrm{kg}}  }{ 0.8\ }\\
    &= 144.26\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

\begin{aligned}
  {T}_{ 2 } &=  \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = {h}_{ 2 } \right)  = \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = 144.26\ \frac{\mathrm{kJ}}{\mathrm{kg}} \right) = \begin{pmatrix}445.0\end{pmatrix}\ \mathrm{K}
\end{aligned}

\begin{aligned}
  {s}_{ 2 } &=  \mathrm{ \underset{ air }{ s } }\left( {p}_{ 2 }, \mathrm{ h } = {h}_{ 2 } \right)  = \mathrm{ \underset{ air }{ s } }\left( 316.23\ \mathrm{kPa}, \mathrm{ h } = 144.26\ \frac{\mathrm{kJ}}{\mathrm{kg}} \right) = 6.7758\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)}
\end{aligned}

 First Law for compressor

\begin{aligned}
  w_{1\to{}2} &=   {h}_{ 1 } - {h}_{ 2 }  \\
    &=  -2.4071\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 144.26\ \frac{\mathrm{kJ}}{\mathrm{kg}} \\
    &= -146.67\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

#### 2$\to$3) Intercooling

\begin{aligned}
  {h}_{ 3 } &=  \mathrm{ \underset{ air }{ h } }\left( {T}_{ 3 } \right)  = \mathrm{ \underset{ air }{ h } }\left( 350\ \mathrm{K} \right) = 47.922\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

\begin{aligned}
  {s}_{ 3 } &=  \mathrm{ \underset{ air }{ s } }\left( {T}_{ 3 }, {p}_{ 3 } \right)  = \mathrm{ \underset{ air }{ s } }\left( 350\ \mathrm{K}, 316.23\ \mathrm{kPa} \right) = 6.5324\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)}
\end{aligned}

\begin{aligned}
  q_{2\to{}3} &=   {h}_{ 3 } - {h}_{ 2 }  \\
    &=  47.922\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 144.26\ \frac{\mathrm{kJ}}{\mathrm{kg}} \\
    &= -96.34\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

#### 3$\to$4) Non-ideal compression

 Isentropic exit state

\begin{aligned}
  {p}_{ 4s } &=  {p}_{ 4 }  = 1000.0\ \mathrm{kPa}
\end{aligned}

\begin{aligned}
  {s}_{ 4s } &=  {s}_{ 3 }  = 6.5324\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)}
\end{aligned}

\begin{aligned}
  {h}_{ 4s } &=  \mathrm{ \underset{ air }{ h } }\left( {p}_{ 4s }, {s}_{ 4s } \right)  = \mathrm{ \underset{ air }{ h } }\left( 1000.0\ \mathrm{kPa}, 6.5324\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)} \right) = 184.68\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

\begin{aligned}
  {T}_{ 4s } &=  \mathrm{ \underset{ air }{ T } }\left( {p}_{ 4s }, {s}_{ 4s } \right)  = \mathrm{ \underset{ air }{ T } }\left( 1000.0\ \mathrm{kPa}, 6.5324\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)} \right) = \begin{pmatrix}484.48\end{pmatrix}\ \mathrm{K}
\end{aligned}

 Isentropic efficiency

\begin{aligned}
  {h}_{ 4 } &=  {h}_{ 3 } + \frac{  {h}_{ 4s } - {h}_{ 3 }  }{ \eta_{c} } \\
    &= 47.922\ \frac{\mathrm{kJ}}{\mathrm{kg}} + \frac{  184.68\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 47.922\ \frac{\mathrm{kJ}}{\mathrm{kg}}  }{ 0.8\ }\\
    &= 218.86\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

\begin{aligned}
  {T}_{ 4 } &=  \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = {h}_{ 4 } \right)  = \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = 218.86\ \frac{\mathrm{kJ}}{\mathrm{kg}} \right) = \begin{pmatrix}517.66\end{pmatrix}\ \mathrm{K}
\end{aligned}

\begin{aligned}
  {s}_{ 4 } &=  \mathrm{ \underset{ air }{ s } }\left( {p}_{ 4 }, \mathrm{ h } = {h}_{ 4 } \right)  = \mathrm{ \underset{ air }{ s } }\left( 1000.0\ \mathrm{kPa}, \mathrm{ h } = 218.86\ \frac{\mathrm{kJ}}{\mathrm{kg}} \right) = 6.6006\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)}
\end{aligned}

 First Law for compressor

\begin{aligned}
  w_{3\to{}4} &=   {h}_{ 3 } - {h}_{ 4 }  \\
    &=  47.922\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 218.86\ \frac{\mathrm{kJ}}{\mathrm{kg}} \\
    &= -170.94\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

#### 4$\to$5) Regenerator

 Skip for now.  Need $T_9$ to analyze

#### 5$\to$6) Combustor 1

 Skip for now.  Need $T_5$ from regenerator analysis

#### 6$\to$7) Non-ideal expansion

\begin{aligned}
  {h}_{ 6 } &=  \mathrm{ \underset{ air }{ h } }\left( {T}_{ 6 } \right)  = \mathrm{ \underset{ air }{ h } }\left( 1400\ \mathrm{K} \right) = 1211.9\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

\begin{aligned}
  {s}_{ 6 } &=  \mathrm{ \underset{ air }{ s } }\left( {T}_{ 6 }, {p}_{ 6 } \right)  = \mathrm{ \underset{ air }{ s } }\left( 1400\ \mathrm{K}, 1000.0\ \mathrm{kPa} \right) = 7.7064\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)}
\end{aligned}

 Isentropic exit state

\begin{aligned}
  {p}_{ 7s } &=  {p}_{ 7 }  = 316.23\ \mathrm{kPa}
\end{aligned}

\begin{aligned}
  {s}_{ 7s } &=  {s}_{ 6 }  = 7.7064\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)}
\end{aligned}

\begin{aligned}
  {h}_{ 7s } &=  \mathrm{ \underset{ air }{ h } }\left( {p}_{ 7s }, {s}_{ 7s } \right)  = \mathrm{ \underset{ air }{ h } }\left( 316.23\ \mathrm{kPa}, 7.7064\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)} \right) = 808.3\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

\begin{aligned}
  {T}_{ 7s } &=  \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = {h}_{ 7s } \right)  = \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = 808.3\ \frac{\mathrm{kJ}}{\mathrm{kg}} \right) = \begin{pmatrix}1056.4\end{pmatrix}\ \mathrm{K}
\end{aligned}

 Isentropic efficiency

\begin{aligned}
  {h}_{ 7 } &=   {h}_{ 6 } -  \eta_{t}  \cdot  \left(  {h}_{ 6 } - {h}_{ 7s }  \right)   \\
    &=  1211.9\ \frac{\mathrm{kJ}}{\mathrm{kg}} -  0.8\  \cdot  \left(  1211.9\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 808.3\ \frac{\mathrm{kJ}}{\mathrm{kg}}  \right)  \\
    &= 889.02\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

\begin{aligned}
  {T}_{ 7 } &=  \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = {h}_{ 7 } \right)  = \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = 889.02\ \frac{\mathrm{kJ}}{\mathrm{kg}} \right) = \begin{pmatrix}1126.3\end{pmatrix}\ \mathrm{K}
\end{aligned}

\begin{aligned}
  {s}_{ 7 } &=  \mathrm{ \underset{ air }{ s } }\left( {p}_{ 7 }, \mathrm{ h } = {h}_{ 7 } \right)  = \mathrm{ \underset{ air }{ s } }\left( 316.23\ \mathrm{kPa}, \mathrm{ h } = 889.02\ \frac{\mathrm{kJ}}{\mathrm{kg}} \right) = 7.7804\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)}
\end{aligned}

 First law for turbine

\begin{aligned}
  w_{6\to{}7} &=   {h}_{ 6 } - {h}_{ 7 }  \\
    &=  1211.9\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 889.02\ \frac{\mathrm{kJ}}{\mathrm{kg}} \\
    &= 322.9\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

#### 7$\to$8) Combustor 2

\begin{aligned}
  {h}_{ 8 } &=  \mathrm{ \underset{ air }{ h } }\left( {T}_{ 8 } \right)  = \mathrm{ \underset{ air }{ h } }\left( 1400\ \mathrm{K} \right) = 1211.9\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

\begin{aligned}
  {s}_{ 8 } &=  \mathrm{ \underset{ air }{ s } }\left( {T}_{ 8 }, {p}_{ 8 } \right)  = \mathrm{ \underset{ air }{ s } }\left( 1400\ \mathrm{K}, 316.23\ \mathrm{kPa} \right) = 8.0368\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)}
\end{aligned}

\begin{aligned}
  q_{7\to{}8} &=   {h}_{ 8 } - {h}_{ 7 }  \\
    &=  1211.9\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 889.02\ \frac{\mathrm{kJ}}{\mathrm{kg}} \\
    &= 322.9\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

#### 8$\to$9) Non-ideal expansion

 Isentropic exit state

\begin{aligned}
  {p}_{ 9s } &=  {p}_{ 9 }  = 100.0\ \mathrm{kPa}
\end{aligned}

\begin{aligned}
  {s}_{ 9s } &=  {s}_{ 8 }  = 8.0368\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)}
\end{aligned}

\begin{aligned}
  {h}_{ 9s } &=  \mathrm{ \underset{ air }{ h } }\left( {p}_{ 9s }, {s}_{ 9s } \right)  = \mathrm{ \underset{ air }{ h } }\left( 100.0\ \mathrm{kPa}, 8.0368\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)} \right) = 808.3\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

\begin{aligned}
  {T}_{ 9s } &=  \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = {h}_{ 9s } \right)  = \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = 808.3\ \frac{\mathrm{kJ}}{\mathrm{kg}} \right) = \begin{pmatrix}1056.4\end{pmatrix}\ \mathrm{K}
\end{aligned}

 Isentropic efficiency

\begin{aligned}
  {h}_{ 9 } &=   {h}_{ 8 } -  \eta_{t}  \cdot  \left(  {h}_{ 8 } - {h}_{ 9s }  \right)   \\
    &=  1211.9\ \frac{\mathrm{kJ}}{\mathrm{kg}} -  0.8\  \cdot  \left(  1211.9\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 808.3\ \frac{\mathrm{kJ}}{\mathrm{kg}}  \right)  \\
    &= 889.02\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

\begin{aligned}
  {T}_{ 9 } &=  \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = {h}_{ 9 } \right)  = \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = 889.02\ \frac{\mathrm{kJ}}{\mathrm{kg}} \right) = \begin{pmatrix}1126.3\end{pmatrix}\ \mathrm{K}
\end{aligned}

\begin{aligned}
  {s}_{ 9 } &=  \mathrm{ \underset{ air }{ s } }\left( {p}_{ 9 }, \mathrm{ h } = {h}_{ 9 } \right)  = \mathrm{ \underset{ air }{ s } }\left( 100.0\ \mathrm{kPa}, \mathrm{ h } = 889.02\ \frac{\mathrm{kJ}}{\mathrm{kg}} \right) = 8.1108\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)}
\end{aligned}

 First law for turbine

\begin{aligned}
  w_{8\to{}9} &=   {h}_{ 8 } - {h}_{ 9 }  \\
    &=  1211.9\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 889.02\ \frac{\mathrm{kJ}}{\mathrm{kg}} \\
    &= 322.9\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

##### $w_{net}$

\begin{aligned}
  w_{net} &=  w_{1\to{}2} + w_{3\to{}4} + w_{6\to{}7} + w_{8\to{}9} \\
    &= -146.67\ \frac{\mathrm{kJ}}{\mathrm{kg}} + -170.94\ \frac{\mathrm{kJ}}{\mathrm{kg}} + 322.9\ \frac{\mathrm{kJ}}{\mathrm{kg}} + 322.9\ \frac{\mathrm{kJ}}{\mathrm{kg}}\\
    &= 328.19\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

```
states.display();
```

      T
      p
      h
      s

      unit
      K
      kPa
      kJ/kg
      kJ/K/kg

      1
      300.0
      100.000000
      -2.407134
      6.7077

      2
      444.999142
      316.227766
      144.261761
      6.775848

      2s
      416.195586
      316.227766
      114.927982
      6.7077

      3
      350.0
      316.227766
      47.921889
      6.532376

      4
      517.66372
      1000.000000
      218.864401
      6.60063

      4s
      484.475173
      1000.000000
      184.675898
      6.532376

      5
      -
      1000.000000
      -
      -

      6
      1400.0
      1000.000000
      1211.923096
      7.706368

      7
      1126.290809
      316.227766
      889.023152
      7.780355

      7s
      1056.418838
      316.227766
      808.298166
      7.706368

      8
      1400.0
      316.227766
      1211.923096
      8.03685

      9
      1126.290809
      100.000000
      889.023152
      8.110837

      9s
      1056.418838
      100.000000
      808.298166
      8.03685

      10
      -
      100.000000
      -
      -

#### (b) Heat input

```
%%showcalc
##### 4$\to$5 and 9$\to$10) Regenerator
h[5] = h[4] + eta_reg*(h[9]-h[4])
h[10] = h[9] - (h[5]-h[4])
T[5] = air.T(h=h[5])
s[5] = air.s(p[5],h=h[5])
T[10] = air.T(h=h[10])
s[10] = air.s(p[10],h=h[10])

##### 5$\to$6) Combustor 1
q_5_to_6 =h[6]-h[5]

##### 7$\to$8) Combustor 2
q_7_to_8 = h[8]-h[7]

##### Heat input
q_in = q_5_to_6 + q_7_to_8
```

#### 4$\to$5 and 9$\to$10) Regenerator

\begin{aligned}
  {h}_{ 5 } &=  {h}_{ 4 } +  \eta_{reg}  \cdot  \left(  {h}_{ 9 } - {h}_{ 4 }  \right)  \\
    &= 218.86\ \frac{\mathrm{kJ}}{\mathrm{kg}} +  0.85\  \cdot  \left(  889.02\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 218.86\ \frac{\mathrm{kJ}}{\mathrm{kg}}  \right) \\
    &= 788.5\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

\begin{aligned}
  {h}_{ 10 } &=   {h}_{ 9 } -  \left(  {h}_{ 5 } - {h}_{ 4 }  \right)  \\
    &=  889.02\ \frac{\mathrm{kJ}}{\mathrm{kg}} - \left(  788.5\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 218.86\ \frac{\mathrm{kJ}}{\mathrm{kg}}  \right) \\
    &= 319.39\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

\begin{aligned}
  {T}_{ 5 } &=  \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = {h}_{ 5 } \right)  = \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = 788.5\ \frac{\mathrm{kJ}}{\mathrm{kg}} \right) = \begin{pmatrix}1039.2\end{pmatrix}\ \mathrm{K}
\end{aligned}

\begin{aligned}
  {s}_{ 5 } &=  \mathrm{ \underset{ air }{ s } }\left( {p}_{ 5 }, \mathrm{ h } = {h}_{ 5 } \right)  = \mathrm{ \underset{ air }{ s } }\left( 1000.0\ \mathrm{kPa}, \mathrm{ h } = 788.5\ \frac{\mathrm{kJ}}{\mathrm{kg}} \right) = 7.357\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)}
\end{aligned}

\begin{aligned}
  {T}_{ 10 } &=  \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = {h}_{ 10 } \right)  = \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = 319.39\ \frac{\mathrm{kJ}}{\mathrm{kg}} \right) = \begin{pmatrix}614.03\end{pmatrix}\ \mathrm{K}
\end{aligned}

\begin{aligned}
  {s}_{ 10 } &=  \mathrm{ \underset{ air }{ s } }\left( {p}_{ 10 }, \mathrm{ h } = {h}_{ 10 } \right)  = \mathrm{ \underset{ air }{ s } }\left( 100.0\ \mathrm{kPa}, \mathrm{ h } = 319.39\ \frac{\mathrm{kJ}}{\mathrm{kg}} \right) = 7.4396\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)}
\end{aligned}

#### 5$\to$6) Combustor 1

\begin{aligned}
  q_{5\to{}6} &=   {h}_{ 6 } - {h}_{ 5 }  \\
    &=  1211.9\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 788.5\ \frac{\mathrm{kJ}}{\mathrm{kg}} \\
    &= 423.42\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

#### 7$\to$8) Combustor 2

\begin{aligned}
  q_{7\to{}8} &=   {h}_{ 8 } - {h}_{ 7 }  \\
    &=  1211.9\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 889.02\ \frac{\mathrm{kJ}}{\mathrm{kg}} \\
    &= 322.9\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

#### Heat input

\begin{aligned}
  q_{in} &=  q_{5\to{}6} + q_{7\to{}8} \\
    &= 423.42\ \frac{\mathrm{kJ}}{\mathrm{kg}} + 322.9\ \frac{\mathrm{kJ}}{\mathrm{kg}}\\
    &= 746.32\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

#### (c) Thermal efficiency

```
%%showcalc
##### Thermal efficiency
eta_th = w_net/q_in

##### Second Law efficiency
# The low temperature is assumed to be the intake temperature
T_L = T[1]
# The high temperature is approximated as the hottest temperature of the working fluid
T_H = T[6]
# Carnot efficiency
eta_th_Carnot = 1-T_L/T_H

# Second Law efficiency
eta_II = eta_th/eta_th_Carnot
```

#### Thermal efficiency

\begin{aligned}
  \eta_{th} &=  \frac{ w_{net} }{ q_{in} } \\
    &= \frac{ 328.19\ \frac{\mathrm{kJ}}{\mathrm{kg}} }{ 746.32\ \frac{\mathrm{kJ}}{\mathrm{kg}} }\\
    &= 0.43974\
\end{aligned}

#### Second Law efficiency

 The low temperature is assumed to be the intake temperature

\begin{aligned}
  T_{L} &=  {T}_{ 1 }  = 300\ \mathrm{K}
\end{aligned}

 The high temperature is approximated as the hottest temperature of the working fluid

\begin{aligned}
  T_{H} &=  {T}_{ 6 }  = 1400\ \mathrm{K}
\end{aligned}

 Carnot efficiency

\begin{aligned}
  \eta_{th,Carnot} &=   1 - \frac{ T_{L} }{ T_{H} }  \\
    &=  1 - \frac{ 300\ \mathrm{K} }{ 1400\ \mathrm{K} } \\
    &= 0.78571\
\end{aligned}

 Second Law efficiency

\begin{aligned}
  \eta_{II} &=  \frac{ \eta_{th} }{ \eta_{th,Carnot} } \\
    &= \frac{ 0.43974\ }{ 0.78571\ }\\
    &= 0.55967\
\end{aligned}

#### (d) Back-Work-Ratio

```
%%showcalc
##### work in
w_in = -(w_1_to_2 + w_3_to_4)

##### work out
w_out = w_6_to_7 + w_8_to_9

##### back work ratio
BWR = w_in/w_out
```

#### work in

\begin{aligned}
  w_{in} &=  -w_{1\to{}2} + w_{3\to{}4} \\
    &= -\left( -146.67\ \frac{\mathrm{kJ}}{\mathrm{kg}} + -170.94\ \frac{\mathrm{kJ}}{\mathrm{kg}} \right)\\
    &= 317.61\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

#### work out

\begin{aligned}
  w_{out} &=  w_{6\to{}7} + w_{8\to{}9} \\
    &= 322.9\ \frac{\mathrm{kJ}}{\mathrm{kg}} + 322.9\ \frac{\mathrm{kJ}}{\mathrm{kg}}\\
    &= 645.8\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

#### back work ratio

\begin{aligned}
  BWR &=  \frac{ w_{in} }{ w_{out} } \\
    &= \frac{ 317.61\ \frac{\mathrm{kJ}}{\mathrm{kg}} }{ 645.8\ \frac{\mathrm{kJ}}{\mathrm{kg}} }\\
    &= 0.49181\
\end{aligned}

#### Summary of Results

```
Summary()
Summary(['w_net','q_in','eta_th','BWR']);
```

\begin{aligned}{ BWR }&={ 0.49181\ }&{ R }&={ 0.28705\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)} }&{ T_{H} }&={ 1400\ \mathrm{K} }\\{ T_{L} }&={ 300\ \mathrm{K} }&{ \eta_{II} }&={ 0.55967\ }&{ \eta_{c} }&={ 0.8\ }\\{ \eta_{reg} }&={ 0.85\ }&{ \eta_{t} }&={ 0.8\ }&{ \eta_{th} }&={ 0.43974\ }\\{ \eta_{th,Carnot} }&={ 0.78571\ }&{ q_{2\to{}3} }&={ -96.34\ \frac{\mathrm{kJ}}{\mathrm{kg}} }&{ q_{5\to{}6} }&={ 423.42\ \frac{\mathrm{kJ}}{\mathrm{kg}} }\\{ q_{7\to{}8} }&={ 322.9\ \frac{\mathrm{kJ}}{\mathrm{kg}} }&{ q_{in} }&={ 746.32\ \frac{\mathrm{kJ}}{\mathrm{kg}} }&{ r_{p} }&={ 3.1623\ }\\{ w_{1\to{}2} }&={ -146.67\ \frac{\mathrm{kJ}}{\mathrm{kg}} }&{ w_{3\to{}4} }&={ -170.94\ \frac{\mathrm{kJ}}{\mathrm{kg}} }&{ w_{6\to{}7} }&={ 322.9\ \frac{\mathrm{kJ}}{\mathrm{kg}} }\\{ w_{8\to{}9} }&={ 322.9\ \frac{\mathrm{kJ}}{\mathrm{kg}} }&{ w_{in} }&={ 317.61\ \frac{\mathrm{kJ}}{\mathrm{kg}} }&{ w_{net} }&={ 328.19\ \frac{\mathrm{kJ}}{\mathrm{kg}} }\\{ w_{out} }&={ 645.8\ \frac{\mathrm{kJ}}{\mathrm{kg}} }&{  }\end{aligned}

      T
      p
      h
      s

      unit
      K
      kPa
      kJ/kg
      kJ/K/kg

      1
      300.000000
      100.000000
      -2.407134
      6.707700

      2
      444.999142
      316.227766
      144.261761
      6.775848

      2s
      416.195586
      316.227766
      114.927982
      6.707700

      3
      350.000000
      316.227766
      47.921889
      6.532376

      4
      517.663720
      1000.000000
      218.864401
      6.600630

      4s
      484.475173
      1000.000000
      184.675898
      6.532376

      5
      1039.179799
      1000.000000
      788.499339
      7.356988

      6
      1400.000000
      1000.000000
      1211.923096
      7.706368

      7
      1126.290809
      316.227766
      889.023152
      7.780355

      7s
      1056.418838
      316.227766
      808.298166
      7.706368

      8
      1400.000000
      316.227766
      1211.923096
      8.036850

      9
      1126.290809
      100.000000
      889.023152
      8.110837

      9s
      1056.418838
      100.000000
      808.298166
      8.036850

      10
      614.028615
      100.000000
      319.388213
      7.439626

\begin{aligned}{ w_{net} }&={ 328.19\ \frac{\mathrm{kJ}}{\mathrm{kg}} }\\{ q_{in} }&={ 746.32\ \frac{\mathrm{kJ}}{\mathrm{kg}} }\\{ \eta_{th} }&={ 0.43974\ }\\{ BWR }&={ 0.49181\ }\\{  }\end{aligned}

### Plotting
*Note: The plotting library uses the property tables to draw the process paths, which inherently assumes variable specific heat (i.e. real-fluid or air-standard assumptions).  If the library is used to draw process paths between states that were obtained using constant specific heat (cold-air-standard assumptions) There will be inconsistencies between the state points and the process paths.*

In order to plot the paths of the cycle on the $p$-$v$ diagram and states on the $T$-$s$ diagram, we need discrete values for the specifc enthalpy and entropy at each state rather than just the changes in properties we calculated above.  To do this we can pick an arbritrary value for the enthalpy and entropy at any state, then caclulate the enthalpy and entropy at the rest of the states relative to the reference point. For this case, we will look up the properties from the tables for state 1 and use that as our starting point.

```
for i in [1,'2s',2,3,'4s',4,5,6,'7s',7,8,'9s',9,10]:
    v[i] = R*T[i]/p[i]

states.display()
```

      T
      p
      v
      h
      s

      unit
      K
      kPa
      m³/kg
      kJ/kg
      kJ/K/kg

      1
      300.000000
      100.000000
      0.861158
      -2.407134
      6.707700

      2
      444.999142
      316.227766
      0.403944
      144.261761
      6.775848

      2s
      416.195586
      316.227766
      0.377797
      114.927982
      6.707700

      3
      350.000000
      316.227766
      0.317709
      47.921889
      6.532376

      4
      517.663720
      1000.000000
      0.148597
      218.864401
      6.600630

      4s
      484.475173
      1000.000000
      0.139070
      184.675898
      6.532376

      5
      1039.179799
      1000.000000
      0.298299
      788.499339
      7.356988

      6
      1400.000000
      1000.000000
      0.401874
      1211.923096
      7.706368

      7
      1126.290809
      316.227766
      1.022379
      889.023152
      7.780355

      7s
      1056.418838
      316.227766
      0.958954
      808.298166
      7.706368

      8
      1400.000000
      316.227766
      1.270836
      1211.923096
      8.036850

      9
      1126.290809
      100.000000
      3.233048
      889.023152
      8.110837

      9s
      1056.418838
      100.000000
      3.032478
      808.298166
      8.036850

      10
      614.028615
      100.000000
      1.762585
      319.388213
      7.439626

#### (a) $p$-$v$

```
pv = air.pv_diagram()

# plot each state on the p,v diagram
for i in [1,2,3,4,5,6,7,8,9,10]:
    pv.plot_state(states[i],label_loc='north east')

# plot the process paths
pv.plot_process(states[1],states['2s'],path='isentropic',linestyle='dashed',arrow=False)
pv.plot_process(states[1],states[2],path='isentropic efficiency')
pv.plot_process(states[2],states[3],path='isobaric',arrow=False)
pv.plot_process(states[3],states['4s'],path='isentropic',linestyle='dashed',arrow=False)
pv.plot_process(states[3],states[4],path='nonideal')
pv.plot_process(states[4],states[5],path='isobaric')
pv.plot_process(states[5],states[6],path='isobaric')
pv.plot_process(states[6],states['7s'],path='isentropic',linestyle='dashed',arrow=False)
pv.plot_process(states[7],states['7s'],path='isobaric',linestyle='dashed',arrow=False)
pv.plot_process(states[6],states[7],path='nonideal')
pv.plot_process(states[7],states[8],path='isobaric')
pv.plot_process(states[8],states['9s'],path='isentropic',linestyle='dashed',arrow=False)
pv.plot_process(states[8],states[9],path='nonideal')
pv.plot_process(states[9],states[10],path='isobaric')
pv.plot_process(states[10],states[1],path='isobaric',linestyle='dashed');
```

#### (b) $T$-$s$ diagram

```
Ts = air.Ts_diagram()

# plot each state on the p,v diagram
for i in [1,3]:
    Ts.plot_state(states[i],label_loc='west')
Ts.plot_state(states[4])
for i in [6,8,9]:
    Ts.plot_state(states[i],label_loc='east')
for i in [2,5,7,10]:
    Ts.plot_state(states[i],label_loc='south east')

# plot the process paths
Ts.plot_process(states[1],states['2s'],path='isentropic',linestyle='dashed',arrow=False)
Ts.plot_process(states[1],states[2],path='nonideal')
Ts.plot_process(states['2s'],states[2],path='isobaric',linestyle='dashed',arrow=False)
Ts.plot_process(states[2],states[3],path='isobaric',label='intercooler',labelprops=dict(ha='left'))
Ts.plot_process(states[3],states['4s'],path='isentropic',linestyle='dashed',arrow=False)
Ts.plot_process(states[3],states[4],path='nonideal')
Ts.plot_process(states['4s'],states[4],path='isobaric',linestyle='dashed',arrow=False)
Ts.plot_process(states[4],states[5],path='isobaric',label='regenerator')
Ts.plot_process(states[5],states[6],path='isobaric',label='combustor')
Ts.plot_process(states[6],states['7s'],path='isentropic',linestyle='dashed',arrow=False)
Ts.plot_process(states[6],states[7],path='nonideal')
Ts.plot_process(states['7s'],states[7],path='isobaric',linestyle='dashed',arrow=False)
Ts.plot_process(states[7],states[8],path='isobaric',label='reheat')
Ts.plot_process(states[8],states['9s'],path='isentropic',linestyle='dashed',arrow=False)
Ts.plot_process(states[8],states[9],path='nonideal');
Ts.plot_process(states['9s'],states[9],path='isobaric',linestyle='dashed',arrow=False)
Ts.plot_process(states[9],states[10],path='isobaric',label='regenerator');
Ts.plot_process(states[10],states[1],path='isobaric',label='exhaust and intake',linestyle='dashed');
```

examples/ME321-Thermodynamics_II/02 - Chapter 9 Gas Power Cycles/Ex9.5 Modified Brayton Cycle (Air-Standard).ipynb


# Kalman filter PIT sensitivity

# Purpose
Check the PIT for various Kalman filters

# Methodology
* Filter a model test differently...
* PIT on each
* compare result

# Setup

```python
# %load imports.py
## Local packages:

%matplotlib inline
%load_ext autoreload
%autoreload 2
%config Completer.use_jedi = False  ## (To fix autocomplete)

## External packages:
import pandas as pd
pd.options.display.max_rows = 999
pd.options.display.max_columns = 999
pd.set_option("display.max_columns", None)

import numpy as np
import os
import matplotlib.pyplot as plt
#if os.name == 'nt':
#    plt.style.use('presentation.mplstyle')  # Windows

import plotly.express as px
import plotly.graph_objects as go

import seaborn as sns
import sympy as sp
from sympy.physics.mechanics import (dynamicsymbols, ReferenceFrame,
                                      Particle, Point)
from sympy.physics.vector.printing import vpprint, vlatex
from IPython.display import display, Math, Latex
from src.substitute_dynamic_symbols import run, lambdify

import pyro

import sklearn
import pykalman
from statsmodels.sandbox.regression.predstd import wls_prediction_std
import statsmodels.api as sm

from scipy.integrate import solve_ivp

## Local packages:
from src.data import mdl

from src.symbols import *
from src.parameters import *
import src.symbols as symbols
from src import prime_system
from src.models import regression
from src.visualization.plot import track_plot
from src.equation import Equation

## Load models:
# (Uncomment these for faster loading):
import src.models.vmm_abkowitz  as vmm_abkowitz
```

```python
from src.data import kalman_filter
from src.data.lowpass_filter import lowpass_filter
import scipy.stats as stats
```

```python
id=22774
df, units, meta_data = mdl.load(id=id)
```

```python
fig,ax=plt.subplots()

psi = df['psi']
t = df.index.total_seconds()
psi1d = np.gradient(psi,t)

psi_ = df['psi'].resample('0.2S').mean()
t_ = psi_.index.total_seconds()
psi1d_ = np.gradient(psi_,t_)
ax.plot(t, psi1d)
ax.plot(t_, psi1d_)

```

```python
df_filtered = pd.DataFrame()

for observation_covariance in [10,1000,10000,100000, 1000000]:

    df_ = kalman_filter.filter_and_transform(df=df, observation_covariance=observation_covariance)
    #df_ = kalman_filter.yaw(df=df, observation_covariance=observation_covariance)

    df_['observation_covariance'] = observation_covariance
    df_['t'] = df_.index
    df_filtered = df_filtered.append(df_, ignore_index=True)

```

```python
df_epsilons = pd.DataFrame()

for observation_covariance, group in df_filtered.groupby(by='observation_covariance'):

    df_epsilon_ = pd.DataFrame()

    for key in ['x0','y0','psi']:
        df_epsilon_[f'{key}'] = group[f'{key}_filtered'].values - df[key]

    df_epsilon_['t'] = df.index.total_seconds()
    df_epsilon_['observation_covariance'] = observation_covariance

    df_epsilons = df_epsilons.append(df_epsilon_, ignore_index=True)

```

```python
#
#for key in ['x0','y0','psi']:
#    fig,ax=plt.subplots()
#    fig.set_size_inches(15,5)
#    for observation_covariance, df_epsilon in df_epsilons.groupby(by='observation_covariance'):
#        df_epsilon.plot(x='t', y=key, alpha=0.1, label=observation_covariance, ax=ax)
#
#for key in ['x0','y0','psi']:
#    fig,axes=plt.subplots(ncols=len(df_epsilons['observation_covariance'].unique()))
#    for ax,(observation_covariance, df_epsilon) in zip(axes,df_epsilons.groupby(by='observation_covariance')):
#        sns.displot(df_epsilon, x=key)
#
#        fig,ax2=plt.subplots()
#        stats.probplot(df_epsilon[key], dist="norm", plot=ax2);
```

```python
df_ = df_filtered.groupby(by='observation_covariance').get_group(10).copy()
df_.index = df_['t']
df_['t'] = df_.index.total_seconds()
```

```python

psi_ = df_['psi'].resample('0.50S').mean()
t_ = psi_.index.total_seconds()
psi1d_ = np.gradient(psi_,t_)

fig,ax=plt.subplots()
fig.set_size_inches(15,10)

df_.plot(x='t', y='psi1d_gradient', ax=ax)
df_.plot(x='t', y='psi1d', ax=ax)

ax.plot(t_, psi1d_, label='resample')
ax.legend()
```

```python

psi_ = df_['x0'].resample('1.0S').mean()
t_ = psi_.index.total_seconds()
psi1d_ = np.gradient(psi_,t_)

fig,ax=plt.subplots()
fig.set_size_inches(15,10)

df_.plot(x='t', y='x01d_gradient', ax=ax)
df_.plot(x='t', y='x01d', ax=ax)

ax.plot(t_, psi1d_, label='resample')
ax.legend()
```

```python
psi_ = df_['y0'].resample('0.50S').mean()
t_ = psi_.index.total_seconds()
psi1d_ = np.gradient(psi_,t_)

fig,ax=plt.subplots()
fig.set_size_inches(15,10)

#df_.plot(x='t', y='y01d_gradient', ax=ax)
df_.plot(x='t', y='y01d', ax=ax)

ax.plot(t_, psi1d_, label='resample')
ax.legend()
```

```python
df_ = df_filtered.groupby(by='observation_covariance').get_group(1000).copy()
df_.set_index('t', inplace=True)
df_['t']=df_.index.total_seconds()
ts = df_['t'].diff().mean()
fs = 1/ts
```

```python
fs
```

```python

psi_filt = lowpass_filter(data=df_['psi'], cutoff=10, fs=fs, order=5)

fig,ax=plt.subplots()
df_.plot(x='t',y='psi', ax=ax,  label='kalman')
ax.plot(df_['t'], psi_filt, label='lowpass')
ax.legend()

r_filt = np.gradient(psi_filt,df_['t'])
r_filt = lowpass_filter(data=r_filt, cutoff=1, fs=fs, order=5)

r1d_filt = np.gradient(r_filt,df_['t'])
r1d_filt = lowpass_filter(data=r1d_filt, cutoff=1, fs=fs, order=5)

fig,ax=plt.subplots()
df_.plot(x='t',y='r', ax=ax, label='kalman')
ax.plot(df_['t'], r_filt, label='lowpass')
ax.legend()

fig,ax=plt.subplots()
df_.plot(x='t',y='r1d', ax=ax, label='kalman')
ax.plot(df_['t'], r1d_filt, alpha=0.5, label='lowpass')
ax.legend()

```

```python
np.gradient(df_gradient['x0'], df_gradient.index)
```

```python
df_gradient['x0']
```

```python
def lowpass_gradient(df_,cutoff=1, order=5):
    df_gradient = df_[['x0','y0','psi']].copy()
    df_gradient.index=df_['t']
    df_gradient['x01d'] = np.gradient(df_gradient['x0'], df_gradient.index)
    df_gradient['y01d'] = np.gradient(df_gradient['y0'], df_gradient.index)
    df_gradient['r'] = np.gradient(df_gradient['psi'], df_gradient.index)

    df_gradient['x02d'] = np.gradient(df_gradient['x01d'], df_gradient.index)
    df_gradient['y02d'] = np.gradient(df_gradient['y01d'], df_gradient.index)
    df_gradient['r1d'] = np.gradient(df_gradient['r'], df_gradient.index)

    df_lowpass = pd.DataFrame(columns=df_gradient.columns, index=df_gradient.index)
    for key,value in df_gradient.items():
        df_lowpass[key] = lowpass_filter(data=value, cutoff=cutoff, fs=fs, order=order)

    return df_lowpass
```

```python
def df_filter(df_gradient, cutoff=1, order=5):

    df_lowpass = pd.DataFrame(columns=df_gradient.columns, index=df_gradient.index)
    for key,value in df_gradient.items():
        df_lowpass[key] = lowpass_filter(data=value, cutoff=cutoff, fs=fs, order=order)

    return df_lowpass

def lowpass(df_,cutoff=1, order=5):

    df_gradient = df_[['x0','y0','psi']].copy()
    df_gradient.index=df_['t']

    df_gradient[['x0','y0','psi']] = df_filter(df_gradient[['x0','y0','psi']], cutoff=cutoff, order=order)
    df_gradient['x01d'] = np.gradient(df_gradient['x0'], df_gradient.index)
    df_gradient['y01d'] = np.gradient(df_gradient['y0'], df_gradient.index)
    df_gradient['r'] = np.gradient(df_gradient['psi'], df_gradient.index)

    df_gradient[['x01d','y01d','r']] = df_filter(df_gradient[['x01d','y01d','r']], cutoff=cutoff, order=order)
    df_gradient['x02d'] = np.gradient(df_gradient['x01d'], df_gradient.index)
    df_gradient['y02d'] = np.gradient(df_gradient['y01d'], df_gradient.index)
    df_gradient['r1d'] = np.gradient(df_gradient['r'], df_gradient.index)

    df_gradient[['x02d','y02d','r1d']] = df_filter(df_gradient[['x02d','y02d','r1d']], cutoff=cutoff, order=order)

    return df_gradient
```

```python
df_lowpass_gradient = lowpass_gradient(df_=df_, cutoff=0.5, order=1)
df_lowpass = lowpass(df_=df_, cutoff=1, order=1)

states = [
     ['x0','x01d','x02d'],
     ['y0','y01d','y02d'],
     ['psi','r','r1d']
]

for dof in states:
    fig,axes=plt.subplots(ncols=3)
    fig.set_size_inches(15,4)
    for state,ax in zip(dof,axes):
        ax.set_title(state)
        #df_lowpass_gradient.plot(y=state, ax=ax, label='lowpass gradient')
        df_lowpass.plot(y=state, ax=ax, label='lowpass', alpha=0.5)
        df_.plot(x='t', y=state, ax=ax, label='kalman', alpha=0.5)

```

```python
df_lowpass = pd.DataFrame(lowpass_filter(data=df_, cutoff=0.01, fs=fs, order=5),
                          columns=df_.columns, index=df_.index)
df_lowpass['t'] = df_lowpass.index.total_seconds()
df_lowpass['r'] = np.gradient(df_lowpass['psi'],df_lowpass['t'])
df_lowpass['r1d'] = np.gradient(df_lowpass['r'],df_lowpass['t'])

```

```python

fig,ax=plt.subplots()
fig.set_size_inches(15,10)

df_.plot(x='t', y='r1d', label='kalman', ax=ax)
df_lowpass.plot(x='t', y='r1d', label='lowpass', ax=ax)

for resample in [0.1,0.2,0.3,0.5,1]:

    psi_ = df_['psi'].resample(f'{resample}S').mean()
    t_ = psi_.index.total_seconds()
    r_ = np.gradient(psi_,t_)
    r1d_ = np.gradient(r_,t_)
    ax.plot(t_, r1d_, label=resample)
ax.legend()
```

```python

```