# This code example is to demonstrate 2D Ambisonic panning functions interactively
Acoustic Holography and Holophony

Franz Zotter, 2016

## Ambisonic Panning function in 2D
The Ambisonic panning function is derived from the equivalence that an infinite Fourier series equals a Dirac delta function:

\begin{equation}
g_\infty(\varphi)=\sum_{m=-\infty}^\infty e^{\mathrm{i}m(\varphi)}=\sum_{m=0}^\infty(2-\delta_{m,0})\cos(m\varphi)=\delta(\varphi).
\end{equation}

After making the order finite, we obtain a sinc function

\begin{equation}
g_\mathrm{N}(\varphi)=\sum_{m=0}^\mathrm{N}(2-\delta_{m,0})\cos(m\varphi)=\frac{\sin[(\mathrm{N}+\frac{1}{2})\varphi]}{\sin(\frac{1}{2}\varphi)}.
\end{equation}

```python
import numpy as np
import scipy as sp
import math
from bokeh.plotting import figure, output_file, show
from bokeh.io import push_notebook, output_notebook
from ipywidgets import interact, interactive, fixed
import ipywidgets as widgets

phi=np.linspace(-np.pi,np.pi,100)
output_notebook()
p1 = figure(title="5th order periodic sinc function",plot_width=400,plot_height=250)
N=5
normalization=2*N+1
sincp=np.sin((N+0.5)*phi)/np.sin(0.5*phi)
p1.line(phi*180/np.pi,normalization*sincp,line_width=3)
show(p1)
```

    Loading BokehJS ...

This kind of periodic sinc function could be cyclically shifted by any angle $\varphi_\mathrm{s}$ and would not change its shape. However, we are not quite satisfied with the height of its sidelobes. It is worth to involve weights $a_m$ for the suppression of these sidelobes:

\begin{equation}
   g_\mathrm{N}(\varphi)=\sum_{m=0}^\mathrm{N}a_m\,(2-\delta_{m,0})\cos(m\varphi).
\end{equation}

The so-called in-phase weighting (see Jérôme Daniel et al 1999) is defined as

\begin{equation}
   a_m=\frac{N!^2}{(N-m)!(N+m)!}=\frac{N^\underline{m}}{N^\overline{m}},
\end{equation}

and the so-called max-$\boldsymbol{r}_\mathrm{E}$ weighting is

\begin{equation}
   a_m=\cos\Bigl(\frac{\pi}{2(\mathrm{N}+1)}\Bigr),
\end{equation}

and the neutral,rectangular weighting with $a_m=1$ is called basic.

```python
def inphase_weights(N):
    a=np.ones(N+1)
    for n in range(1,N+1):
        a[n]=(N-n+1)/(1.0*(N+n))*a[n-1]
    return a

def maxre_weights(N):
    m=np.arange(0,N+1)
    a=np.cos(np.pi/(2*(N+1))*m)
    return a

def basic_weights(N):
    a=np.ones(N+1)
    return a

N=7
p2 = figure(title="7th-order weights",plot_width=400,plot_height=250)
m=np.arange(0,N+1)
a=basic_weights(N)
p2.line(m,a,color="green",line_width=3,legend_label="basic")
a=inphase_weights(N)
p2.line(m,a,color="red",line_width=3,legend_label="in-phase")
a=maxre_weights(N)
p2.line(m,a,color="blue",line_width=3,legend_label="max-rE")
p2.legend.background_fill_alpha=0.4
show(p2)
```

```python
def weighted_cosine_series(phi,a):
    N=a.size-1
    g=np.zeros(phi.size)
    amplitude=0;
    for m in range(0,N+1):
        g+=np.cos(m*phi)*a[m]*(2-(m==0))
        amplitude+=a[m]*(2-(m==0))
    return g/amplitude

p3 = figure(title="weighted cosine series",plot_width=400,plot_height=250)
a=basic_weights(N)
g=weighted_cosine_series(phi,a)
p3.line(phi*180/np.pi,g,line_width=3,color='green',legend_label="rectangular")
a=inphase_weights(N)
g=weighted_cosine_series(phi,a)
p3.line(phi*180/np.pi,g,line_width=3,color='red',legend_label="in-phase")
a=maxre_weights(N)
g=weighted_cosine_series(phi,a)
p3.line(phi*180/np.pi,g,line_width=3,color='blue',legend_label="max-rE")
show(p3)
```

```python
def g_ambipan(N,phis,phi,weight_type):
    g=np.zeros(phi.size)
    ampl=0
    if weight_type == 1:
        a=inphase_weights(N)
    elif weight_type == 2:
        a=maxre_weights(N)
    else:
        a=basic_weights(N)
    g=weighted_cosine_series(phi-phis,a)
    return g
```

```python
output_notebook()
g=g_ambipan(5,0,phi,0)
p = figure(title="2D Ambi Panning Function",plot_width=400, plot_height=270, x_range=(-180,180), y_range=(-.4,1.1))
ll=p.line(phi*180/np.pi, g , line_width=3)
```

    Loading BokehJS ...

```python
def plot_ambipan(N,phis,weight_type):
    phi=np.linspace(-np.pi,np.pi,100)
    g=g_ambipan(N,phis*np.pi/180,phi,weight_type)
    ll.data_source.data['y']=g
    push_notebook()
```

```python
show(p,notebook_handle=True)
```

<Bokeh Notebook handle for In[7]>

```python
interact(plot_ambipan,N=(0,10,1), phis=(-180.0,180.0,1.0),weight_type={'in-phase':1,'max-rE':2,'basic':3});
```


# Example 9.5: Modified Brayton Cycle (Real-Fluid)

*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{align}
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{align}
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 math import log, sqrt
from kilojoule.templates.kSI import *

air = realfluid.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
- Real fluid
  - non-ideal gas
  - variable specific heat
- Isobaric heat exchagners: Intercooler, Combustors, Regeneator
- Negligible changes in kinetic energy
- Negligible changes in potential energy

