# Wind farm

```python
import numpy as np
import sympy as sym
import pydae.build as db
from pydae.grid_bpu import bpu
```

```python

data = {
    "sys":{"name":"wind_farm","S_base":100e6, "K_sec":0.01,"K_p_agc":0.01,"K_i_agc":0.01},
    "lines":[
        {"bus_j": "W1mv",  "bus_k":  "W2mv", "R_km":0.262,  "X_km":0.118, "C_muF_km":0.250, "km":0.5},
        {"bus_j": "W2mv",  "bus_k":  "W3mv", "R_km":0.262,  "X_km":0.118, "C_muF_km":0.250, "km":0.5},
        {"bus_j": "W3mv",  "bus_k": "POImv", "R_km":0.262,  "X_km":0.118, "C_muF_km":0.250, "km":0.5},
        {"bus_j": "STmv",  "bus_k": "POImv", "R_km":0.262,  "X_km":0.118, "C_muF_km":0.250, "km":0.5},
        {"bus_j": "POI",   "bus_k":  "GRID", "R_km":0.262,  "X_km":0.118, "C_muF_km":0.250, "km":50.0, 'monitor':True},
        {"bus_j": "POI",  "bus_k": "POImv", "X_pu":0.01,"R_pu":0.05, "S_mva":10.0},
        {"bus_j": "W1mv",  "bus_k": "W1lv", "X_pu":0.01,"R_pu":0.06, "S_mva": 2.5},
        {"bus_j": "W2mv",  "bus_k": "W2lv", "X_pu":0.01,"R_pu":0.06, "S_mva": 2.5},
        {"bus_j": "W3mv",  "bus_k": "W3lv", "X_pu":0.01,"R_pu":0.06, "S_mva": 2.5},
        {"bus_j": "STmv",  "bus_k": "STlv", "X_pu":0.01,"R_pu":0.06, "S_mva": 2.5}
            ],
    "buses":[
            {"name":  "W1lv", "P_W":0.0, "Q_var":0.0, "pos_x": -1500.0, "pos_y":  200.0, "units": "m", "U_kV":0.69},
            {"name":  "W2lv", "P_W":0.0, "Q_var":0.0, "pos_x": -1000.0, "pos_y":  200.0, "units": "m", "U_kV":0.69},
            {"name":  "W3lv", "P_W":0.0, "Q_var":0.0, "pos_x":  -500.0, "pos_y":  200.0, "units": "m", "U_kV":0.69},
            {"name":  "STlv", "P_W":0.0, "Q_var":0.0, "pos_x":  -500.0, "pos_y":  200.0, "units": "m", "U_kV":0.69},
            {"name":  "W1mv", "P_W":0.0, "Q_var":0.0, "pos_x": -1500.0, "pos_y":  180.0, "units": "m", "U_kV":20.0},
            {"name":  "W2mv", "P_W":0.0, "Q_var":0.0, "pos_x": -1000.0, "pos_y":  180.0, "units": "m", "U_kV":20.0},
            {"name":  "W3mv", "P_W":0.0, "Q_var":0.0, "pos_x":  -500.0, "pos_y":  180.0, "units": "m", "U_kV":20.0},
            {"name": "POImv", "P_W":0.0, "Q_var":0.0, "pos_x":     0.0, "pos_y":    0.0, "units": "m", "U_kV":20.0},
            {"name":  "STmv", "P_W":0.0, "Q_var":0.0, "pos_x":     0.0, "pos_y":    0.0, "units": "m", "U_kV":20.0},
            {"name":   "POI", "P_W":0.0, "Q_var":0.0, "pos_x":   100.0, "pos_y":    0.0, "units": "m", "U_kV":66.0},
            {"name":  "GRID", "P_W":0.0, "Q_var":0.0, "pos_x":   500.0, "pos_y":    0.0, "units": "m", "U_kV":66.0},
    ],
    "vsgs":[{"bus":"GRID","S_n":50e6,"type":"vsg_co",
        "R_v":0.01,"X_v":0.1, "T_v":0.1,"T_w":1,
    "K_p":0.01,"T_p":0.1,"D":1.0,"v_ref":1.1,"p_c":0.0,"Droop":0.05,
    "K_q":0.01,
    "Omega_b":314.1592653589793,"omega_s":1.0,"K_sec":0.0,
    "K_delta":0.001}],

    }
```

```python
bpu_obj = bpu(data_input=data)

g_list = bpu_obj.dae['g']
h_dict = bpu_obj.dae['h_dict']
f_list = bpu_obj.dae['f']
x_list = bpu_obj.dae['x']
params_dict = bpu_obj.dae['params_dict']

sys = {'name':'wind_farm',
       'params_dict':params_dict,
       'f_list':f_list,
       'g_list':g_list,
       'x_list':x_list,
       'y_ini_list':bpu_obj.dae['y_ini'],
       'y_run_list':bpu_obj.dae['y_run'],
       'u_run_dict':bpu_obj.dae['u_run_dict'],
       'u_ini_dict':bpu_obj.dae['u_ini_dict'],
       'h_dict':h_dict}

sys = db.system(sys)
db.sys2num(sys)
```

```python

```


This is the first of two notebooks on the Euler equations.  In this notebook, we discuss the equations and the structure of the exact solution to the Riemann problem.  In the next notebook, we investigate approximate Riemann solvers.

# Table of contents