```
# 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}{ \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=T[1],p=p[1])
s[1] = air.s(T=T[1],p=p[1])
# Isentropic exit state: Isentropic Ideal Gas Relations
p['2s'] = p[2]
s['2s'] = s[1]
h['2s'] = air.h(p=p['2s'],s=s['2s'])
T['2s'] = air.T(p=p['2s'],s=s['2s'])

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

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

##### 2$\to$3) Intercooling
h[3] = air.h(T=T[3],p=p[3])
s[3] = air.s(T=T[3],p=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=p['4s'],s=s['4s'])
T['4s'] = air.T(p=p['4s'],s=s['4s'])

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

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

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

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

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

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

# Isentropic efficiency
h[7] = h[6] - eta_t*(h[6]-h['7s'])
T[7] = air.T(h=h[7],p=p[7])
s[7] = air.s(p=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=T[8],p=p[8])
s[8] = air.s(T=T[8],p=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=p['9s'],s=s['9s'])
T['9s'] = air.T(h=h['9s'],p=p[8])

# Isentropic efficiency
h[9] = h[8] - eta_t*(h[8]-h['9s'])
T[9] = air.T(h=h[9],p=p[9])
s[9] = air.s(h=h[9],p=p[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( \mathrm{ T } = {T}_{ 1 }, \mathrm{ p } = {p}_{ 1 } \right)  = \mathrm{ \underset{ air }{ h } }\left( \mathrm{ T } = 300\ \mathrm{K}, \mathrm{ p } = 100.0\ \mathrm{kPa} \right) = 426.3\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

\begin{aligned}
  {s}_{ 1 } &=  \mathrm{ \underset{ air }{ s } }\left( \mathrm{ T } = {T}_{ 1 }, \mathrm{ p } = {p}_{ 1 } \right)  = \mathrm{ \underset{ air }{ s } }\left( \mathrm{ T } = 300\ \mathrm{K}, \mathrm{ p } = 100.0\ \mathrm{kPa} \right) = 3.8905\ \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 }  = 3.8905\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)}
\end{aligned}

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

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

 Isentropic efficiency

\begin{aligned}
  {h}_{ 2 } &=  {h}_{ 1 } + \frac{  {h}_{ 2s } - {h}_{ 1 }  }{ \eta_{c} } \\
    &= 426.3\ \frac{\mathrm{kJ}}{\mathrm{kg}} + \frac{  543.67\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 426.3\ \frac{\mathrm{kJ}}{\mathrm{kg}}  }{ 0.8\ }\\
    &= 573.02\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

\begin{aligned}
  {T}_{ 2 } &=  \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = {h}_{ 2 }, \mathrm{ p } = {p}_{ 1 } \right)  = \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = 573.02\ \frac{\mathrm{kJ}}{\mathrm{kg}}, \mathrm{ p } = 100.0\ \mathrm{kPa} \right) = 444.99\ \mathrm{K}
\end{aligned}

\begin{aligned}
  {s}_{ 2 } &=  \mathrm{ \underset{ air }{ s } }\left( \mathrm{ h } = {h}_{ 2 }, \mathrm{ p } = {p}_{ 2 } \right)  = \mathrm{ \underset{ air }{ s } }\left( \mathrm{ h } = 573.02\ \frac{\mathrm{kJ}}{\mathrm{kg}}, \mathrm{ p } = 316.23\ \mathrm{kPa} \right) = 3.9586\ \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 }  \\
    &=  426.3\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 573.02\ \frac{\mathrm{kJ}}{\mathrm{kg}} \\
    &= -146.72\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

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

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

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

\begin{aligned}
  q_{2\to{}3} &=   {h}_{ 3 } - {h}_{ 2 }  \\
    &=  476.33\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 573.02\ \frac{\mathrm{kJ}}{\mathrm{kg}} \\
    &= -96.684\ \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 }  = 3.7143\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)}
\end{aligned}

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

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

 Isentropic efficiency

\begin{aligned}
  {h}_{ 4 } &=  {h}_{ 3 } + \frac{  {h}_{ 4s } - {h}_{ 3 }  }{ \eta_{c} } \\
    &= 476.33\ \frac{\mathrm{kJ}}{\mathrm{kg}} + \frac{  613.35\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 476.33\ \frac{\mathrm{kJ}}{\mathrm{kg}}  }{ 0.8\ }\\
    &= 647.6\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

\begin{aligned}
  {T}_{ 4 } &=  \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = {h}_{ 4 }, \mathrm{ p } = {p}_{ 4 } \right)  = \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = 647.6\ \frac{\mathrm{kJ}}{\mathrm{kg}}, \mathrm{ p } = 1000.0\ \mathrm{kPa} \right) = 518.01\ \mathrm{K}
\end{aligned}

\begin{aligned}
  {s}_{ 4 } &=  \mathrm{ \underset{ air }{ s } }\left( \mathrm{ h } = {h}_{ 4 }, \mathrm{ p } = {p}_{ 4 } \right)  = \mathrm{ \underset{ air }{ s } }\left( \mathrm{ h } = 647.6\ \frac{\mathrm{kJ}}{\mathrm{kg}}, \mathrm{ p } = 1000.0\ \mathrm{kPa} \right) = 3.7827\ \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 }  \\
    &=  476.33\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 647.6\ \frac{\mathrm{kJ}}{\mathrm{kg}} \\
    &= -171.26\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

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

 Skip for now.  Need $h_9$ to analyze

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

 Skip for now.  Need $h_5$ from regenerator analysis

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

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

\begin{aligned}
  {s}_{ 6 } &=  \mathrm{ \underset{ air }{ s } }\left( \mathrm{ T } = {T}_{ 6 }, \mathrm{ p } = {p}_{ 6 } \right)  = \mathrm{ \underset{ air }{ s } }\left( \mathrm{ T } = 1400\ \mathrm{K}, \mathrm{ p } = 1000.0\ \mathrm{kPa} \right) = 4.8902\ \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 }  = 4.8902\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)}
\end{aligned}

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

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

 Isentropic efficiency

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

\begin{aligned}
  {T}_{ 7 } &=  \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = {h}_{ 7 }, \mathrm{ p } = {p}_{ 7 } \right)  = \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = 1319.0\ \frac{\mathrm{kJ}}{\mathrm{kg}}, \mathrm{ p } = 316.23\ \mathrm{kPa} \right) = 1127.0\ \mathrm{K}
\end{aligned}

\begin{aligned}
  {s}_{ 7 } &=  \mathrm{ \underset{ air }{ s } }\left( \mathrm{ p } = {p}_{ 7 }, \mathrm{ h } = {h}_{ 7 } \right)  = \mathrm{ \underset{ air }{ s } }\left( \mathrm{ p } = 316.23\ \mathrm{kPa}, \mathrm{ h } = 1319.0\ \frac{\mathrm{kJ}}{\mathrm{kg}} \right) = 4.9643\ \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 }  \\
    &=  1642.5\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 1319.0\ \frac{\mathrm{kJ}}{\mathrm{kg}} \\
    &= 323.52\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

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

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

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

\begin{aligned}
  q_{7\to{}8} &=   {h}_{ 8 } - {h}_{ 7 }  \\
    &=  1642.0\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 1319.0\ \frac{\mathrm{kJ}}{\mathrm{kg}} \\
    &= 322.96\ \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 }  = 5.2208\ \frac{\mathrm{kJ}}{\left(\mathrm{K} \cdot \mathrm{kg}\right)}
\end{aligned}

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

\begin{aligned}
  {T}_{ 9s } &=  \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = {h}_{ 9s }, \mathrm{ p } = {p}_{ 8 } \right)  = \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = 1238.0\ \frac{\mathrm{kJ}}{\mathrm{kg}}, \mathrm{ p } = 316.23\ \mathrm{kPa} \right) = 1057.0\ \mathrm{K}
\end{aligned}

 Isentropic efficiency

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

\begin{aligned}
  {T}_{ 9 } &=  \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = {h}_{ 9 }, \mathrm{ p } = {p}_{ 9 } \right)  = \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = 1318.8\ \frac{\mathrm{kJ}}{\mathrm{kg}}, \mathrm{ p } = 100.0\ \mathrm{kPa} \right) = 1126.9\ \mathrm{K}
\end{aligned}

\begin{aligned}
  {s}_{ 9 } &=  \mathrm{ \underset{ air }{ s } }\left( \mathrm{ h } = {h}_{ 9 }, \mathrm{ p } = {p}_{ 9 } \right)  = \mathrm{ \underset{ air }{ s } }\left( \mathrm{ h } = 1318.8\ \frac{\mathrm{kJ}}{\mathrm{kg}}, \mathrm{ p } = 100.0\ \mathrm{kPa} \right) = 5.2948\ \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 }  \\
    &=  1642.0\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 1318.8\ \frac{\mathrm{kJ}}{\mathrm{kg}} \\
    &= 323.17\ \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.72\ \frac{\mathrm{kJ}}{\mathrm{kg}} + -171.26\ \frac{\mathrm{kJ}}{\mathrm{kg}} + 323.52\ \frac{\mathrm{kJ}}{\mathrm{kg}} + 323.17\ \frac{\mathrm{kJ}}{\mathrm{kg}}\\
    &= 328.71\ \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
      426.300776
      3.890501

      2
      444.993407
      316.227766
      573.017977
      3.958643

      2s
      416.389023
      316.227766
      543.674536
      3.890501

      3
      350.0
      316.227766
      476.334363
      3.714342

      4
      518.012779
      1000.000000
      647.598028
      3.782671

      4s
      484.899672
      1000.000000
      613.345295
      3.714342

      5
      -
      1000.000000
      -
      -

      6
      1400.0
      1000.000000
      1642.536333
      4.890246

      7
      1126.993542
      316.227766
      1319.012131
      4.96433

      7s
      1057.124089
      316.227766
      1238.131081
      4.890246

      8
      1400.0
      316.227766
      1641.976857
      5.220798

      9
      1126.941368
      100.000000
      1318.806293
      5.294803

      9s
      1057.022121
      100.000000
      1238.013652
      5.220798

      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],p=p[5])
s[5] = air.s(h=h[5],p=p[5])
T[10] = air.T(h=h[10],p=p[10])
s[10] = air.s(h=h[10],p=p[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)  \\
    &= 647.6\ \frac{\mathrm{kJ}}{\mathrm{kg}} +  0.85\  \cdot  \left(  1318.8\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 647.6\ \frac{\mathrm{kJ}}{\mathrm{kg}}  \right) \\
    &= 1218.1\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

\begin{aligned}
  {h}_{ 10 } &=   {h}_{ 9 } -  \left(  {h}_{ 5 } - {h}_{ 4 }  \right)  \\
    &=  1318.8\ \frac{\mathrm{kJ}}{\mathrm{kg}} - \left(  1218.1\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 647.6\ \frac{\mathrm{kJ}}{\mathrm{kg}}  \right) \\
    &= 748.28\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

\begin{aligned}
  {T}_{ 5 } &=  \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = {h}_{ 5 }, \mathrm{ p } = {p}_{ 5 } \right)  = \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = 1218.1\ \frac{\mathrm{kJ}}{\mathrm{kg}}, \mathrm{ p } = 1000.0\ \mathrm{kPa} \right) = 1039.4\ \mathrm{K}
\end{aligned}

\begin{aligned}
  {s}_{ 5 } &=  \mathrm{ \underset{ air }{ s } }\left( \mathrm{ h } = {h}_{ 5 }, \mathrm{ p } = {p}_{ 5 } \right)  = \mathrm{ \underset{ air }{ s } }\left( \mathrm{ h } = 1218.1\ \frac{\mathrm{kJ}}{\mathrm{kg}}, \mathrm{ p } = 1000.0\ \mathrm{kPa} \right) = 4.5401\ \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 }, \mathrm{ p } = {p}_{ 10 } \right)  = \mathrm{ \underset{ air }{ T } }\left( \mathrm{ h } = 748.28\ \frac{\mathrm{kJ}}{\mathrm{kg}}, \mathrm{ p } = 100.0\ \mathrm{kPa} \right) = 614.12\ \mathrm{K}
\end{aligned}

\begin{aligned}
  {s}_{ 10 } &=  \mathrm{ \underset{ air }{ s } }\left( \mathrm{ h } = {h}_{ 10 }, \mathrm{ p } = {p}_{ 10 } \right)  = \mathrm{ \underset{ air }{ s } }\left( \mathrm{ h } = 748.28\ \frac{\mathrm{kJ}}{\mathrm{kg}}, \mathrm{ p } = 100.0\ \mathrm{kPa} \right) = 4.6228\ \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 }  \\
    &=  1642.5\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 1218.1\ \frac{\mathrm{kJ}}{\mathrm{kg}} \\
    &= 424.41\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

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

\begin{aligned}
  q_{7\to{}8} &=   {h}_{ 8 } - {h}_{ 7 }  \\
    &=  1642.0\ \frac{\mathrm{kJ}}{\mathrm{kg}} - 1319.0\ \frac{\mathrm{kJ}}{\mathrm{kg}} \\
    &= 322.96\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

#### Heat input

\begin{aligned}
  q_{in} &=  q_{5\to{}6} + q_{7\to{}8} \\
    &= 424.41\ \frac{\mathrm{kJ}}{\mathrm{kg}} + 322.96\ \frac{\mathrm{kJ}}{\mathrm{kg}}\\
    &= 747.38\ \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.71\ \frac{\mathrm{kJ}}{\mathrm{kg}} }{ 747.38\ \frac{\mathrm{kJ}}{\mathrm{kg}} }\\
    &= 0.43982\
\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.43982\ }{ 0.78571\ }\\
    &= 0.55978\
\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.72\ \frac{\mathrm{kJ}}{\mathrm{kg}} + -171.26\ \frac{\mathrm{kJ}}{\mathrm{kg}} \right)\\
    &= 317.98\ \frac{\mathrm{kJ}}{\mathrm{kg}}
\end{aligned}

#### work out

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

#### back work ratio

\begin{aligned}
  BWR &=  \frac{ w_{in} }{ w_{out} } \\
    &= \frac{ 317.98\ \frac{\mathrm{kJ}}{\mathrm{kg}} }{ 646.69\ \frac{\mathrm{kJ}}{\mathrm{kg}} }\\
    &= 0.4917\
\end{aligned}

#### Summary of Results

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

\begin{aligned}{ BWR }&={ 0.4917\ }&{ T_{H} }&={ 1400\ \mathrm{K} }&{ T_{L} }&={ 300\ \mathrm{K} }\\{ \eta_{II} }&={ 0.55978\ }&{ \eta_{c} }&={ 0.8\ }&{ \eta_{reg} }&={ 0.85\ }\\{ \eta_{t} }&={ 0.8\ }&{ \eta_{th} }&={ 0.43982\ }&{ \eta_{th,Carnot} }&={ 0.78571\ }\\{ q_{2\to{}3} }&={ -96.684\ \frac{\mathrm{kJ}}{\mathrm{kg}} }&{ q_{5\to{}6} }&={ 424.41\ \frac{\mathrm{kJ}}{\mathrm{kg}} }&{ q_{7\to{}8} }&={ 322.96\ \frac{\mathrm{kJ}}{\mathrm{kg}} }\\{ q_{in} }&={ 747.38\ \frac{\mathrm{kJ}}{\mathrm{kg}} }&{ r_{p} }&={ 3.1623\ }&{ w_{1\to{}2} }&={ -146.72\ \frac{\mathrm{kJ}}{\mathrm{kg}} }\\{ w_{3\to{}4} }&={ -171.26\ \frac{\mathrm{kJ}}{\mathrm{kg}} }&{ w_{6\to{}7} }&={ 323.52\ \frac{\mathrm{kJ}}{\mathrm{kg}} }&{ w_{8\to{}9} }&={ 323.17\ \frac{\mathrm{kJ}}{\mathrm{kg}} }\\{ w_{in} }&={ 317.98\ \frac{\mathrm{kJ}}{\mathrm{kg}} }&{ w_{net} }&={ 328.71\ \frac{\mathrm{kJ}}{\mathrm{kg}} }&{ w_{out} }&={ 646.69\ \frac{\mathrm{kJ}}{\mathrm{kg}} }\\{  }\end{aligned}

      T
      p
      h
      s

      unit
      K
      kPa
      kJ/kg
      kJ/K/kg

      1
      300.000000
      100.000000
      426.300776
      3.890501

      2
      444.993407
      316.227766
      573.017977
      3.958643

      2s
      416.389023
      316.227766
      543.674536
      3.890501

      3
      350.000000
      316.227766
      476.334363
      3.714342

      4
      518.012779
      1000.000000
      647.598028
      3.782671

      4s
      484.899672
      1000.000000
      613.345295
      3.714342

      5
      1039.367378
      1000.000000
      1218.125053
      4.540090

      6
      1400.000000
      1000.000000
      1642.536333
      4.890246

      7
      1126.993542
      316.227766
      1319.012131
      4.964330

      7s
      1057.124089
      316.227766
      1238.131081
      4.890246

      8
      1400.000000
      316.227766
      1641.976857
      5.220798

      9
      1126.941368
      100.000000
      1318.806293
      5.294803

      9s
      1057.022121
      100.000000
      1238.013652
      5.220798

      10
      614.117765
      100.000000
      748.279268
      4.622808

\begin{aligned}{ w_{net} }&={ 328.71\ \frac{\mathrm{kJ}}{\mathrm{kg}} }\\{ q_{in} }&={ 747.38\ \frac{\mathrm{kJ}}{\mathrm{kg}} }\\{ \eta_{th} }&={ 0.43982\ }\\{ BWR }&={ 0.4917\ }\\{  }\end{aligned}

### Plotting

```
for i in [1,'2s',2,3,'4s',4,5,6,'7s',7,8,'9s',9,10]:
    v[i] = air.v(T=T[i],p=p[i])

#ShowCalculations( comments=True);
states.display()
```

      T
      p
      v
      h
      s

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

      1
      300.000000
      100.000000
      0.860882
      426.300776
      3.890501

      2
      444.993407
      316.227766
      0.404280
      573.017977
      3.958643

      2s
      416.389023
      316.227766
      0.378235
      543.674536
      3.890501

      3
      350.000000
      316.227766
      0.317722
      476.334363
      3.714342

      4
      518.012779
      1000.000000
      0.149207
      647.598028
      3.782671

      4s
      484.899672
      1000.000000
      0.139638
      613.345295
      3.714342

      5
      1039.367378
      1000.000000
      0.299256
      1218.125053
      4.540090

      6
      1400.000000
      1000.000000
      0.402841
      1642.536333
      4.890246

      7
      1126.993542
      316.227766
      1.023932
      1319.012131
      4.964330

      7s
      1057.124089
      316.227766
      0.960492
      1238.131081
      4.890246

      8
      1400.000000
      316.227766
      1.271792
      1641.976857
      5.220798

      9
      1126.941368
      100.000000
      3.235802
      1318.806293
      5.294803

      9s
      1057.022121
      100.000000
      3.035082
      1238.013652
      5.220798

      10
      614.117765
      100.000000
      1.763464
      748.279268
      4.622808

#### (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',linestyle='dashed');
```