- [Fluid dynamics](#Fluid-dynamics)
- [The Euler equations](#The-Euler-equations)
- [Hyperbolic structure](#Hyperbolic-structure-of-the-Euler-equations)
- [Exact solution of the Riemann problem](#Exact-solution-of-the-Riemann-problem)
- [Plot particle trajectories](#Plot-particle-trajectories)
- [Plot Riemann solution with advected colors](#Plot-Riemann-solution-with-advected-colors)
- [Interactive Riemann solver](#Interactive-Riemann-solver)
- [Riemann problems with vacuum](#Riemann-problems-with-vacuum)

# Fluid dynamics

In this chapter we study the system of hyperbolic PDEs that governs the motions of fluids in the absence of viscosity.  These consist of conservation laws for **mass, momentum**, and **energy**.  Together, they are referred to as the **compressible Euler equations**, or simply the Euler equations.  Our discussion here is fairly brief; for much more detail see (Toro, 2013).

### Mass conservation

We will use $\rho(x,t)$ to denote the fluid density and $u(x,t)$ for its velocity.  Then the equation for conservation of mass is just the **continuity equation**:

$$\rho_t + (\rho u)_x = 0.$$

### Momentum conservation

The momentum is given by the product of density and velocity, $\rho u$.  The momentum flux has two components.  First, the momentum is transported in the same way that the density is; this flux is given by the momentum times the density: $\rho u^2$.

To understand the second term in the momentum flux, we must realize that a fluid is made up of many tiny molecules.  The density and velocity we are modeling are average values over some small region of space.  The individual molecules in that region are not all moving with exactly velocity $u$; that's just their average.  Each molecule also has some additional random velocity component.  These random velocities are what accounts for the **pressure** of the fluid, which we'll denote by $p$.  These velocity components also lead to a net flux of momentum.  Thus the momentum conservation equation is

$$(\rho u)_t + (\rho u^2 + p)_x = 0.$$

### Energy conservation

The energy has two components: internal energy $\rho e$ and kinetic energy $\rho u^2/2$:

$$E = \rho e + \frac{1}{2}\rho u^2.$$

Like the momentum flux, the energy flux involves both bulk transport ($Eu$) and transport due to pressure ($pu$):

$$E_t + (u(E+p)) = 0.$$

### Equation of state

You may have noticed that we have 4 unknowns (density, momentum, energy, and pressure) but only 3 conservation laws.  We need one more relation to close the system.  That relation, known as the equation of state, expresses how the pressure is related to the other quantities.  We'll focus on the case of a polytropic ideal gas, for which

$$p = \rho e (\gamma-1).$$

Here $\gamma$ is the ratio of specific heats, which for air is approximately 1.4.

## The Euler equations

We can write the three conservation laws as a single system $q_t + f(q)_x = 0$ by defining

\begin{align}
q & = \begin{pmatrix} \rho \\ \rho u \\ E\end{pmatrix}, &
f(q) & = \begin{pmatrix} \rho u \\ \rho u^2 + p \\ u(E+p)\end{pmatrix}.
\end{align}

In three dimensions, the equations are similar.  We have two additional velocity components $v, w$, and their corresponding fluxes.  Additionally, we have to account for fluxes in the $y$ and $z$ directions.  We can write the full system as

$$ q_t + f(q)_x + g(q)_y + h(q)_z = 0$$

with

\begin{align}
q & = \begin{pmatrix} \rho \\ \rho u \\ \rho v \\ \rho w \\ E\end{pmatrix}, &
f(q) & = \begin{pmatrix} \rho u \\ \rho u^2 + p \\ \rho u v \\ \rho u w \\ u(E+p)\end{pmatrix} &
g(q) & = \begin{pmatrix} \rho v \\ \rho uv \\ \rho v^2 + p \\ \rho v w \\ v(E+p)\end{pmatrix} &
h(q) & = \begin{pmatrix} \rho w \\ \rho uw \\ \rho vw \\ \rho w^2 + p \\ w(E+p)\end{pmatrix}.
\end{align}

## Hyperbolic structure of the 1D Euler equations

In our discussion of the structure of these equations, it is convenient to work with the primitive variables $(\rho, u, p)$ rather than the conserved variables.  In quasilinear form, we have

\begin{align}
\begin{bmatrix} \rho \\ u \\ p \end{bmatrix}_t
+ \begin{bmatrix} u & \rho & 0 \\ 0 & u & 1/\rho \\ 0 & \gamma \rho & u \end{bmatrix} \begin{bmatrix} \rho \\ u \\ p \end{bmatrix}_x & = 0.
\end{align}

### Characteristic velocities
In primitive variables, the eigenvalues of the flux Jacobian for the 1D Euler equations are:

\begin{align}
\lambda_1 & = u-c & \lambda_2 & = u & \lambda_3 & = u+c
\end{align}

Here $c$ is the sound speed:

$$ c = \sqrt{\frac{\gamma p}{\rho}}.$$

The eigenvectors of the flux Jacobian are (again in primitive variables):

\begin{align}
r_1 & = \begin{bmatrix} -\rho/c \\ 1 \\ - \rho c \end{bmatrix} &
r_2 & = \begin{bmatrix} 1 \\ 0 \\ 0 \end{bmatrix} &
r_3 & = \begin{bmatrix}  \rho/c \\ 1 \\ \rho c \end{bmatrix}.
\end{align}

Notice that the second characteristic speed, $\lambda_2$, depends only on $u$ and that $u$ does not change as we move in the direction of $r_2$.  In other words, the 2-characteristic velocity is constant on 2-integral curves.  We say this characteristic field is **linearly degenerate**; it admits neither shocks nor rarefactions.  In a simple 2-wave, all characteristics are parallel.  A jump in this family carries a change only in the density, and is referred to as a **contact discontinuity**.

The other two fields have characteristic velocities that **do** vary along the corresponding integral curves; thus the 1-wave and the 3-wave in any Riemann solution will be either a shock or a rarefaction.  We say these characteristic fields are **genuinely nonlinear**.

Mathematically, the $p$th field is linearly degenerate if

$$\nabla \lambda_p(q) \cdot r_p(q) = 0$$

and genuinely nonlinear if

$$\nabla \lambda_p(q) \cdot r_p(q) \ne 0.$$

### Riemann invariants

Since the Euler equations have three components, we expect each integral curve (a 1D set in 3D space) to be defined by two Riemann invariants.  These are:

\begin{align}
1 & : s, u+\frac{2c}{\gamma-1} \\
2 & : u, p \\
3 & : s, u-\frac{2c}{\gamma-1}.
\end{align}

Here $s$ is the **specific entropy**:

$$ s = c_v \log(p/\rho^\gamma) + C.$$

The level sets of these Riemann invariants are two-dimensional surfaces; the intersection of two appropriate level sets defines an integral curve.

### Integral curves
The 2-integral curves, of course, are simply lines of constant pressure and velocity (with varying density).  Since the field is linearly degenerate, these coincide with the Hugoniot loci.
We can determine the form of the 1- and 3-integral curves using the Riemann invariants above.  For a curve passing through $(\rho_0,u_0,p_0)$, we find

\begin{align}
    u(p) & = u_0 \pm \frac{2c_0}{\gamma-1}\left(1-(p/p_0)^{(\gamma-1)/(2\gamma)}\right).
\end{align}
Here the plus sign is for 1-waves and the minus sign is for 3-waves.

Below we plot the projection of some integral curves on the $p-u$ plane.

**To do**: Discuss how $\rho$ fits into this, or plot 3D integral curves.

```python
%matplotlib inline
from exact_solvers import Euler
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import widgets
from clawpack import riemann
from utils import riemann_tools
import matplotlib
matplotlib.rcParams.update({'font.size': 12})
from collections import namedtuple
Primitive_State = namedtuple('State', Euler.primitive_variables)
gamma = 1.4
```

```python
from ipywidgets import interact                   # for interactive widgets
#from utils.snapshot_widgets import interact      # for static figure that can be viewed online
```

```python
def plot_integral_curves(plot_1=True,plot_3=False,gamma=1.4,rho_0=1.):
    N = 400
    p = np.linspace(0.,5,N)
    p_0 = 1.
    uu = np.linspace(-3,3,15)
    c_0 = np.sqrt(gamma*p_0/rho_0)
    if plot_1:
        for u_0 in uu:
            u = u_0 + (2*c_0)/(gamma-1.)* \
                (1.-(p/p_0)**((gamma-1)/(2*gamma)))
            plt.plot(p,u,color='coral')
    if plot_3:
        for u_0 in uu:
            u = u_0 - (2*c_0)/(gamma-1.)* \
                (1.-(p/p_0)**((gamma-1)/(2*gamma)))
            plt.plot(p,u,color='maroon')
    plt.xlabel('p'); plt.ylabel('u')
    plt.show()
interact(plot_integral_curves,
         gamma=widgets.FloatSlider(min=1.1,max=3,value=1.4));
```

## The structure of centered rarefaction waves

## Rankine-Hugoniot jump conditions

The Hugoniot loci for 1- and 3-shocks are
\begin{align}
    u(p) & = u_0 \pm \frac{2c_0}{\sqrt{2\gamma(\gamma-1)}}
        \left(\frac{1-p/p_0}{\sqrt{1+\beta p/p_0}}\right), \\
\end{align}
where $\beta = (\gamma+1)/(\gamma-1)$.
Here the plus sign is for 1-shocks and the minus sign is for 3-shocks.

**To do**: Discuss how $\rho$ varies, and maybe plot 3D integral curves.

```python
def plot_hugoniot_loci(plot_1=True,plot_3=False,gamma=1.4,rho_0=1.):
    N = 400
    p = np.linspace(1.e-3,5,N)
    p_0 = 1.
    uu = np.linspace(-3,3,15)
    c_0 = np.sqrt(gamma*p_0/rho_0)
    beta = (gamma+1.)/(gamma-1.)
    if plot_1:
        for u_0 in uu:
            u_1 = u_0 + (2*c_0)/np.sqrt(2*gamma*(gamma-1.))* \
                (1.-p/p_0)/(np.sqrt(1+beta*p/p_0))
            plt.plot(p,u_1,color='coral')
    if plot_3:
        for u_0 in uu:
            u_1 = u_0 - (2*c_0)/np.sqrt(2*gamma*(gamma-1.))* \
                (1.-p/p_0)/(np.sqrt(1+beta*p/p_0))
            plt.plot(p,u_1,color='maroon')
    plt.xlabel('p'); plt.ylabel('u')
    plt.show()
interact(plot_hugoniot_loci,
         gamma=widgets.FloatSlider(min=1.1,max=3,value=1.4));
```

### Entropy condition

## Exact solution of the Riemann problem
Executing the cell below loads some subroutines that find the exact solution of the Riemann problem.  In brief, the Riemann solution is found as follows:

1. Define a piecewise function giving the middle state velocity $u_m$ that can be connected to the left state by an entropy-satisfying shock or rarefaction, as a function of the middle-state pressure $p_m$.
2. Define a piecewise function giving the middle state velocity $u_m$ that can be connected to the right state by an entropy-satisfying shock or rarefaction, as a function of the middle-state pressure $p_m$.
3. Use an iterative solver to find the intersection of the two functions defined above.
4. Use the Riemann invariants to find the intermediate state densities and the solution structure inside any rarefaction waves.

Execute the cell below (after removing `#`) to bring the Euler solver into the notebook with syntax highlighting, or you can examine it by looking at this file: [exact_solvers/Euler.py](exact_solvers/Euler.py)

```python
#%load exact_solvers/Euler.py
```

### Examples of Riemann solutions

### Problem 1: Sod shock tube

First we consider the classic shock tube problem, with high density and pressure on the left, low density and pressure on the right.  Both sides are initially at rest.  The solution includes a rarefaction, a contact, and a shock.

```python
def riemann_solution(left_state, right_state):
    q_left  = Euler.primitive_to_conservative(*left_state)
    q_right = Euler.primitive_to_conservative(*right_state)

    ex_states, ex_speeds, reval, wave_types = Euler.exact_riemann_solution(q_left ,q_right, gamma)

    plot_function = riemann_tools.make_plot_function(ex_states, ex_speeds, reval, wave_types,
                                                     layout='vertical',
                                                     variable_names=Euler.primitive_variables,
                                                     plot_chars=[Euler.lambda1,Euler.lambda2,Euler.lambda3],
                                                     derived_variables=Euler.cons_to_prim)

    interact(plot_function, t=widgets.FloatSlider(value=0.1,min=0,max=.9),
             which_char=widgets.Dropdown(options=[None,1,2,3],description='Show characteristics'))
```

```python
left_state  = Primitive_State(Density = 3.,
                              Velocity = 0.,
                              Pressure = 3.)
right_state = Primitive_State(Density = 1.,
                              Velocity = 0.,
                              Pressure = 1.)

riemann_solution(left_state,right_state)
```

Here is a plot of the solution in the phase plane, showing the integral curve connecting the left and middle states, and the Hugoniot locus connecting the middle and right states.

```python
Euler.phase_plane_plot(left_state, right_state)
```

### Problem 2: Symmetric expansion

Next we consider the case of equal densities and pressures, and equal and opposite velocities, with the initial states moving away from each other.  The result is two rarefaction waves (the contact has zero strength).

```python
left_state  = Primitive_State(Density = 1.,
                              Velocity = -3.,
                              Pressure = 1.)
right_state = Primitive_State(Density = 1.,
                              Velocity = 3.,
                              Pressure = 1.)

riemann_solution(left_state,right_state);
```

```python
Euler.phase_plane_plot(left_state, right_state)
```

### Problem 3: Colliding flows

Next, consider the case in which the left and right states are moving toward eachother.  This leads to a pair of shocks, with a high-density, high-pressure state in between.

```python
left_state  = Primitive_State(Density = 1.,
                              Velocity = 3.,
                              Pressure = 1.)
right_state = Primitive_State(Density = 1.,
                              Velocity = -3.,
                              Pressure = 1.)

riemann_solution(left_state,right_state)
```

```python
Euler.phase_plane_plot(left_state, right_state)
```

## Plot particle trajectories

In the next plot of the Riemann solution in the $x$-$t$ plane, we also plot the trajectories of a set of particles initially distributed along the $x$ axis at $t=0$, with the spacing inversely proportional to the density.  The evolution of the distance between particles gives an indication of how the density changes.

```python
left_state  = Primitive_State(Density = 3.,
                              Velocity = 0.,
                              Pressure = 3.)
right_state = Primitive_State(Density = 1.,
                              Velocity = 0.,
                              Pressure = 1.)

q_left  = Euler.primitive_to_conservative(*left_state)
q_right = Euler.primitive_to_conservative(*right_state)

ex_states, ex_speeds, reval, wave_types = Euler.exact_riemann_solution(q_left ,q_right, gamma)

def reval_rho_u(x):
    q = reval(x)
    rho = q[0]
    u = q[1]/q[0]
    rho_u = np.vstack((rho,u))
    return rho_u

# Specify density of trajectories to left and right:
rho_l = q_left[0] / 10.
rho_r = q_right[0] / 10.
x_traj, t_traj, xmax = riemann_tools.compute_riemann_trajectories(ex_states, ex_speeds, reval_rho_u, wave_types,
                              i_vel=1, rho_left=rho_l, rho_right=rho_r)
riemann_tools.plot_riemann_trajectories(x_traj, t_traj, ex_speeds, wave_types)

```

Recall that the evolution of the distance between particles gives an indication of how the density changes.  Note that it increases across the shock wave and decreases through the rarefaction wave, and that in general there is a jump in density across the contact discontinuity.

## Plot Riemann solution with advected colors

The next cell defines a function to plot the Riemann solution with the density plot also showing an advected color to help visualize the flow better. The fluid to the left of $x=0$ initially is colored red and to the right of $x=0$ is colored blue, with stripes of different shades of these colors to help visualize the motion of the fluids.

```python
def plot_exact_riemann_solution_stripes(rho_l=3.,u_l=0.,p_l=3.,rho_r=1.,u_r=0.,p_r=1.,t=0.4):
    q_l = Euler.primitive_to_conservative(rho_l,u_l,p_l)
    q_r = Euler.primitive_to_conservative(rho_r,u_r,p_r)

    from matplotlib.mlab import find

    x = np.linspace(-1.,1.,1000)
    states, speeds, reval, wave_types = Euler.exact_riemann_solution(q_l, q_r, gamma=gamma)
    q = reval(x/t)
    primitive = Euler.conservative_to_primitive(q[0],q[1],q[2])

    # compute particle trajectories:
    def reval_rho_u(x):
        q = reval(x)
        rho = q[0]
        u = q[1]/q[0]
        rho_u = np.vstack((rho,u))
        return rho_u

    # Specify density of trajectories to left and right:
    num_left = 10
    num_right = 10
    rho_left = q_l[0] / 10.
    rho_right = q_r[0] / 10.
    x_traj, t_traj, xmax = riemann_tools.compute_riemann_trajectories(states, speeds, reval_rho_u, wave_types,
                                  i_vel=1, xmax=1, rho_left=rho_left, rho_right=rho_right)

    fig = plt.figure(figsize=(18,6))
    names = ['Density','Velocity','Pressure']
    axes = [0]*3
    for i in range(3):
        axes[i] = fig.add_subplot(1,3,i+1)
        q = primitive[i]
        plt.plot(x,q,linewidth=3)
        plt.title(names[i])
        qmax = max(q)
        qmin = min(q)
        qdiff = qmax - qmin
        axes[i].set_ylim((qmin-0.1*qdiff,qmax+0.1*qdiff))
        axes[i].set_xlim(-xmax,xmax)

        if i==0:
            # plot stripes only on density plot
            n = find(t > t_traj)
            if len(n)==0:
                n = 0
            else:
                n = min(n.max(), len(t_traj)-1)

            for i in range(1, x_traj.shape[1]-1):
                j1 = find(x_traj[n,i] > x)
                if len(j1)==0:
                    j1 = 0
                else:
                    j1 = min(j1.max(), len(x)-1)
                j2 = find(x_traj[n,i+1] > x)
                if len(j2)==0:
                    j2 = 0
                else:
                    j2 = min(j2.max(), len(x)-1)

                # set advected color for density plot:
                if x_traj[0,i]<0:
                    # shades of red for fluid starting from x<0
                    if np.mod(i,2)==0:
                        c = [1,0,0]
                    else:
                        c = [1,0.8,0.8]
                else:
                    # shades of blue for fluid starting from x<0
                    if np.mod(i,2)==0:
                        c = [0,0,1]
                    else:
                        c = [0.8,0.8,1]
                plt.fill_between(x[j1:j2],q[j1:j2],0,color=c)
    plt.show()

```

Make a plot with only a time slider to illustrate this viewpoint with the Sod shock tube data:

```python
def plot_exact_riemann_solution_stripes_t_slider(t):
    plot_exact_riemann_solution_stripes(rho_l=3.,u_l=0.,p_l=3.,rho_r=1.,u_r=0.,p_r=1.,t=t)

interact(plot_exact_riemann_solution_stripes_t_slider,
         t=widgets.FloatSlider(min=0.1,max=1.,step=0.1,value=0.5));
```

Note the following in the figure above:

 - The edges of each stripe are being advected with the fluid velocity, so you can visualize how the fluid is moving.
 - The width of each stripe initially is inversely proportional to the density of the fluid, so that the total mass of gas within each stripe is the same.
 - The total mass within each stripe remains constant as the flow evolves, and the width of each stripe remains inversely proportional to the local density.
 - The interface between the red and blue gas moves with the contact discontinuity.  The velocity and pressure are constant but the density can vary across this wave.

## Interactive Riemann solver

Here you can set up your own Riemann problem and immediately see the solution.  If you don't want to download and run the notebook, an online interactive version is [here](http://sagecell.sagemath.org/?z=eJytWNtu47YWfQ-Qf2BnHiLJsmzFGeAgqIsCnfaxOCgGpw-DwJAtOiaOLgwviTJf30VSlKjYSgdog8FEIdfeXPvKLbGat0KRRtf8lRSSNPz6irm1ulC8alXF9hl_NU9mn1fq-uoo2prIA-OvWcsVq9k3Snqho2yrZ3p9dX1V0iOhXXFQO8FoXTTNDltasbaJnnZV-rQTabf9vW1oqtyvx6Kui22e3cX311cEPx8-fPiDKi0aok7U6SJeB1GtXf3D6SZctPuK1uSFqRNhDVOsqIhUhaKSmOPwn8icWvx8geSgiUlyaGuuFS1JoQjsoQS2NiXhLWuUJB2JXk5UUDzUxSvZU1KQ_LP3mRDFazyqnpzBGqglz_SgWiEBpZbzoW0kFc847kkXjQJXkNwDaDZbrSDjwF_FqU11yh-ywSHuAeu7imyNZV_XD25NDyv5wyrc-HXYuO1XRiXC7ohAiV-xSkSgxG8YJW7tI_nF-Y1U9Og8JtjjSTm_IyRUSo3_HJpbGpGN8jLP4iQyxJZknX1KrD0JLEiS29jDxRlcBHABuHDw72IjW40VySkte0IHS6jhmXwSyh2UgOTKkok9RlzACIsRcejNouKnYsJ4Fd1mif2rB-6pGhELixjAgRUnevg_ObaCHIpnBu7IUbfHjjbISxulBblNIpiwAMVQEfmRrO-HJEQQkMLk5pdBFSmpQj7S8gcEtUPuNY_ZzYgXruBMQYbWfSSf6ZE1Ln-hkT4K1NdBI4uldfVJP7YNQ4-o2gPr6fawnYXtcixtSVXU-7IgnNxbW4wVMGIVxjnPlhFfIRJxkkQX3RnHF0_YnJ9gMsacIC6eIP72BG_WDmZpCRtmLfA5cus0JKPamCQkivKls2kA5guTD4lbvXzcZt6c7ztOXDxOxPE0ti7l0OMQXWFDnC9fimdqWmNB5KnFLvJRoCcd0YSHhDQtnp_Yrop4fO8zaMwl5Cv_aQvz7n1avXUn5EY4rSQdkG-TZ0D-W5yF4XzOVcxx3Xw31ymy72YnNo2ld9vSczlvYzUry4r6ZpoSTfavBEVYomRd6dmThaSBeTxlzbFNCaMiJbV8xKnuSo5wUEoiRGNh8-I2S4-6qnbuvtl-EZqmnWorXMB0md_Fns1vcKJUosWZgS9lioZaU3NXyv4A0pjWSk604kO7AosftnnYjv4JPXN2K7brLA-YrgNff8Sli_Aj-LoqSYNudCo4p800zj2pIFGDRvlnIRr4994bVTKnCHf2MxWPNOyVoxgs8fHTMMkH168NvkTwpmEtaSPt_R_c6zts2btvaIJ5tnJtCaVtIcEFPkGLS2jxNhk_-nnHlo0tmvBefJHuyvtGRSujT_Eg9cXNLsrOYUbg3gtgKICMvmCtK0R7GY_ZMznOVp5tEiNPaFwbjdEwGKDxjc5JNHq1m4HC5XiiIDcKrCK3bEt2RBxGR7-53ldzGi0lx-XgYxAept2GlfSh760Tb6y7G6yzc0xvhphaN12ectn01t3NWSfes25Wo154yXO6buowE9EQ5POm1bfE56LSJqnHbt220szNdrreFxLDb9tczj-4rDNd3Mwg93i0JlSskbw40AhXXGr-rde-8oFXI14Bv87-k9RFF0Gu2Muoi-NV8OeLnN5_nbSuTBR4_mnIoOHbUUmeF47L6eHNAcVb0klmHwsv1pmO361U3xbM6BDMGomfHcwMh4eOxX6KW9hx0AptzoTcBBAd7MNFIcQ3H0vHjwc6XwKNeXk1Trpx3y2C2WFQsRnz06vo2FJvpirErApuOOCMxCjJ-5m6V9UjNhYhLGLTT9QTxNjlcAsYPt2P286UYexeGOC2qPupX-k3c7eZD3v5uHfrBV1peMTtiNj0CDFFbEbEnUNshr07L-JfoAxXMmWr57nqWaZ6jqCe46WnrLTnxC9x4vOc-CwnPseJz3HiU07cc-r7RB_c1PktdVSB8Algp7WqxTuyyRLAUjMJdX5yOzIzQfBKZXjCq2aEX5J9o9soX6d3Qy4KVuOSRWVvx1dqt9UUZoDB8s1nexO_3qQ3_6PmHcY-_rd_hb3p4UXn0OuHZNMzwHjE0AjQBJpHGm3CmdKgvzLTOkErK8pyJ_XeWBPl6SZlizxowE_GDs8TQsHIBOusUJc-pWiD9IWV6rTdxFMIXFTRyNoD8VAzWh-Umwb4NFkGaSyzZrpcsuPRvOkbqaVFnRmUSap2rxWro8jsLzGQJVYuNVKL4U_vf_u5CAMyxqKD9B-KcGHUv9mgOdD4twni0KBtOwCfPLszt8n11c925kW3HZNjN_OZydb6Vlbo0QIu_5SayRFCha4U_JfqcXf56c32Ok75u8K25uf2c6NcvK_8PWFAxs9g7vsNn7wlo6jefjcJv9PwyQtvAPZfTcIG-2T1f7W6UjKoTM3BDx4iPEQ4iFVkIGLyTenvb-uhjIF85_OgORKFnhI0BfdhMPySEtQE9A2LQ59Agadd_BeW1ABm&lang=python).

```python
interact(plot_exact_riemann_solution_stripes,
         rho_l=widgets.FloatSlider(min=1.,max=10.,step=0.1,value=3.,description=r'$\rho_l$'),
         u_l=widgets.FloatSlider(min=-10.,max=10.,step=0.1,value=0.,description=r'$u_l$'),
         p_l=widgets.FloatSlider(min=1.,max=10.,step=0.1,value=3.,description=r'$p_l$'),
         rho_r=widgets.FloatSlider(min=1.,max=10.,step=0.1,value=1.,description=r'$\rho_r$'),
         u_r=widgets.FloatSlider(min=-10.,max=10.,step=0.1,value=0.,description=r'$u_r$'),
         p_r=widgets.FloatSlider(min=1.,max=10.,step=0.1,value=1.,description=r'$p_r$'),
         t=widgets.FloatSlider(min=0.1,max=1.,step=0.1,value=0.5));
```

## Riemann problems with vacuum
A vacuum state (with zero pressure and density) can arise in the solution of the Riemann problem in two ways:

1. An initial left or right vacuum state: in this case the Riemann solution consists of a single rarefaction, connecting the non-vacuum state to vacuum.
2. A problem where the left and right states are not vacuum but middle states are vacuum.  Since this means the middle pressure is smaller than that to the left or right, this can occur only if the 1- and 3-waves are both rarefactions.  These rarefactions are precisely those required to connect the left and right states to the middle vacuum state.

### Initial vacuum state
The velocity plot looks a bit strange, but note that the velocity is undefined in vacuum.

```python
left_state  = Primitive_State(Density =0.,
                              Velocity = 0.,
                              Pressure = 0.)
right_state = Primitive_State(Density = 1.,
                              Velocity = -3.,
                              Pressure = 1.)

riemann_solution(left_state,right_state)
```

```python
Euler.phase_plane_plot(left_state, right_state)
```

### Middle vacuum state

```python
left_state  = Primitive_State(Density =1.,
                              Velocity = -10.,
                              Pressure = 1.)
right_state = Primitive_State(Density = 1.,
                              Velocity = 10.,
                              Pressure = 1.)

riemann_solution(left_state,right_state)
```

```python
Euler.phase_plane_plot(left_state, right_state)
```

```python

```


# Tutorial: Creating and pushing calculated signals

How to push calculated signals and scalars using Seeq Data Lab and manipulate worksheet items.

- **Author:** Siang Lim
- **Date:** June 6th 2022

## Background

**Steam savings** is one component in a typical refinery energy dashboard.

In this notebook, we will demonstrate how to use Seeq Data Lab to calculate steam savings and push the results back to the Workbench, using the following tags in the Splitter as a demo:

- **53FFR412** - Stripping Steam/Bottoms Ratio
- **53FC128** - Bottoms to FCC flow

### Calculations
Steam savings is defined relative to a baseline steam flow to bottoms. For this tag, the baseline ratio is $R_b = 7.1$.

$$
\begin{align}
F, \text{Steam Saved [lb/h]}       &= (R_b - R_s) * F_B \\
C, \text{Steam Cost [\$/(klb/h)]}  &= \$13.51 \quad \text{ (from Planning dept or market prices)}\\
S, \text{Savings [\$/day]}         &= F \text{ [lb/h]} \cdot C \text{ [\$/(klb/h)]} \cdot 24 \text{ [h/day]} \cdot 1/1000 \text{ [(klb/lb)]}
\end{align}
$$

Where,

$$
\begin{align}
R_s &= \text{Current steam/bottoms ratio, 53FFR412, [unitless]} \\
F_B &= \text{Bottoms flow, 53FC128, [lb/h]}
\end{align}
$$

### Set up variables

```python
R_b = 7.1
cost = 13.51
```

### Seeq formula
Check out the Seeq training courses if you need a review on Seeq formulas. **Note that:** Seeq variables must start with a letter: `$f128` is valid, `$53ffr412` is not.

**First, a signal:** Steam saved is a `signal`, and will be defined as follows.

```python
steam_saved = f"(({R_b}-$f412)*$f128/24).setUnits('lb/h')"
steam_saved
```

**Aside:** We've used the Python f-string syntax (as indicated by the `f` before the string) to embed expressions inside string. In this case, our expression was `R_b`. For more information on f-strings, see https://realpython.com/python-f-strings/#:~:text=%E2%80%9CF%2Dstrings%20provide%20a%20way,which%20contains%20expressions%20inside%20braces.

**Second, scalar:** Steam cost is a `scalar` (it has a single value), and will be defined as:

```python
steam_cost = f"({cost}).setunits('$/klb/h')"
steam_cost
```

**Third, another signal:** Finally, the savings per day is calculated as:

```python
savings = f"($ss*$co*24*(1/1000)).setunits('$/day').remove(islessthan(0))"
savings
```

## Variable assignment
We'll need to tell Seeq what those variables are:

- `$f412`
- `f128`
- `$ss`
- `$co`

Since `$ss` and `$co` in the savings calculation depends on previously calculated values, we will need to push the calculations to Seeq in 2 separate steps.

We will see how to do this below.

## Step 1 - Import libraries

```python
from seeq import spy
import pandas as pd
pd.set_option('display.max_colwidth', None)
```

## Step 2 - Define tags and data source

Replace `YOUR PI SERVER` with your PI server.

```python
my_items = pd.DataFrame({
    'Name': ['53FFR412', '53FC128'],
    'Datasource Name': 'YOUR PI SERVER'
})

my_items
```

## Step 3 - Search for tags using `spy.search`

```python
metadata_df = spy.search(my_items)
metadata_df
```

## Step 4 - First, push PI tags back to Seeq Workbench

You can tell Seeq which workbook and worksheet you want to push the signal back to. Leave it blank and it will push it to a default SDL workbook called `Data Lab >> Data Lab Analysis` and worksheet called `From Data Lab`.

More information can be found in the SPy.push documentation below using `help(spy.push)`.

```python
help(spy.push)
```

```python
workbook_ID = '7836C665-2B4C-4B36-8262-BE5230E102A5' # Change this to where you want it
worksheet_name = 'Dev 1'
```

```python
push_results = spy.push(
    workbook=workbook_ID,
    worksheet=worksheet_name,
    metadata=metadata_df)

push_results
```

> Navigate to the link above to see the results. You may need to replace the IP address with your Seeq server if it's not pointing to the right place (still trying to figure out how to fix this)

#### WRONG:
- http://`1.2.3.4`/892DF617-6104-4BFF-BCFC-6256FE4DFA7A/workbook/7836C665-2B4C-4B36-8262-BE5230E102A5/worksheet/9F20FD2B-F56E-4D04-8FCB-FF9A661EA39D

#### RIGHT:
- http://`MYSEEQSERVER`/892DF617-6104-4BFF-BCFC-6256FE4DFA7A/workbook/7836C665-2B4C-4B36-8262-BE5230E102A5/worksheet/9F20FD2B-F56E-4D04-8FCB-FF9A661EA39D

## Step 4 - Define calculations
If you have multi-step calculations where a step depends on a previous calculation, you will need to break them down into several calculations.

Here, we will do the steam saved and cost of steam first. In `Formula Parameters`, we are telling Seeq to grab the IDs of `53FFR412` and `53FC128` to assign tags to the variables we've defined.

> Notice that we passed an entire DataFrame row to it, Seeq will automatically parse the row to find the IDs. We could've also passed the `metadata_df` row instead of `push_results`. However, we want to display all the signals (tags + calculated values), which we will see later in this notebook. Using `push_results` makes this step easier to understand (code-wise).

```python
calc_signals = [{'Name': 'Steam Saved',
                'Type': 'Signal',
                'Formula': steam_saved,
                'Formula Parameters': {'$f412': push_results[push_results['Name'] == '53FFR412'],
                                       '$f128': push_results[push_results['Name'] == '53FC128'],
                                      }
               },
               {'Name': 'Cost of Steam',
                'Type': 'Scalar',
                'Formula': steam_cost
               }]
df_calcs = pd.DataFrame(calc_signals)
df_calcs
```

    NameError: name 'steam_saved' is not defined

    Error found at line 3 in cell 1.

    Button(description='Click to show stack trace', layout=Layout(height='auto', width='auto'), style=ButtonStyle(…

## Step 6 - Push first 2 calculations to Seeq
Now we push the first 2 calcs, then store the results as `push_results_2`.

```python
push_results_2 = spy.push(
    workbook=workbook_ID,
    worksheet=worksheet_name,
    metadata=df_calcs)

push_results_2
```

## Step 6 - Push third calculation to Seeq
Notice in `push_results_2`, we now have IDs for the steam saved and steam cost variables. Now we can push the savings signal and then store the results as `push_results_3`.

```python
calc_signals = [{'Name': 'Savings per Day',
                'Type': 'Signal',
                'Formula': savings,
                'Formula Parameters': {'$ss': push_results_2[push_results_2['Name'] == 'Steam Saved'],
                                       '$co': push_results_2[push_results_2['Name'] == 'Cost of Steam'],
                                      }
               }]
df_calcs = pd.DataFrame(calc_signals)
df_calcs
```

```python
push_results_3 = spy.push(
    workbook=workbook_ID,
    worksheet=worksheet_name,
    metadata=df_calcs)

push_results_3
```

## Step 7 - Getting all signals displayed (Method 1)
You may have noticed that every time we pushed a signal back to the worksheet, the earlier pushed signals are no longer displayed in the Workbench. However, they are still available in the 'Recently Accessed' menu.

To make Seeq display ALL signals, we just need to combine all 3 `push_results` DataFrame and push all signals together. There are, of course, other ways to do this, that may be computationally more efficient, but this method seems to be the most straightforward (code-wise, and to understand what's going on for novice users).

Use `pd.concat()` to merge the 3 dataframes

```python
df_combined = pd.concat([push_results, push_results_2, push_results_3]).reset_index(drop=True)
df_combined
```

```python
push_results_final = spy.push(
    workbook=workbook_ID,
    worksheet=worksheet_name,
    metadata=df_combined)

push_results_final
```

> Take a look at the Workbench, you will see that all 5 signals are now displayed.

## Step 8 - Getting all signals displayed (Method 2)

Alternatively, you could also pull the worksheet using `spy.workbooks` and then modify the `display_items`.

```python
workbooks_df = spy.workbooks.search({
    'ID': workbook_ID
})

workbooks_df
```

```python
workbooks = spy.workbooks.pull(workbooks_df)
workbooks
```

Check out the worksheets

```python
workbooks[0].worksheets
```

We see that the worksheet we want is the 3rd one. Let's look at the display items:

```python
worksheet_items = workbooks[0].worksheets[2].display_items
worksheet_items
```

It shows all 5 signals as expected. As long as we know the name and ID of a signal (tag), we can add it to the display. You could also remove a signal by removing a row here.

Let's remove the first 2 signals then push it back.

```python
new_worksheet_items = worksheet_items.loc[2:,:]
new_worksheet_items
```

## Reassign the dataframe

```python
workbooks[0].worksheets[2].display_items = new_worksheet_items
```

## Then push it back to the workbench

```python
spy.workbooks.push(workbooks)
```

> You should see that the first 2 signals are gone now, if you navigate to the URL listed in the table above (last column)


# Helpers

This notebook provides functions for the exercise notebook. It is not necessary to read through.

```python
import matplotlib.pyplot as plt
import numpy.random as nr

from numpy import pi as PI, cos, sin

%matplotlib inline
```

## Simple toy model

```python
binsize = 0.001  # seconds

def link(z):
    return np.exp(z)

def toyModel(X, theta):
    # compute the natural rate, the firing rate and the firing probability
    z = np.dot(theta, X)    # natural rate (a.u.)
    r = link(z) / binsize   # firing rate (Hz)
    p = r * binsize         # instantaneous firing rate

    y = np.random.poisson(p)  # spikes
    return y, p

```

```python
from IPython.display import display, Markdown, Latex

def toygrad():
    return display(Markdown(r'$\begin{align} \frac{\partial}{\partial \theta} \log \mathcal{L} &= \sum_{k=1}^K \sum_{t=1}^T  \bigg( y_{kt} - \frac{\mathrm{e}^{z_{kt}}}{1 + \mathrm{e}^{z_{kt}}} \bigg) \mathbf{x}_{kt} = \mathbf{r}^\top \mathbf{X},  \end{align}$ for residual error $\mathbf{r}_{kT+t} = y_{kt} - \frac{1}{1 + \mathrm{e}^{-z_{kt}}}$ and design matrix with rows $\mathbf{X}_{[kT+t,\dot{}]} = \mathbf{x}_{kt}$. '))

def prgrad():
    return display(Markdown(r'''$\begin{align} \frac{\partial}{\partial \theta} \log p(\theta)  &= - \left[0,\ \beta^\top \Sigma_\theta^{-1}, \ \psi ^\top \Sigma_\psi^{-1}\right]^\top + const. \end{align}$

Note: we can also stick to $\theta$, not breaking it up into $b$, $\beta, \psi$, if we define $P_\theta = \Sigma_\theta^{-1}$ as
$\mathbf{P}_\theta = \begin{pmatrix}
                \mathbf{0} & \mathbf{0} & \mathbf{0}
                \\
                \mathbf{0} & \boldsymbol{\Sigma_\phi}^{-1} & \mathbf{0}
                \\
                \mathbf{0} & \mathbf{0} & \boldsymbol{\Sigma_\psi}^{-1}
              \end{pmatrix}$.
Note the zero precision for bias $b$, for which we did not assume a prior. Then,

$ \frac{\partial}{\partial \theta} \log p(\theta)  = - \theta^\top P_\theta + const. $'''))

def toyll():
    return display(Markdown(r'''```python
def ll(theta):
    z = np.dot(theta, X)
    return np.sum( y * z - link(z) )```'''))

def toydll():
    return display(Markdown(r'''```python
def dll(theta):
    z = np.dot(theta, X)
    r = y - link(z)
    return np.dot(X, r)```'''))

def toymap():
    return display(Markdown(r'''```python
def po(theta):
    return ll(theta) + pr(theta)

def pr(theta):
    beta, psi = theta[1:delta+1], theta[delta+1:]
    lpr  = - .5 * (  np.dot(np.dot(beta,P_beta), beta)
                   + np.dot(np.dot(psi, P_psi ), psi) )
    return lpr```'''))

def toydmap():
    return display(Markdown(r'''```python
def dpo(theta):
    return dll(theta) + dpr(theta)

def dpr(theta):
    beta, psi = theta[1:delta+1], theta[delta+1:]
    return - np.hstack((0., np.dot(beta, P_beta), np.dot(psi, P_psi))) # leading zero for d/db```'''))

def toydme():
    return display(Markdown(r'''```python
def extendedDesignMatrix(y):
    nbins = y.shape[0]
    t = np.arange(nbins) * binsize  # time axis

    X = np.zeros((1 + delta + tau, nbins))

    X[0,:] = 1.       # bias
    if delta > 0:
        X[1, :] = U   # instantaneous input
    for i in range(1,delta):
        X[i+1, i+1:] = U[:-(i+1)]

    for i in range(tau):
        X[delta+i+1,i+1:] = y[:-(i+1)]

    return X```'''))
```

## Dataset for inference with extended model

```python
def createDataset(U, T, K, theta, delta):
    b, beta, psi = theta[0], theta[1:delta+1], theta[delta+1:]
    tau = len(psi)

    nbins = T*K
    X = toyDesignMatrix(U=U, T=T, K=K)
    z = np.dot(np.hstack((b, beta)), X)

    y = np.zeros(T*K, dtype=np.int64)
    eps = np.random.uniform(size=z.shape)
    psi = psi[::-1]
    for k in range(K):
        for t in range(1,tau):
            idx = k*T+t
            n = np.min((tau, t))
            h = y[idx-n : idx]
            y[idx] = np.random.poisson( link(z[idx] + np.inner(h, psi[-n:])) )
        for t in range(tau,T):
            idx = k*T+t
            h = y[idx-tau : idx]
            y[idx] = np.random.poisson( link(z[idx] + np.inner(h, psi)) )

    return y
```


# 13. Gyakorlat - Rezgésgerjesztő
2021.05.05.

## Feladat:

A mellékelt ábrán egy két szabadságfokú rendszer látható, melyet két merev test alkot: egy $m_1$ tömegű, $R$ sugarú tárcsa és egy $m_2$ tömegű test. A tárcsa vízszintes talajon gördül és a tömegközéppontja egy $k_1$ merevségű rugóval van a környezethez rögzítve. A másik test a gravitációs térben van és függőlegesen mozog egy súrlódásmentes megvezetés mentén, miközben a $k_2$ merevségű rugóhoz van rögzítve. A $k_2$ rugó másik vége egy idális kötélhez csatlakozik, ami egy ideális (súrlódásmentes/tömeg nélküli) csigán keresztül a tárcsa tömegközéppontjához van rögzítve. A kötél végig feszített állapotban van.

### Adatok:
|||
|-------------------------------------|-------------------------------------|
| $m_0$ = 0.1 kg                      | $R$ = 0.3 m                         |
| $m_1$ = 1 kg                        | $e$ = 0.01 m                        |
| $m_2$ = 3 kg                        | $M_0$ = 3 Nm                        |
| $k_1$ = 100 N/m                     | $\omega$ = 30 rad/s                 |
| $k_2$ = 200 N/m                     | $\varepsilon$ = $\pi$/6 rad/s$^2$   |

### Részfeladatok:

1. Írja fel a lineáris mátrix együtthatós mozgásegyenletet!
2. Határozza meg a mozgástörvény állandósult állapotbeli tagját!
3. Mekkora a $k_2$ merevségű rugóban ébredő erő legnagyobb értéke az állandósult állapotban?
4. Határozza meg a sajátkörfrekvenciákat és a hozzátartozó sajátvektorokat!

## Megoldás:

## 1. Feladat:
Kis elmozdulások esetén a lineáris mozgásegyenlet mátrixegyütthatós alakja a következő egyenlettel adható meg

$$\mathbf{M}\mathbf{\ddot{q}}+\mathbf{C\dot{q}}+\mathbf{Kq} = \mathbf{Q^*},$$

ahol $\mathbf{q}$ az általános koordináták vektora, $\mathbf{M}$ a tömegmátrix, $\mathbf{C}$ a csillapítási mátrix, $\mathbf{K}$ a merevségi mátrix, a $\mathbf{Q^*}$ pedig az általános erők vektora. (Disszipatív energia nincs a rendszerben ezért a csillapítási mátrix zérus lesz.)
Első lépésként az általános koordinátákat kell meghatározni. A rendszer 2 szabadsági fokú, tehát két általános koordinátát kell definiálni, melyből az egyik az ábra alapján legyen a merev test $y$ irányú elmozdulása a másik pedig a tárcsa $\psi$ szögelfordulása:

$$
\mathbf{q} = \begin{bmatrix}
    q_1\\
    q_2
\end{bmatrix} = \begin{bmatrix}
    y\\
    \psi
\end{bmatrix}.
$$

```python
import sympy as sp
from IPython.display import display, Math

sp.init_printing()
```

```python
## Függvények, szimbólumok definiálása

m0, m1, m2, R, e, k1, k2, M0, ω, ε, g = sp.symbols("m0, m1, m2, R, e, k1, k2, M0, ω, ε, g", real=True)

# Készítsünk behelyettesítési listát az adatok alapján, SI-ben
adatok = [(m0, 0.1), (m1, 1), (m2, 3), (R, 0.2),
          (e, 0.01), (k1, 100), (k2, 200), (M0, 3),
          (ω, 30), (ε, sp.pi/6), (g, 9.81)]

# általános koordináták
t = sp.symbols("t", real=True, positive=True)
y = sp.Function('y')(t)
ψ = sp.Function('ψ')(t)

# gerjesztés
M_t = M0*sp.cos(ω*t+ε)
```

```python
### Kinetikus energia, potenciális energia, disszipatív energia

### Először fejezzük ki a mennyiségeket az általános koordinátákkal
# B pont sebessége
vB = R*ψ.diff(t)

# 1-es test szögsebessége
ω1 = ψ.diff(t)

# C pont sebessége
vC = y.diff(t)

# Tárcsa tehetetlenségi nyomatéka a B pontra
ΘB = sp.Rational(1,2)*m1*R**2

# m0 tömeg sebessége (helyvektor deriváltja)
konst = sp.symbols("konst") # konstans tag (deriválás után kiesik a kifejezésből)
r0 = sp.Matrix([[e*sp.cos(ω*t)+konst],[y + e*sp.sin(ω*t)+konst]])
v0 = r0.diff(t)

# tárcsa x irányú elmozdulása
x = R*ψ

## Kinetikus energia
T = (sp.Rational(1,2)*m1*vB**2 + sp.Rational(1,2)*ΘB*ω1**2 +
     sp.Rational(1,2)*m2*vC**2 + sp.Rational(1,2)*m0*v0.dot(v0)).expand().trigsimp().simplify()

display(Math('T = {}'.format(sp.latex(T))))

## Potenciális energia
U = sp.Rational(1,2)*k1*(x)**2 + sp.Rational(1,2)*k2*(x-y)**2+m0*g*e*sp.sin(ω*t)

display(Math('U = {}'.format(sp.latex(U))))

## Disszipatív energia most nincs!
```

$\displaystyle T = \frac{3 R^{2} m_{1} \left(\frac{d}{d t} ψ{\left(t \right)}\right)^{2}}{4} + \frac{e^{2} m_{0} ω^{2}}{2} + e m_{0} ω \cos{\left(t ω \right)} \frac{d}{d t} y{\left(t \right)} + \frac{m_{0} \left(\frac{d}{d t} y{\left(t \right)}\right)^{2}}{2} + \frac{m_{2} \left(\frac{d}{d t} y{\left(t \right)}\right)^{2}}{2}$

$\displaystyle U = \frac{R^{2} k_{1} ψ^{2}{\left(t \right)}}{2} + e g m_{0} \sin{\left(t ω \right)} + \frac{k_{2} \left(R ψ{\left(t \right)} - y{\left(t \right)}\right)^{2}}{2}$

```python
### Mátrix együtthatók legenerálása
""" A tömegmátrix most nem számítható közvetlenül a kinetikus energiából,
mert az excentrikus tag forgása egy álatlános erő tagot is eredményez,
ami a parciális deriválásnál kiesne az egyenletből.
Ilyen esetben a másodfajú Lagrange-egyenletet kell használni
"""
# Állítsuk elő a Lagrange-egyenletben szereplő deriváltakat
# Ehhez rendezzük listába az általános koordinátákat
q = [y, ψ]
# Majd hozzunk létre egy 2 dimenziós nullvektort a 2 Lagrange egyenlet első két tagjának
Mat = sp.zeros(2,1)
for i in range(2):
    Mat[i] = (T.diff((q[i]).diff(t))).diff(t)-T.diff(q[i])
display(Mat)
```

$\displaystyle \left[\begin{matrix}- e m_{0} ω^{2} \sin{\left(t ω \right)} + m_{0} \frac{d^{2}}{d t^{2}} y{\left(t \right)} + m_{2} \frac{d^{2}}{d t^{2}} y{\left(t \right)}\\\frac{3 R^{2} m_{1} \frac{d^{2}}{d t^{2}} ψ{\left(t \right)}}{2}\end{matrix}\right]$

Ebből a kétdimenziós rendszerből már könnyen kifejezhető a tömegmátrix és az általános erővektor tagja is, mivel erre az kifejezésre az alábbi írható fel (Lagrange alapján)

$$
\left[\begin{matrix}- e m_{0} ω^{2} \sin{\left(t ω \right)} + m_{0} \frac{d^{2}}{d t^{2}} y{\left(t \right)} + m_{2} \frac{d^{2}}{d t^{2}} y{\left(t \right)}\\\frac{3 R^{2} m_{1} \frac{d^{2}}{d t^{2}} ψ{\left(t \right)}}{2}\end{matrix}\right] = \mathbf{M\ddot{q}}-\mathbf{Q}^{m_0}(t)
$$

Tehát a tömegmátrix az általános erővektor második időszerinti deriváltjának az együttható mátrixa, míg az excentrikus forgómozgásból származó általános erő tag az inhomogenitást okozó tag.

```python
# nullmátrix létrehozása a tömegmátrixnak és az erővektornak
M = sp.zeros(2)
Q = sp.zeros(2,1)

# általános koordináták második deriváltja
ddq = sp.Matrix([y.diff(t,2), ψ.diff(t,2)])

for i in range(2):
    for j in range(2):
        M[i,j] = Mat[i].expand().coeff(ddq[j])
Q_m0 = (M*ddq).expand()-Mat.expand()

display(Math('Q^{{m_0}} = {}'.format(sp.latex(Q_m0))))
display(Math('M = {}'.format(sp.latex(M))))
```

$\displaystyle Q^{m_0} = \left[\begin{matrix}e m_{0} ω^{2} \sin{\left(t ω \right)}\\0\end{matrix}\right]$

$\displaystyle M = \left[\begin{matrix}m_{0} + m_{2} & 0\\0 & \frac{3 R^{2} m_{1}}{2}\end{matrix}\right]$

```python
## Merevségi mátrix már közvetlenül kapható a potenciális energiából
# nullmátrix létrehozása a merevségi mátrixnak
K = sp.zeros(2,2)
# nullmátrix feltöltése a megfelelő parciális derivált értékekkel
for i in range(2):
    for j in range(2):
        K[i,j] = U.expand().diff(q[i]).diff(q[j])

display(Math('K = {}'.format(sp.latex(K))))
```

$\displaystyle K = \left[\begin{matrix}k_{2} & - R k_{2}\\- R k_{2} & R^{2} k_{1} + R^{2} k_{2}\end{matrix}\right]$

```python
### Az általános erővektor másik tagja a külső erők teljesítményéből számítható
# Ebben a feladatban csak az M(t) nyomaték működik külső erőként, ennek teljesítménye pedig a következő:
P = -M_t*ψ.diff(t)

"""Ebből a külső erők vektora kapható ha vesszük az általános koordináták
deriváltjainak az együtthatóit a megfelelő helyen"""
Q_M = sp.zeros(2,1)
for i in range(2):
    Q_M[i] = P.expand().coeff(q[i].diff(t))
Q_M
```

$\displaystyle \left[\begin{matrix}0\\- M_{0} \cos{\left(t ω + ε \right)}\end{matrix}\right]$

```python
## Az általános erő a két erő tag összegéből kapható
Q = Q_M+Q_m0
display(Math('Q = {}'.format(sp.latex(Q))))

"""Az általános erő szétszedhető sin-os és cos-os tagokra,
(ez a sajátkörfrekvencia számolásnál egy fontos lépés lesz).
Ehhez először használjuk a trig_expand() parancsot, hogy kibontsuk a cos-os tagot"""
Q[1] = sp.expand_trig(Q[1])
display(Math('Q = {}'.format(sp.latex(Q))))

# Majd szedjuk ki a sin(tω) és cos(tω) együtthatóit
Fc = sp.zeros(2,1)
Fs = sp.zeros(2,1)
for i in range(2):
    Fc[i] = Q[i].expand().coeff(sp.cos(ω*t))
    Fs[i] = Q[i].expand().coeff(sp.sin(ω*t))

display(Math('F_s = {}'.format(sp.latex(Fs))))
display(Math('F_c = {}'.format(sp.latex(Fc))))
```

$\displaystyle Q = \left[\begin{matrix}e m_{0} ω^{2} \sin{\left(t ω \right)}\\- M_{0} \cos{\left(t ω + ε \right)}\end{matrix}\right]$

$\displaystyle Q = \left[\begin{matrix}e m_{0} ω^{2} \sin{\left(t ω \right)}\\- M_{0} \left(- \sin{\left(ε \right)} \sin{\left(t ω \right)} + \cos{\left(ε \right)} \cos{\left(t ω \right)}\right)\end{matrix}\right]$

$\displaystyle F_s = \left[\begin{matrix}e m_{0} ω^{2}\\M_{0} \sin{\left(ε \right)}\end{matrix}\right]$

$\displaystyle F_c = \left[\begin{matrix}0\\- M_{0} \cos{\left(ε \right)}\end{matrix}\right]$

Ezzel a mozgásegyenlet

$$\mathbf{M}\mathbf{\ddot{q}}+\mathbf{Kq} = F_s\sin(\omega t)+F_c\cos(\omega t).$$

## 2. Feladat
A harmonikus gerjesztés miatt a partikuláris megoldást harmonikus próbafüggvény segaítségével keressük:

$$
\mathbf{q}(t) = \mathbf{L}\cos(\omega t)+\mathbf{N}\sin(\omega t).
$$

Ennek a deriváltjai:

$$
\mathbf{\dot{q}}(t) = -\omega\mathbf{L}\sin(\omega t)+\omega\mathbf{N}\cos(\omega t),
$$

$$
\mathbf{\ddot{q}}(t) = -\omega^2\mathbf{L}\cos(\omega t)-\omega^2\mathbf{N}\sin(\omega t).
$$

Visszaírva a próbafüggvényt és a deriváltjait a mozgásegyenletbe, majd a $\sin(\omega t)$ és $\cos(\omega t)$ együtthatókat összegyűjtve adódik az egyenletrendszer $\mathbf{L}$-re és $\mathbf{N}$ -re:

$$
\begin{bmatrix}
    -\omega^2\mathbf{M}+ \mathbf{K} & \mathbf{0}\\
    \mathbf{0} & -\omega^2\mathbf{M}+ \mathbf{K}
\end{bmatrix} \begin{bmatrix}
    \mathbf{L}\\
    \mathbf{N}
\end{bmatrix} = \begin{bmatrix}
    \mathbf{F}_c\\
    \mathbf{F}_s
\end{bmatrix}.
$$

```python
### Oldjuk meg az egyenletrendszert
# Hozzunk létre szimbolikusan vektorokat a megoldásnak
L1, L2, N1, N2 = sp.symbols("L1, L2, N1, N2")
L = sp.Matrix([[L1],[L2]])
N = sp.Matrix([[N1],[N2]])

# Megoldás
L_sol = sp.solve(((-ω**2*M+K)*L-Fc).subs(adatok))
N_sol = sp.solve(((-ω**2*M+K)*N-Fs).subs(adatok))
L[0] = L_sol[L1].evalf(4)
L[1] = L_sol[L2].evalf(4)
N[0] = N_sol[N1].evalf(4)
N[1] = N_sol[N2].evalf(4)

# írjuk be a partikuláris megoldásba az eredményeket
q_p = (L*sp.cos(ω*t)+N*sp.sin(ω*t)).expand().subs(adatok)
display(Math('\mathbf{{q}}_p = {}'.format(sp.latex(q_p))))
```

$\displaystyle \mathbf{q}_p = \left[\begin{matrix}0.0002071 \sin{\left(30 t \right)} - 0.0009696 \cos{\left(30 t \right)}\\- 0.03591 \sin{\left(30 t \right)} + 0.06278 \cos{\left(30 t \right)}\end{matrix}\right]$

## 3. Feladat

```python
## A rugerő maximumánál figyelembe kell venni a statikus és dinamikus részt is
# Statikus deformációból adódó rész:
Fk2_st =  ((m0+m2)*g).subs(adatok).evalf(4)
display(Math('F_\\mathrm{{k2,st}} = {}\\ \mathrm{{N}}'.format(sp.latex(Fk2_st))))

# A dinamikus rész numerikusan könnyen számítható
import numpy as np
t_val = np.linspace(0,0.5,1000) # lista létrehozása a [0 ; 0,5] intervallum 1000 részre való bontásával
Fk2_din = np.zeros(len(t_val)) # nulla lista létrehozása (ugyanannyi elemszámmal)
# dinamikus tag számítása adott időpillanatban
for i in range(len(t_val)):
    Fk2_din[i] = (k2*(R*q_p[1]-q_p[0])).subs(adatok).subs(t,t_val[i]).evalf()
Fk2_din_max = max(Fk2_din).round(2)

# Dinamikus tag
display(Math('F_\\mathrm{{k2,din,max}} = {}\\ \mathrm{{N}}'.format(sp.latex(Fk2_din_max))))

# Az erő maximuma
Fk2_max = (Fk2_din_max + Fk2_st).evalf(4)
display(Math('F_\\mathrm{{k2,max}} = {}\\ \mathrm{{N}}'.format(sp.latex(Fk2_max))))
```

$\displaystyle F_\mathrm{k2,st} = 30.41\ \mathrm{N}$

$\displaystyle F_\mathrm{k2,din,max} = 3.08\ \mathrm{N}$

$\displaystyle F_\mathrm{k2,max} = 33.49\ \mathrm{N}$

## 4. Feladat

```python
## A sajátfrekvenciák a frekvencia egyenletből kaphatók
ω_n2, ω_n = sp.symbols("ω_n2, ω_n")
# oldjuk meg az egyenletet `ω_n^2`-re, majd vonjunk gyököt
ω_n2_val = sp.solve((-ω_n2*M+K).subs(adatok).det())
ω_n = [(sp.sqrt(i)) for i in ω_n2_val]

display(Math('ω_{{n,1}} = {}\\ \mathrm{{rad/s}}'.format(sp.latex(ω_n[0].evalf(3)))))
display(Math('ω_{{n,2}} = {}\\ \mathrm{{rad/s}}'.format(sp.latex(ω_n[1].evalf(4)))))
```

$\displaystyle ω_{n,1} = 4.17\ \mathrm{rad/s}$

$\displaystyle ω_{n,2} = 15.72\ \mathrm{rad/s}$

```python
## lengéskép vektorok meghatározása
# Hozzunk létre a lengésképvektoroknak egy üres listát, majd töltsük fel 2 lengésképvektorral, melyek első elemme 1
A = []
A2 = sp.symbols("A2")
for i in range(2):
    A.append(sp.Matrix([[1],[A2]]))

    # oldjuk meg az egyenletet a lengésképekre és írjuk be a megoldásokat a lengésképvektorba (2. koordináta)
    A[i][1] = sp.solve((((-ω_n[i]**2*M+K)*A[i]).subs(adatok))[0])[0]

display(Math('A_{{1}} = {}\\begin{{bmatrix}}\\mathrm{{m}} \\\\ \\mathrm{{rad}}\\end{{bmatrix}} '.format(sp.latex(A[0].evalf(3)))))
display(Math('A_{{2}} = {}\\begin{{bmatrix}}\\mathrm{{m}} \\\\ \\mathrm{{rad}}\\end{{bmatrix}} '.format(sp.latex(A[1].evalf(4)))))
```

$\displaystyle A_{1} = \left[\begin{matrix}1.0\\3.65\end{matrix}\right]\begin{bmatrix}\mathrm{m} \\ \mathrm{rad}\end{bmatrix} $

$\displaystyle A_{2} = \left[\begin{matrix}1.0\\-14.15\end{matrix}\right]\begin{bmatrix}\mathrm{m} \\ \mathrm{rad}\end{bmatrix} $

Készítette:

       Juhos-Kiss Álmos (Alkalmazott Mechanika Szakosztály)
       Bachrathy Dániel (BME MM) kidolgozása alapján.

        Hibák, javaslatok:
        amsz.bme@gmail.com
        csuzdi02@gmail.com
        almosjuhoskiss@gmail.com

            2021.05.05.


# Gym Intro + Monte Carlo Learning

Originally from https://skettee.github.io/post/monte_carlo_learning/ (in Korean)

## Load Libraries and Extensions

```python
%reload_ext autoreload
%autoreload 2
%matplotlib inline
```

```python
from IPython.display import display, clear_output, Pretty
import numpy as np
from time import sleep
from tqdm import tqdm_notebook as tqdm

import gym
```

## Frozen Lake Environment

```python
ENV_NAME = 'FrozenLake-v0'
N_STEP = 100
```

```python
env = gym.make(ENV_NAME, is_slippery=False)
state = env.reset()
```

```python
world = env.render(mode='ansi')
Pretty(world)
```

    SFFF
    FHFH
    FFFH
    HFFG

```python
for step in range(N_STEP):
    action = env.action_space.sample()
    next_state, reward, done, info = env.step(action)
    state = next_state

    # updated world display
    world = env.render(mode='ansi')
    clear_output(wait=True)
    display(Pretty(world))
    sleep(0.5)

    if done: # an episode finished
        print("Episode finished after {} timesteps".format(step + 1))
        break
```

      (Right)
    SFFF
    FHFH
    FFFH
    HFFG

    Episode finished after 2 timesteps

```python
env.action_space
```

    Discrete(4)

There are 4 actions in Fronzen Lake.

$A = \{0, 1, 2, 3\}$

Num	| Action
----|----
0 |	Move Left
1 |	Move Down
2 |	Move Right
3 |	Move Up

```python
env.observation_space
```

    Discrete(16)

There are 16 states as follows:

$S = \{0, 1, \cdots , 15\}$

$\begin{vmatrix}
0 & 1 & 2 & 3 \\
4 & 5 & 6 & 7 \\
8 & 9 & 10 & 11 \\
12 & 13 & 14 & 15
\end{vmatrix}$

For each state, there are actions possible associated with next state, reward, and done as follows:

`{action: [(probability, nextstate, reward, done)]}`

For example, let's look at State 6 and 14:

```python
env.P[6]
```

    {0: [(1.0, 5, 0.0, True)],
     1: [(1.0, 10, 0.0, False)],
     2: [(1.0, 7, 0.0, True)],
     3: [(1.0, 2, 0.0, False)]}

```python
env.P[14]
```

    {0: [(1.0, 13, 0.0, False)],
     1: [(1.0, 14, 0.0, False)],
     2: [(1.0, 15, 1.0, True)],
     3: [(1.0, 10, 0.0, False)]}

## Monte Carlo Learning

$G_t = R_{t+1} + \gamma R_{t+2} + \cdots + = \sum_{k=0}^{\infty} \gamma^{k} R_{t+k+1}$

$q_{\pi}(s,a) = \mathbb E_{\pi} [G_t | S_t = s, A_t = a]$

$q_*(s,a) = \max_{\pi} q_{\pi}(s,a)$

$ \pi_*(s, a) = \begin{cases}
1 & \text{if } a= \text{argmax}_{a \in A} q_\star(s,a) \\
0 & \text{otherwise}
\end{cases} $

$\begin{align}
N(S_t, A_t) & \leftarrow N(S_t, A_t) + 1 \\
Q(S_t, A_t) & \leftarrow Q(S_t, A_t) + \dfrac{1}{N(S_t, A_t)} \left( G_t - Q(S_t, A_t) \right)
\end{align}$

```python
n_state = env.observation_space.n
n_action = env.action_space.n
n_episode = 5000
GAMMA = .6
```

```python
Q_table = np.zeros((n_state, n_action))
N_table = np.zeros((n_state, n_action))
R_table = np.zeros((n_state, n_action))

for episode in tqdm(range(n_episode)):
    memory = []
    state = env.reset()

    for step in range(N_STEP):
        action = env.action_space.sample()
        memory.append((state, action))

        next_state, reward, done, info = env.step(action)
        R_table[state][action] = reward
        state = next_state

        if done:
            for i in range(len(memory)):
                G_t = 0
                gamma = GAMMA
                for j in range(i, len(memory)):
                    S_t, A_t = memory[j]
                    if i == j:
                        G_t += R_table[S_t][A_t]
                    else:
                        G_t += gamma * R_table[S_t][A_t]
                        gamma = gamma * gamma
                S_t, A_t = memory[i]
                N_table[S_t][A_t] += 1
                Q_table[S_t][A_t] += (G_t - Q_table[S_t][A_t]) / N_table[S_t][A_t]
            break
```

    HBox(children=(IntProgress(value=0, max=5000), HTML(value='')))

## Solution

```python
state = env.reset()
done = False

world = env.render(mode='ansi')
display(Pretty(world))
sleep(.5)
```

    SFFF
    FHFH
    FFFH
    HFFG

```python
while not done:
    action = np.argmax(Q_table[state])
    state, reward, done, info = env.step(action)

    world = env.render(mode='ansi')
    clear_output(wait=True)
    display(Pretty(world))
    sleep(.5)

    if done and state == 15:
        print('\nSuccess!')
```

      (Right)
    SFFF
    FHFH
    FFFH
    HFFG

    Success!

```python

```


Finally, we write the code file itself.  Most of the text below will appear literally in the file.  Statements in single curly braces will be substituted by the `format` statement at the end of the string; double curly braces will just appear as literal braces in the output.

In the following, the ODE system directly integrates the quaternion rotors describing the spin directions and orbital angular velocity vector.  This allows us to reduce the number of variables in the system and automatically satisfy certain constraints, which should lead to better numerical behavior.

Note that in this formulation, `Phi` is a completely unnecessary variable.  It doesn't need to be computed because it is implict in the definition of the frame.  It is calculated here simply because it is easy to do, and might be handy to have around.  Of course, it could be derived from knowledge of the frame alone.

```python
# Always run this first
# NOTE: Do not define new basic variables in this notebook;
#       define them in Variables_Q.ipynb.  Use this notebook
#       to define new expressions built from those variables.

from __future__ import division # This needs to be here, even though it's in Variables.py
import sys
sys.path.insert(0, '..') # Look for modules in directory above this one
execfile('../Utilities/ExecNotebook.ipy')
UseQuaternions = True
from sympy import N
from sympy import Rational as frac # Rename for similarity to latex
```

```python
with open('PNApproximants_Q.ipp', 'w') as f :
    f.write("""// File produced automatically by OrbitalEvolutionCodeGen_Q.ipynb""")

    for PNOrbitalEvolutionOrder in [frac(n,2) for n in range(0,13)]:
        print("Working on {0} PN...".format(PNOrbitalEvolutionOrder))
        PNOrbitalEvolutionOrderString = str(N(PNOrbitalEvolutionOrder,2)).replace('.','p')
        %cd -q ../PNTerms
        execnotebook('OrbitalEvolution.ipynb')
        ExpressionsForMemberFunctions = CodeOutput.CodeConstructor(PNVariables, PrecessionVelocities)
        execnotebook('AngularMomentum.ipynb')
        %cd -q -
        PrecessionVelocities.AddDerivedVariable('OrbitalAngularMomentum',
            AngularMomentumExpression(PNOrder=PNOrbitalEvolutionOrder),
            datatype=ellHat.datatype)
        CodeConstructor.AddDependencies(PrecessionVelocities)
        # Start the class, write the declarations, initializations, etc.
        f.write("""

class TaylorTn_{PNOrbitalEvolutionOrderString}PN_Q : public TaylorTn_Q {{
private:
{Declarations}
  double Phi;
  const bool EvolveSpin1, EvolveSpin2;

public:
  TaylorTn_{PNOrbitalEvolutionOrderString}PN_Q({InputArguments}) :
{Initializations},
    Phi(0.0), EvolveSpin1(S_chi1.normsquared()>1e-12), EvolveSpin2(S_chi2.normsquared()>1e-12)
  {{ }}

  void Recalculate(double t, const double* y) {{
    v = y[0];
    rfrak_chi1_x = y[1];
    rfrak_chi1_y = y[2];
    rfrak_chi2_x = y[3];
    rfrak_chi2_y = y[4];
    rfrak_frame_x = y[5];
    rfrak_frame_y = y[6];
    rfrak_frame_z = y[7];
    Phi = y[8];

{Evaluations}
  }}

{MemberFunctions}
""".format(PNOrbitalEvolutionOrderString=PNOrbitalEvolutionOrderString,
           InputArguments=CodeConstructor.CppInputArguments(11),
           Declarations=CodeConstructor.CppDeclarations(2),
           Initializations=CodeConstructor.CppInitializations(4),
           Evaluations=CodeConstructor.CppEvaluations(4),
           MemberFunctions=CodeOutput.CodeConstructor(PNVariables, PrecessionVelocities).CppExpressionsAsFunctions(2)))

        # Write the external interfaces to the ODE stepper
        for n,Expressions in zip([1,4,5], [T1Expressions,T4Expressions,T5Expressions]) :
            f.write("""
  int TaylorT{n}(double t, const double* y, double* dydt) {{
    Recalculate(t, y);
    if(v>=1.0) {{ return GSL_EDOM; }} // Beyond domain of PN validity
{Computations}
    if(dvdt_T{n}<1.0e-12) {{ return GSL_EDIVERGE; }} // v is decreasing
    return CommonRHS(dvdt_T{n}, y, dydt);
  }}
""".format(n=n,
           Computations=CodeOutput.CodeConstructor(PNVariables, Expressions).CppEvaluateExpressions() ))

        # Finish up, writing the common RHS function and closing the class
        f.write("""
  int CommonRHS(const double dvdt, const double* y, double* dydt) {{
    std::vector<double> rfrak_frame(3);
    rfrak_frame[0] = y[5];
    rfrak_frame[1] = y[6];
    rfrak_frame[2] = y[7];
    const std::vector<double> rfrakdot_frame = FrameFromAngularVelocity_Integrand(rfrak_frame, OmegaVec().vec());
    dydt[0] = dvdt;
    if(EvolveSpin1) {{
      FrameFromAngularVelocity_2D_Integrand(y[1], y[2],
                                            (S_chi1.inverse()*OmegaVec_chiVec_1()*S_chi1).vec(),
                                            dydt[1], dydt[2]);
    }} else {{
      dydt[1] = 0.0;
      dydt[2] = 0.0;
    }}
    if(EvolveSpin2) {{
      FrameFromAngularVelocity_2D_Integrand(y[3], y[4],
                                            (S_chi2.inverse()*OmegaVec_chiVec_2()*S_chi2).vec(),
                                            dydt[3], dydt[4]);
    }} else {{
      dydt[3] = 0.0;
      dydt[4] = 0.0;
    }}
    dydt[5] = rfrakdot_frame[0];
    dydt[6] = rfrakdot_frame[1];
    dydt[7] = rfrakdot_frame[2];
    dydt[8] = v*v*v/M;

    return GSL_SUCCESS; // GSL expects this if everything went well
  }}
}}; // class TaylorTn_{PNOrbitalEvolutionOrderString}PN_Q : public TaylorTn_Q
""".format(PNOrbitalEvolutionOrderString=PNOrbitalEvolutionOrderString))

print("All done")
```

    Working on 0 PN...
    WARNING: Absorption term in EnergyAbsorption.ipynb temporarily disabled.
    Working on 1/2 PN...
    WARNING: Absorption term in EnergyAbsorption.ipynb temporarily disabled.
    Working on 1 PN...
    WARNING: Absorption term in EnergyAbsorption.ipynb temporarily disabled.
    Working on 3/2 PN...
    WARNING: Absorption term in EnergyAbsorption.ipynb temporarily disabled.
    Working on 2 PN...
    WARNING: Absorption term in EnergyAbsorption.ipynb temporarily disabled.
    Working on 5/2 PN...
    WARNING: Absorption term in EnergyAbsorption.ipynb temporarily disabled.
    Working on 3 PN...
    WARNING: Absorption term in EnergyAbsorption.ipynb temporarily disabled.
    Working on 7/2 PN...
    WARNING: Absorption term in EnergyAbsorption.ipynb temporarily disabled.
    Working on 4 PN...
    WARNING: Absorption term in EnergyAbsorption.ipynb temporarily disabled.
    Working on 9/2 PN...
    WARNING: Absorption term in EnergyAbsorption.ipynb temporarily disabled.
    Working on 5 PN...
    WARNING: Absorption term in EnergyAbsorption.ipynb temporarily disabled.
    Working on 11/2 PN...
    WARNING: Absorption term in EnergyAbsorption.ipynb temporarily disabled.
    Working on 6 PN...
    WARNING: Absorption term in EnergyAbsorption.ipynb temporarily disabled.
    All done