```

```

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


# Linear Gaussian filtering and smoothing

Provided are two examples of linear state-space models on which one can perform Bayesian filtering and smoothing in order to obtain
a posterior distribution over a latent state trajectory based on noisy observations.
In order to understand the theory behind these methods in detail we refer to [1] and [2].

We provide examples for two different types of state-space model:
1. [Linear, Discrete State-Space Model](#1.-Linear-Discrete-State-Space-Model:-Car-Tracking): Car Tracking
2. [Linear, Continuous-Discrete State-Space Model](#2.-Linear-Continuous-Discrete-State-Space-Model:-Ornstein-Uhlenbeck-Process): The Ornstein-Uhlenbeck Process

**References**:
> [1] Särkkä, Simo, and Solin, Arno. Applied Stochastic Differential Equations. Cambridge University Press, 2019.
>
> [2] Särkkä, Simo. Bayesian Filtering and Smoothing. Cambridge University Press, 2013.

```python
import numpy as np

import probnum as pn
from probnum import filtsmooth, randvars, statespace
from probnum.problems import RegressionProblem
```

```python
np.random.seed(12345)
```

```python
# Make inline plots vector graphics instead of raster graphics
%matplotlib inline
from IPython.display import set_matplotlib_formats
set_matplotlib_formats('pdf', 'svg')

# Plotting
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
plt.style.use('../../probnum.mplstyle')
```

## 1. **Linear Discrete** State-Space Model: Car Tracking

---

We begin showcasing the arguably most simple case in which we consider the following state-space model. Consider matrices $A \in \mathbb{R}^{d \times d}$ and $H \in \mathbb{R}^{m \times d}$ where $d$ is the state dimension and $m$ is the dimension of the measurements. Then we define the dynamics and the measurement model as follows:

For $k = 1, \dots, K$ and $x_0 \sim \mathcal{N}(\mu_0, \Sigma_0)$:

$$
\begin{align}
    \boldsymbol{x}_k &\sim \mathcal{N}(\boldsymbol{A} \, \boldsymbol{x}_{k-1}, \boldsymbol{Q}) \\
    \boldsymbol{y}_k &\sim \mathcal{N}(\boldsymbol{H} \, \boldsymbol{x}_k, \boldsymbol{R})
\end{align}
$$

This defines a dynamics model that assumes a state $\boldsymbol{x}_k$ in a **discrete** sequence of states arising from a linear projection of the previous state $x_{k-1}$ corrupted with additive Gaussian noise under a **process noise** covariance matrix $Q$.
Similarly, the measurements $\boldsymbol{y}_k$ are assumed to be linear projections of the latent state under additive Gaussian noise according to a **measurement noise** covariance $R$.
In the following example we consider projections and covariances that are constant over the state and measurement trajectories (linear time invariant, or **LTI**). Note that this can be generalized to a linear time-varying state-space model, as well. Then $A$ is a function $A: \mathbb{T} \rightarrow \mathbb{R}^{d \times d}$ and $H$ is a function $H: \mathbb{T} \rightarrow \mathbb{R}^{m \times d}$ where $\mathbb{T}$ is the "time dimension".

In other words, here, every relationship is linear and every distribution is a Gaussian distribution.
Under these simplifying assumptions it is possible to obtain a filtering posterior distribution over the state trajectory $(\boldsymbol{x}_k)_{k=1}^{K}$ by using a
**Kalman Filter**. The example is taken from Example 3.6 in [2].

### Define State-Space Model

#### I. Discrete Dynamics Model: Linear, Time-Invariant, Gaussian Transitions

```python
state_dim = 4
observation_dim = 2
```

```python
delta_t = 0.2
# Define linear transition operator
dynamics_transition_matrix = np.eye(state_dim) + delta_t * np.diag(np.ones(2), 2)
# Define process noise (covariance) matrix
process_noise_matrix = (
    np.diag(np.array([delta_t ** 3 / 3, delta_t ** 3 / 3, delta_t, delta_t]))
    + np.diag(np.array([delta_t ** 2 / 2, delta_t ** 2 / 2]), 2)
    + np.diag(np.array([delta_t ** 2 / 2, delta_t ** 2 / 2]), -2)
)
```

To create a discrete, LTI Gaussian dynamics model, `probnum` provides the `DiscreteLTIGaussian` class that takes
- `state_trans_mat` : the linear transition matrix (above: $A$)
- `shift_vec` : a force vector for _affine_ transformations of the state (here: zero)
- `proc_noise_cov_mat` : the covariance matrix for the Gaussian process noise

```python
# Create discrete, Linear Time-Invariant Gaussian dynamics model
dynamics_model = statespace.DiscreteLTIGaussian(
    state_trans_mat=dynamics_transition_matrix,
    shift_vec=np.zeros(state_dim),
    proc_noise_cov_mat=process_noise_matrix,
)
```

#### II. Discrete Measurement Model: Linear, Time-Invariant, Gaussian Measurements

```python
measurement_marginal_variance = 0.5
measurement_matrix = np.eye(observation_dim, state_dim)
measurement_noise_matrix = measurement_marginal_variance * np.eye(observation_dim)
```

```python
measurement_model = statespace.DiscreteLTIGaussian(
    state_trans_mat=measurement_matrix,
    shift_vec=np.zeros(observation_dim),
    proc_noise_cov_mat=measurement_noise_matrix,
)
```

#### III. Initial State Random Variable

```python
mu_0 = np.zeros(state_dim)
sigma_0 = 0.5 * measurement_marginal_variance * np.eye(state_dim)
initial_state_rv = randvars.Normal(mean=mu_0, cov=sigma_0)
```

### Generate Data for the State-Space Model

`statespace.generate_samples()` is used to sample both latent states and noisy observations from the specified state space model.

```python
time_grid = np.arange(0., 10., step=delta_t)
```

```python
latent_states, observations = statespace.generate_samples(
    dynmod=dynamics_model,
    measmod=measurement_model,
    initrv=initial_state_rv,
    times=time_grid,
)
```

```python
regression_problem = RegressionProblem(
    observations=observations,
    locations=time_grid,
)
```

### Kalman Filtering

#### I. Kalman Filter

```python
kalman_filter = filtsmooth.Kalman(
    dynamics_model=dynamics_model,
    measurement_model=measurement_model,
    initrv=initial_state_rv
)
```

#### II. Perform Kalman Filtering + Rauch-Tung-Striebel Smoothing

```python
state_posterior, _ = kalman_filter.filtsmooth(regression_problem)
```

The method `filtsmooth` returns a `KalmanPosterior` object which provides convenience functions for e.g. sampling and interpolation.
We can also extract the just computed posterior smoothing state variables by querying the `.state_rvs` property.
This yields a list of Gaussian Random Variables from which we can extract the statistics in order to visualize them.

```python
grid = state_posterior.locations
posterior_state_rvs = state_posterior.states          # List of <num_time_points> Normal Random Variables
posterior_state_means = posterior_state_rvs.mean      # Shape: (num_time_points, state_dim)
posterior_state_covs = posterior_state_rvs.cov        # Shape: (num_time_points, state_dim, state_dim)
```

### Visualize Results

```python
state_fig = plt.figure()
state_fig_gs = gridspec.GridSpec(ncols=2, nrows=2, figure=state_fig)

ax_00 = state_fig.add_subplot(state_fig_gs[0, 0])
ax_01 = state_fig.add_subplot(state_fig_gs[0, 1])
ax_10 = state_fig.add_subplot(state_fig_gs[1, 0])
ax_11 = state_fig.add_subplot(state_fig_gs[1, 1])

# Plot means
mu_x_1, mu_x_2, mu_x_3, mu_x_4 = [posterior_state_means[:, i] for i in range(state_dim)]

ax_00.plot(grid, mu_x_1, label="posterior mean");
ax_01.plot(grid, mu_x_2);
ax_10.plot(grid, mu_x_3);
ax_11.plot(grid, mu_x_4);

# Plot marginal standard deviations
std_x_1, std_x_2, std_x_3, std_x_4 = [np.sqrt(posterior_state_covs[:, i, i]) for i in range(state_dim)]

ax_00.fill_between(grid, mu_x_1 - 1.96 * std_x_1,  mu_x_1 + 1.96 * std_x_1, alpha=0.2, label="1.96 marginal stddev");
ax_01.fill_between(grid, mu_x_2 - 1.96 * std_x_2,  mu_x_2 + 1.96 * std_x_2, alpha=0.2);
ax_10.fill_between(grid, mu_x_3 - 1.96 * std_x_3,  mu_x_3 + 1.96 * std_x_3, alpha=0.2);
ax_11.fill_between(grid, mu_x_4 - 1.96 * std_x_4,  mu_x_4 + 1.96 * std_x_4, alpha=0.2);

# Plot groundtruth
obs_x_1, obs_x_2 = [observations[:, i] for i in range(observation_dim)]

ax_00.scatter(time_grid, obs_x_1, marker=".", label="measurements");
ax_01.scatter(time_grid, obs_x_2, marker=".");

# Add labels etc.
ax_00.set_xlabel("t")
ax_01.set_xlabel("t")
ax_10.set_xlabel("t")
ax_11.set_xlabel("t")

ax_00.set_title(r"$x_1$")
ax_01.set_title(r"$x_2$")
ax_10.set_title(r"$x_3$")
ax_11.set_title(r"$x_4$")
handles, labels = ax_00.get_legend_handles_labels()
state_fig.legend(handles, labels, loc='center left', bbox_to_anchor=(1, 0.5))

state_fig.tight_layout()
```

## 2. **Linear Continuous-Discrete** State-Space Model: Ornstein-Uhlenbeck Process

---

Now, consider we have a look at **continuous** dynamics. We assume that there is a continuous process that defines the dynamics of our latent space from which we collect discrete linear-Gaussian measurements (as above). Only the dynamics model becomes continuous. In particular, we formulate the dynamics as a stochastic process in terms of a linear time-invariant stochastic differential equation (LTISDE). We refer to [1] for more details.
Consider matrices $\boldsymbol{F} \in \mathbb{R}^{d \times d}$, $\boldsymbol{L} \in \mathbb{R}^{s \times d}$ and $H \in \mathbb{R}^{m \times d}$ where $d$ is the state dimension and $m$ is the dimension of the measurements.
We define the following **continuous-discrete** state-space model:

Let $x(t_0) \sim \mathcal{N}(\mu_0, \Sigma_0)$.

$$
\begin{align}
    d\boldsymbol{x} &= \boldsymbol{F} \, \boldsymbol{x} \, dt + \boldsymbol{L} \, d \boldsymbol{\omega} \\
    \boldsymbol{y}_k &\sim \mathcal{N}(\boldsymbol{H} \, \boldsymbol{x}(t_k), \boldsymbol{R}), \qquad k = 1, \dots, K
\end{align}
$$

where $\boldsymbol{\omega} \in \mathbb{R}^s$ denotes a vector of driving forces (often Brownian Motion).

Note that this can be generalized to a linear time-varying state-space model, as well. Then $\boldsymbol{F}$ is a function $\mathbb{T} \rightarrow \mathbb{R}^{d \times d}$,
$\boldsymbol{L}$ is a function $\mathbb{T} \rightarrow \mathbb{R}^{s \times d}$, and $H$ is a function $\mathbb{T} \rightarrow \mathbb{R}^{m \times d}$ where $\mathbb{T}$ is the "time dimension". In the following example, however, we consider a LTI SDE, namely, the Ornstein-Uhlenbeck Process from which we observe discrete linear Gaussian measurements.

### Define State-Space Model

#### I. Continuous Dynamics Model: Linear, Time-Invariant Stochastic Differential Equation (LTISDE)

```python
state_dim = 1
observation_dim = 1
```

```python
delta_t = 0.2
# Define Linear, time-invariant Stochastic Differential Equation that models
# the (scalar) Ornstein-Uhlenbeck Process
drift_constant = 0.21
dispersion_constant = np.sqrt(0.5)
drift = -drift_constant * np.eye(state_dim)
force = np.zeros(state_dim)
dispersion = dispersion_constant * np.eye(state_dim)
```

The _continuous_ counterpart to the discrete LTI Gaussian model from above is provided via the `LTISDE` class.
It is initialized by the state space components
- `driftmat` : the drift matrix $\boldsymbol{F}$
- `forcevec` : a force vector that is added to the state (note that this is **not** $\boldsymbol{\omega}$.) Here: zero.
- `dispmat` : the dispersion matrix $\boldsymbol{L}$

```python
# Create dynamics model
dynamics_model = statespace.LTISDE(
    driftmat=drift,
    forcevec=force,
    dispmat=dispersion,
)
```

#### II. Discrete Measurement Model: Linear, Time-Invariant Gaussian Measurements

```python
measurement_marginal_variance = 0.1
measurement_matrix = np.eye(observation_dim, state_dim)
measurement_noise_matrix = measurement_marginal_variance * np.eye(observation_dim)
```

As above, the measurement model is discrete, LTI Gaussian. Only the dymanics are continuous (i.e. continuous-discrete).

```python
measurement_model = statespace.DiscreteLTIGaussian(
    state_trans_mat=measurement_matrix,
    shift_vec=np.zeros(observation_dim),
    proc_noise_cov_mat=measurement_noise_matrix,
)
```

#### III. Initial State Random Variable

```python
mu_0 = 10. * np.ones(state_dim)
sigma_0 = np.eye(state_dim)
initial_state_rv = randvars.Normal(mean=mu_0, cov=sigma_0)
```

### Generate Data for the State-Space Model

`statespace.generate_samples()` is used to sample both latent states and noisy observations from the specified state space model.

```python
time_grid = np.arange(0., 10., step=delta_t)
```

```python
latent_states, observations = statespace.generate_samples(
    dynmod=dynamics_model,
    measmod=measurement_model,
    initrv=initial_state_rv,
    times=time_grid,
)
```

```python
regression_problem = RegressionProblem(
    observations=observations,
    locations=time_grid,
)
```

### Kalman Filtering

In fact, since we still consider a **linear** model, we can apply Kalman Filtering in this case again.
According to Section 10 in [1], the moments of the filtering posterior in the continuous-discrete case are solutions to linear differential equations, which `probnum` solves for us when invoking the `<Kalman_object>.filtsmooth(...)` method.

#### I. Kalman Filter

```python
kalman_filter = filtsmooth.Kalman(
    dynamics_model=dynamics_model,
    measurement_model=measurement_model,
    initrv=initial_state_rv,
)
```

#### II. Perform Kalman Filtering + Rauch-Tung-Striebel Smoothing

```python
state_posterior, _ = kalman_filter.filtsmooth(regression_problem)
```

The method `filtsmooth` returns a `KalmanPosterior` object which provides convenience functions for e.g. sampling and prediction.
We can also extract the just computed posterior smoothing state variables by querying the `.state_rvs` property.
This yields a list of Gaussian Random Variables from which we can extract the statistics in order to visualize them.

```python
grid = np.linspace(0, 11, 500)

posterior_state_rvs = state_posterior(grid)                 # List of <num_time_points> Normal Random Variables
posterior_state_means = posterior_state_rvs.mean.squeeze()  # Shape: (num_time_points, )
posterior_state_covs = posterior_state_rvs.cov              # Shape: (num_time_points, )

samples = state_posterior.sample(size=3, t=grid)
```

### Visualize Results

```python
state_fig = plt.figure()

ax = state_fig.add_subplot()

# Plot means
ax.plot(grid, posterior_state_means, label="posterior mean")

# Plot samples
for smp in samples:
    ax.plot(grid, smp[:, 0], color="gray", alpha=0.75, linewidth=1, linestyle="dashed", label="sample")

# Plot marginal standard deviations
std_x = np.sqrt(np.abs(posterior_state_covs)).squeeze()
ax.fill_between(
    grid,
    posterior_state_means - 1.96 * std_x,
    posterior_state_means + 1.96 * std_x,
    alpha=0.2,
    label="1.96 marginal stddev",
)
ax.scatter(time_grid, observations, marker=".", label="measurements")
# Add labels etc.
ax.set_xlabel("t")
ax.set_title(r"$x$")

# These two lines just remove duplicate labels (caused by the samples) from the legend
handles, labels = ax.get_legend_handles_labels()
by_label = dict(zip(labels, handles))

ax.legend(
    by_label.values(), by_label.keys(), loc="center left", bbox_to_anchor=(1, 0.5)
)

state_fig.tight_layout()
```

```python

```


```python
import numpy as np
import matplotlib.pyplot as plt
from math import sin, cos, radians
from sympy import *
init_printing(use_latex='mathjax')
```

# Дороги в симуляторе

Дороги представлены в виде ломанных. Ломанные обеспечивают достаточную точность, нет необходимости усложнять и применять различные кривые Безье и прочее.

Дорогу надо не просто отрендерить, но и передать в каком-то виде в систему управления автомобилем. В качестве представления неподвижных препядствий выбран OccupancyGrid, потому что это просто и хорошо подходит для статических препятствий. Непонятно, как еще можно представить произвольное окружение. Поэтому для представления дороги в виде OccupancyGrid ее надо растрировать.

Для растрирования дороги применяется модифицированный алгоритм Брезенхэма для рисования линий с заданной толщины. Главная проблема - это соединение двух сегментов дороги под углом.

Угловое соединение полосы многополосной дороги с шириной полосы $width$, сдвинутой от центральной полосы на $offset$.

Дорога определяется центральной ломаной, шириной и количеством полос. На рисунке приведены два сегмента дороги, центральная линия которых определана отрезками $B_1B_2$ и $B_2B_3$ имеющих одну полосу шириной $width$, отстаящую от центральной линии на расстояние $offset$. Полоса параллельна центральной линии ($A_1A_2 \mathbin{\|} B_1 B_2 \mathbin{\|} O_1O_2$ и $A_2A_3 \mathbin{\|}  B_2B_3 \mathbin{\|} O2_O3$ соответственно).

Необходимо найти координаты точек $A_2$, $B_2$, чтобы правильно растрировать угловое соединение сегментов. Точку $A_2$ найдем как точку пересечения прямых $A_1A_2$ и $O_2A_2$, а точку $B_2$ найдем как точку пересеячения прымых $B_1B_2$ и $O_2A_2$. Для этого составим канонические уравнения этих прямых.

$\vec{v}_1$, $\vec{v}_2$ - направляющие вектора двух сегментов дороги. $\vec{n}_1$, $\vec{n}_2$ - нормальные вектора к этим сегментам:
$$
\vec{n}_1 = \vec{v}_1 \times \vec{up} \\
\vec{n}_2 = \vec{v}_2 \times \vec{up}
$$
$
\begin{align*}
    \text{где } & up - \text{вектор, направленный вверх (0, 0, 1).}
\end{align*}
$

Координаты точек $B_1$ и $A_1$:
$$
\vec{B}_1 = \vec{O}_1 + \frac{\vec{n}_1}{\left\lVert \vec{n}_1 \right\rVert}\cdot offset \\
\vec{A}_1 = \vec{B}_1 + \frac{\vec{n}_1}{\left\lVert \vec{n}_1 \right\rVert}\cdot width \\
$$

Каноническое уравнение прямой $A_1A_2$:
$$
\frac{x - A_{1x}}{\vec{v}_{1x}} = \frac{y - A_{1y}}{\vec{v}_{1y}}
$$

Каноническое уравнение прямой $B_1B_2$:
$$
\frac{x - B_{1x}}{\vec{v}_{1x}} = \frac{y - B_{1y}}{\vec{v}_{1y}}
$$

Каноническое уравнение прямой $O_2A_3$:
$$
\frac{x - O_{2x}}{\vec{n}_x} = \frac{y - O_{2y}}{\vec{n}_x}
$$

Составим систему уравнений и найдем из нее координату точку $A_2$ пересечения прямых $A_1A_2$ и $O_2A_2$:
$$
\begin{cases}
    \frac{x - A_{1x}}{v_{1x}} = \frac{y - A_{1y}}{v_{1y}} \\
    \frac{x - O_{2x}}{\vec{n}_x} = \frac{y - O_{2y}}{\vec{n}_x}
\end{cases}
$$

Аналогично для точки $B_2$.

Решим систему символьно:

```python
x, y = symbols('x y')
nx, ny, vx, vy = symbols('nx ny vx vy')
Ax, Ay, Ox, Oy = symbols('Ax Ay Ox Oy')

e1 = (x - Ax)/vx - (y - Ay)/vy
e2 = (x - Ox)/nx - (y - Oy)/ny
res = solve([e1, e2], [x, y])

```

```python
e1
```

$$- \frac{- Ay + y}{vy} + \frac{- Ax + x}{vx}$$

```python
e2
```

$$- \frac{- Oy + y}{ny} + \frac{- Ox + x}{nx}$$

```python
res
```

$$\left \{ x : \frac{nx \left(Ax vy - Ay vx\right) - vx \left(Ox ny - Oy nx\right)}{nx vy - ny vx}, \quad y : \frac{ny \left(Ax vy - Ay vx\right) - vy \left(Ox ny - Oy nx\right)}{nx vy - ny vx}\right \}$$

```python
# Для копипаста в питон:
for r in res:
    print(res[r])
```

    (ny*(Ax*vy - Ay*vx) - vy*(Ox*ny - Oy*nx))/(nx*vy - ny*vx)
    (nx*(Ax*vy - Ay*vx) - vx*(Ox*ny - Oy*nx))/(nx*vy - ny*vx)

### Расчет стыков сегментов

```python
def draw_vec(P, vec, length):
    P1 = P + vec/np.linalg.norm(vec) * length
    coords = np.vstack([P, P1])
    plt.plot(coords[:,0], coords[:,1])

def draw_point(P):
    plt.plot([P[0]], [P[1]], 'o')

def draw_line(A, B):
    coords = np.vstack([A, B])
    plt.plot(coords[:,0], coords[:,1])

def draw_line_segment(A1, B1, A2, B2):
    draw_line(A1, A2)
    draw_line(B1, B2)
    draw_line(A2, B2)

def draw_segment(cur_points, next_points):
    for i in range(len(cur_points)-1):
        draw_line_segment(cur_points[i], cur_points[i+1], next_points[i], next_points[i+1])

def line_intersect(P1, v1, P2, v2):
    x = (v2[0]*(P1[0]*v1[1] - P1[1]*v1[0]) - v1[0]*(P2[0]*v2[1] - P2[1]*v2[0]))/(v2[0]*v1[1] - v2[1]*v1[0])
    y = (v2[1]*(P1[0]*v1[1] - P1[1]*v1[0]) - v1[1]*(P2[0]*v2[1] - P2[1]*v2[0]))/(v2[0]*v1[1] - v2[1]*v1[0])
    return np.array([x,y])

def normalize(vec):
    return vec/np.linalg.norm(vec)

points = np.array([
    [0,0],
    [10, 0],
    [10, 10],
    [15,15],
    [30, 15],
    [30, 0]
])

up = np.array([0,0,1])
lines_width = 1
lines_cnt = 6

plt.plot(points[:,0], points[:,1])

# Поддерживаем только дороги с четным количеством полос, половина в одну сторону, половина в другую

# Расчитываем набор оффсетов для всех точек полос относительно центральной линии
# Полосы начинаем считать с самой левой.
offsets = (np.arange(-lines_cnt/2, lines_cnt/2 + 1) * lines_width).reshape((lines_cnt+1, 1))

# Расчитываем вектор нормали к начальному сегменту и координаты начальных точек
# (на чертеже - A1, B1 итп)
v1 = normalize(points[1] - points[0])
n1 = np.cross(up, v1)[:2]
cur_points = points[0] + n1*offsets

for seg_i in range(len(points)-2):

    # Расчет точек на изгибе
    v2 = normalize(points[seg_i+2] - points[seg_i+1])
    n2 = np.cross(up, v2)[:2]
    n = (n1 + n2)/2
    next_points = np.array([line_intersect(p, v1, points[seg_i+1], n) for p in cur_points])

    draw_segment(cur_points, next_points)

    n1 = n2
    v1 = v2
    cur_points = next_points

next_points = points[-1] + n1*offsets
draw_segment(cur_points, next_points)

plt.show()
```

```python

```