# SSRM: Sequential Sample Ratio Mistmatch Test
## Michael Lindon (michael.lindon@optimizely.com)

## What is an SRM?

In almost all online controlled experiments (OCEs) that use random designs, the experimenter must specify a traffic allocation for each of the variations in the experiment. If you have never done this, chances are you have been using a default traffic allocation that assigns equal amounts of traffic to all the experiment variations.

A common cause for concern is when the number of visitors assigned to each variation appears somewhat different from what one might expect under the intended traffic allocation. Indeed, this is a signal of an underlying data quality issue or engineering bug in the implementation, which has the potential to invalidate any causal conclusions drawn from the experiment. Loosely speaking, a **Sample Ratio Mistmatch** (SRM) is said to be presented when the observed sample ratios are significantly different from the expected sample ratios. It is widely regarded as a good experimentation practice to check experiments for SRMs. We can formulate this problem statistically to give a precise mathematical definition of an SRM.

## Statistical Model
The simplest example of an A/B test involves two variations, a control and a treatment, and equal traffic allocation to each. To be precise, a new visitor is assigned to the treatment with probability 0.5 upon entering the experiment. Statistically speaking, this assignment is an example of a $\text{Binomial}(1,p)$ random variable. The intended traffic allocation corresponds to the *null hypothesis:* $p = 0.5$.  Detection of a sample ratio mismatch can be formulated as a hypothesis test which tests precisely this null hypothesis.

A more general example is provided when there are multiple variations in an experiment. In this case a user assignment is an example of a $\text{Multinomial}(1,p)$ random variable $x$, which is simply a higher dimensional generalization of the $\text{Binomial}(1,p)$. If there are $d$ variations in the experiment, then $x$ is a vector of length $d$ containing $d-1$ zeros and a single one to indicate the outcome. As an example $x=(0,0,1)$ indicates that the user was assigned to the third variation. Naturally, the parameter $p$ is also a $d$ dimensional vector, with the constraint that the elements are in $[0,1]$ and sum to 1, so that these values correspond to probabilities. The intended traffic allocation to each variation corresponds to a null hypothesis about the parameter $p$. If we expected equal traffic to each variations, then the null and alternative hypotheses are

$$\begin{align}
H_0: x_1, x_2, \dots \sim \text{Multinomial}(1,p)\hspace{1cm}p=p_0,\\
H_1: x_1, x_2, \dots \sim \text{Multinomial}(1,p)\hspace{1cm}p\neq p_0,\\
\end{align}$$
with $p_0=[1/3, 1/3, 1/3]$.

There are Bayesian and frequentist approaches to testing this, each with sequential and non-sequential tests. The ssrm package contains a test developed at Optimizely which is sequential and has both Bayesian and frequentist interpretations.

## SSRM
The wonderful thing about a sequential version of a sample ratio mismatch test is that you don't have to wait for the experiment to finish before it can be executed. The SSRM test detects in real-time, from the very beginning of an experiment, if an SRM is present. This allows bugs to be rapidly detected in the early stages of an experiment and corrected before exposing new visitors to the variations. See the accompanying [white paper](https://arxiv.org/abs/2011.03567) for the underlying mathematical details. Let's start by generating some data to work with.

```python
from scipy.stats import multinomial
import numpy as np

# Set the seed of our random number generator for reproducibility. Don't worry about this
np.random.seed(0)

# Our intended allocation probabilities
p_0 = [0.1, 0.5, 0.4]

# The actual allocation probabilities
p = [0.1, 0.49, 0.41]

# Specify number of visitors
n = 100000

# Generate allocations
data = multinomial.rvs(1, p, size=n)
```

You have just simulated `n` allocations under probabilities that differ from the intended. The null hypothesis is therefore not correct for this dataset, and it would be correct to reject the null hypothesis.

The SSRM API supports both sequential Bayesian and sequential frequentist analyses. There is a tuning parameter for both, but the above example supplies a good default that works for most use-cases.

This tutorial uses`pandas` and `matplotlib` to transform and visualize data. If you do not already have these libraries installed, uncomment the code below to install them now.

```python
import sys
!{sys.executable} -m pip install matplotlib
```

## Frequentist Analysis
To obtain sequential p-values, simply import the `sequential_p_values` function. The probability under the null hypothesis that the sequential p-value falls below $\alpha$ is less than $\alpha$. In other words, if you reject the null and declare an SRM to be present when the sequential p-value is less than $\alpha$, then the probability of reporting a **false positive** is at most $\alpha$.

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

pvals = ssrm_test.sequential_p_values(data, p_0)
```

```python
fig = plt.figure()
plt.plot(pvals)
fig.suptitle("Sequential p-value for Testing SRM", fontsize=20)
plt.xlabel("Visitor Count", fontsize=18)
plt.ylabel("p-value", fontsize=16)
plt.ylim(0, 1)
plt.show()
```

## Bayesian Analysis
One can obtain posterior probabilities (of an SRM) in the following way

```python
posterior_probabilities = ssrm_test.sequential_posterior_probabilities(data, p_0)
```

```python
fig = plt.figure()
plt.plot(posterior_probabilities)
fig.suptitle("Posterior Probability of SRM", fontsize=20)
plt.xlabel("Visitor Count", fontsize=18)
plt.ylabel("Probability", fontsize=16)
plt.ylim(0, 1)
plt.show()
```

## The Advantages of a Sequential Test
Consider the following tweet in which the user identifies an SRM using a non sequential frequentist test.

</img>

We don't have access to this exact dataset, but we can create one just like it. In total there were 1637070 visitors in the experiment and the empirical sample ratio was 50.2%. With this information we can create a similar dataset as follows.

```python
# Set the seed of our random number generator for reproducibility. Don't worry about this
np.random.seed(1)

# Our intended allocation probabilities
p_0 = [0.5, 0.5]

# The actual allocation probabilities
p = [0.502, 0.498]

# Specify number of visitors
n = 1637070

# Generate allocations
data = multinomial.rvs(1, p, size=n)

x = np.sum(data, axis=0)[0]
print(
    "Empirical sample ratios are {}% and {}%".format(
        round(100 * x / n, 1), round(100 * (n - x) / n, 1)
    )
)
```

The null hypothesis is that $p=0.5$. Let's compute the p-value from a two sided likelihood ratio test.

```python
from scipy.stats import binom

print("{} visitors assigned to the control".format(x))
print("The non sequential p-value is {}".format(2 * binom.sf(x, n, 0.5)))
```

The user has been able to identify an SRM at the 0.05-alpha level test by using a standard likelihood ratio test at the end of the experiment. Note that this happened at the **end of the experiment**. Unfortunately, this dataset is now invalid for testing and this large experiment of $1637070$ visitors has gone to waste. One needs to fix the underlying issue and start a new experiment, collecting new data.

Ideally, you wouldn't have to wait until the end of an experiment to test if there is an SRM. This is exactly the main benefit a *sequential* SRM test provides: it can test for an SRM while your experiment is running, after **every single visitor**, without inflating the false positive rate. Let's run the SSRM test on the same dataset.

```python
print("Computing... (this may take some time)")
pvals = ssrm_test.sequential_p_values(data, p_0)
fig = plt.figure()
plt.plot(pvals)
fig.suptitle("Sequential p-value for Testing SRM", fontsize=20)
plt.xlabel("Visitor Count", fontsize=16)
plt.ylabel("p-value", fontsize=16)
plt.ylim(0, 1)
plt.show()
```

```python
sig_reached = len([p for p in pvals if p > 0.05])
print(
    "SSRM rejected the null (no SRM) at the alpha=0.05 level after {} visitors.".format(
        sig_reached
    )
)
print(
    "That is only {}% of the way through the experiment.".format(
        round(100 * sig_reached / n), 3
    )
)
```

As illustrated with the above example, SSRM allowed us to detect an SRM much sooner than the end of the experiment. Larger SRM's, and hence larger errors, are detected even sooner.

## Time Aggregated Data
Sometimes you may only have time aggregated data, instead of visitor-level data. That is, you might only know the total number of visitors assigned to each variation within an interval (or bucket) of time. For 3 variations, such time aggregated data can be represented as $[[100,120,99], [123,112,111], \dots$, where there is one inner array for each time bucket. The remaining steps are identical.

```python
# Set the seed of our random number generator for reproducibility. Don't worry about this
np.random.seed(0)

# Our intended allocation probabilities
p_0 = [0.1, 0.5, 0.4]

# The actual allocation probabilities
p = [0.1, 0.49, 0.41]

# Generate allocations
data = multinomial.rvs(1000, p, size=100)

print("Now the data looks like: ")
print(data[1:10])
```

```python
pvals = ssrm_test.sequential_p_values(data, p_0)
fig = plt.figure()
plt.plot(pvals)
fig.suptitle("Sequential p-value for Testing SRM", fontsize=20)
plt.xlabel("Time Bucket Index", fontsize=16)
plt.ylabel("p-value", fontsize=16)
plt.ylim(0, 1)
plt.show()
```

## Working With Pandas DataFrames
Suppose your data is inside a `pandas` DataFrame. The following cells will show you how to manipulate them into the format expected by the SSRM API. First, if you haven't done so already, install `pandas` by uncommenting out the code below, and let's create a sample DataFrame to work with.

```python
import sys
!{sys.executable} -m pip install pandas

import pandas as pd

df = pd.DataFrame(data)
df.columns = ["variation_a", "variation_b", "variation_c"]
df.head()
```

The dataframe df gives us the total number of visitors assigned to each variation within an interval of time. It's very easy to extract these values using `df.values`. This gives an array of arrays, which can be fed into the ssrm api.

```python
pvals = ssrm_test.sequential_p_values(df.values, p_0)
fig = plt.figure()
plt.plot(pvals)
fig.suptitle("Sequential p-value for Testing SRM", fontsize=20)
plt.xlabel("Time Bucket Index", fontsize=16)
plt.ylabel("p-value", fontsize=16)
plt.ylim(0, 1)
plt.show()
```


# Extreme Learning Machine Tutorial

Sample code just for quickly teaching how to build an ELM network using Numpy and using a quick computation algorithm.

## Introduction

This machine learning algorithm was designed by Huang et al. [1], using a single layer *feed forward* network.
One of their main features is that both weights $\mathbf{W}$ and biases $\mathbf{b}$ are random and they don't require fitting. Thus, their output weights are independent.
The output $\beta$ is obtained using the following operation
$$ \begin{align}
H\beta &= T \\
\beta &= H^{\dagger} T \\
H^{\dagger} &= \left(H^{T} H \right)^{-1} H^{T}\\
\end{align}$$
WHere $H^{\dagger}$ is the **_Moore-Penrose Pseudoinverse_**, and $H$ is a full-rank column matrix

## Moore-Penrose PseudoInverse
This is a generalization form of the inverse matrix, which allows solving least-squared systems which don't have a well-defined solution.

A matrix $G$ of size $m\times n$ has a Pseudoinverse $G^{\dagger}$ of size $n\times m$ which has the following properties:

$$G G^{\dagger} G = G$$
$$G^{\dagger} G G^{\dagger} = G^{\dagger}$$
$$\left( G G^{\dagger} \right)' = G G^{\dagger}$$
$$\left(G^{\dagger} G \right)' = G^{\dagger} G$$

This calculation is computationally complex. Normally, the least-squares method or Eigen-value Decomposition are used (most commonly on `MATLAB` and `SciPy`).

Courrieu [2] proposed a more computationally simple algorithm, based on reverse ordering and Cholesky factorization of a symmetric full-rank positive matrix.

```python
import numpy as np
from time import time
```

```python
#Defining Moore-Penrose PseudoInverse as defined by Courrieu (original in Matlab)
def mp_pinv(G):
    m,n = G.shape #Shape of matrix
    transpose = False

    if m < n:
        transpose = True #If m is bigger we multiply it with its transpose
        A = np.matmul(G,G.T)
        n = m
    else:
        A = np.matmul(G.T,G) #Else, it's backwards

    #Full-rank Choilesky
    L = np.linalg.cholesky(A)

    #This part is doing the same without the NumPy function, I left it here just if you want to test it.
    #dA = np.diag(A)
    #tol = np.min(dA[dA>0])*1e-9
    #L = np.zeros_like(A,dtype=np.float) #Zeros-like matrix like A
    #r = 0
    #for k in range(n):
    #    L[k:n,r] = A[k:n,k]-np.matmul(L[k:n,0:(r)],L[k,0:(r)].T)
    #    #If is higher than tolerance value:
    #    if L[k,r] > tol:
    #        L[k,r]=np.sqrt(L[k,r]) #We get square-root
    #        if k<n:
    #            L[(k+1):n,r]= L[(k+1):n,r]/L[k,r]; #And divide it
    #    else:
    #        r-=1;
    #    r+=1
    #L=L[:,0:r]

    #We compute generalized inverse
    M = np.linalg.inv(np.matmul(L.T,L))
    if transpose:
        #Y = G.T * L * M * M * L.T
        Y = np.matmul(np.matmul(np.matmul(np.matmul(G.T,L),M),M),L.T)
    else:
        #Y = L* M * M *L.T * G.T
        Y = np.matmul(np.matmul(np.matmul(np.matmul(L,M),M),L.T),G.T)

    return Y
```

### Comparing this method with NumPy implementation

```python
matrix = np.random.rand(800).reshape(25,32) #Random Matrix as input

t0 = time()
m01 =np.linalg.pinv(matrix)
print("Metodo SVD numpy: {:.3f} ms".format((time()-t0)*1000))

t0 = time()
m02 = mp_pinv(matrix)
print("\nCourrieu method: {:.3f} ms".format((time()-t0)*1000))
print("\nMean Squared Error between these two: {}".format(np.mean((m01-m02)**2)))
```

    Metodo SVD numpy: 0.719 ms

    Courrieu method: 1.379 ms

    Mean Squared Error between these two: 1.1021139939204808e-28

*_ TODO: compile using numba and do a better comparison..._*

## Implementing ELM

```python
#Loading Breast Cancer
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split

X,Y = load_breast_cancer(return_X_y=True)
X = (X- X.min(axis=0))/(X.max(axis=0)-X.min(axis=0)) #Normalization between 0 - 1

#Splitting Train and test Data
Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,Y,train_size = 0.7, test_size=0.3)
```

```python
#Params
n_train = len(Xtrain)
n_test = len(Xtest)
n_data = Xtrain.shape[-1] #Number of samples
n_hidden = 1000 #Hidden neurons

#Training
true = Ytrain.reshape(n_train,1).T #Outputs
w_i = np.random.rand(n_hidden,n_data)*2 -1 #Input weights
b_i = np.random.rand(n_hidden,1) #Input biases

b_m = b_i * np.ones(n_train) #Bias matrix for all values
temp_H = np.matmul(w_i,Xtrain.T)+b_m # wX+b

#sigmoid function
def sigmoid(x):
    return 1/(1+np.exp(-x))

H = sigmoid(temp_H) #Activation function
# Computing Beta with the Pseudoinverse
H_cross = mp_pinv(H.T)
beta_i = np.matmul(H_cross,true.T) #And multiply it with the output vector
Y = np.matmul(H.T,beta_i) #And our training prediction
```

```python
#Test
b_mt = b_i *np.ones(n_test)
temp_Htest = np.matmul(w_i,Xtest.T) + b_mt # wX + b
H_test = sigmoid(temp_Htest)
testY = np.matmul(H_test.T,beta_i) # Prediction

Ypred = np.where(testY <= 0.5,0,1) #If it's higher than 0.5, then is positive, else not.

from sklearn.metrics import accuracy_score
print("Test Accuracy: {:.2f}%".format(accuracy_score(Ytest,Ypred)*100))
```

    Test Accuracy: 85.96%

# Future Work
* I plan to do a Tensorflow or PyTorch implementation of this same code. If someone wants to use it
* Compiling in order to better compare Courrieu's method, specially without using Cholesky from numpy...

This code is way far from optimal, but can be a simple example just for fun.

## References
1. Huang, G. B., Zhu, Q. Y., & Siew, C. K. (2004, July). Extreme learning machine: a new learning scheme of feedforward neural networks. In Neural Networks, 2004. Proceedings. 2004 IEEE International Joint Conference on (Vol. 2, pp. 985-990). IEEE.
2. Courrieu, P. (2008). Fast computation of Moore-Penrose inverse matrices. arXiv preprint arXiv:0804.4809. Obtained from https://arxiv.org/abs/0804.4809
3. Akusok, A., Björk, K. M., Miche, Y., & Lendasse, A. (2015). High-Performance Extreme Learning Machines: A Complete Toolbox for Big Data Applications. IEEE Access, 3, 1011–1025. https://doi.org/10.1109/ACCESS.2015.2450498

```python

```


```python
import scipy
import sympy
import einsteinpy

from sympy import cos, sin, sinh
from einsteinpy.symbolic import MetricTensor, RicciTensor, RicciScalar, ChristoffelSymbols, RiemannCurvatureTensor, WeylTensor
from einsteinpy.symbolic.predefined import AntiDeSitter

sympy.init_printing()
```

```python
metric = AntiDeSitter()  # Define anti de Sitter metric
metric.tensor()
```

$\displaystyle \left[\begin{matrix}-1 & 0 & 0 & 0\\0 & \cos^{2}{\left(t \right)} & 0 & 0\\0 & 0 & \cos^{2}{\left(t \right)} \sinh^{2}{\left(\chi \right)} & 0\\0 & 0 & 0 & \sin^{2}{\left(\theta \right)} \cos^{2}{\left(t \right)} \sinh^{2}{\left(\chi \right)}\end{matrix}\right]$

```python
Ric = RicciTensor.from_metric(metric) # Compute Ricci Tensor from AdS metric
Ric.tensor()
```

$\displaystyle \left[\begin{matrix}3 & 0 & 0 & 0\\0 & - 3 \cos^{2}{\left(t \right)} & 0 & 0\\0 & 0 & \left(\sin^{2}{\left(t \right)} - 1\right) \sinh^{2}{\left(\chi \right)} - 2 \cos^{2}{\left(t \right)} \sinh^{2}{\left(\chi \right)} & 0\\0 & 0 & 0 & \left(\sin^{2}{\left(t \right)} - 1\right) \sin^{2}{\left(\theta \right)} \sinh^{2}{\left(\chi \right)} - 2 \sin^{2}{\left(\theta \right)} \cos^{2}{\left(t \right)} \sinh^{2}{\left(\chi \right)}\end{matrix}\right]$

```python
R = RicciScalar.from_riccitensor(Ric) # Compute Ricci Scalar from AdS metric
R.simplify()
R.expr
```

```python
ch = ChristoffelSymbols.from_metric(metric) # Compute Christoffel Symbols from AdS metric
ch.tensor()
```

$\displaystyle \left[\begin{matrix}\left[\begin{matrix}0 & 0 & 0 & 0\\0 & - \sin{\left(t \right)} \cos{\left(t \right)} & 0 & 0\\0 & 0 & - \sin{\left(t \right)} \cos{\left(t \right)} \sinh^{2}{\left(\chi \right)} & 0\\0 & 0 & 0 & - \sin{\left(t \right)} \sin^{2}{\left(\theta \right)} \cos{\left(t \right)} \sinh^{2}{\left(\chi \right)}\end{matrix}\right] & \left[\begin{matrix}0 & - \frac{\sin{\left(t \right)}}{\cos{\left(t \right)}} & 0 & 0\\- \frac{\sin{\left(t \right)}}{\cos{\left(t \right)}} & 0 & 0 & 0\\0 & 0 & - \sinh{\left(\chi \right)} \cosh{\left(\chi \right)} & 0\\0 & 0 & 0 & - \sin^{2}{\left(\theta \right)} \sinh{\left(\chi \right)} \cosh{\left(\chi \right)}\end{matrix}\right] & \left[\begin{matrix}0 & 0 & - \frac{\sin{\left(t \right)}}{\cos{\left(t \right)}} & 0\\0 & 0 & \frac{\cosh{\left(\chi \right)}}{\sinh{\left(\chi \right)}} & 0\\- \frac{\sin{\left(t \right)}}{\cos{\left(t \right)}} & \frac{\cosh{\left(\chi \right)}}{\sinh{\left(\chi \right)}} & 0 & 0\\0 & 0 & 0 & - \sin{\left(\theta \right)} \cos{\left(\theta \right)}\end{matrix}\right] & \left[\begin{matrix}0 & 0 & 0 & - \frac{\sin{\left(t \right)}}{\cos{\left(t \right)}}\\0 & 0 & 0 & \frac{\cosh{\left(\chi \right)}}{\sinh{\left(\chi \right)}}\\0 & 0 & 0 & \frac{\cos{\left(\theta \right)}}{\sin{\left(\theta \right)}}\\- \frac{\sin{\left(t \right)}}{\cos{\left(t \right)}} & \frac{\cosh{\left(\chi \right)}}{\sinh{\left(\chi \right)}} & \frac{\cos{\left(\theta \right)}}{\sin{\left(\theta \right)}} & 0\end{matrix}\right]\end{matrix}\right]$

```python
wt = WeylTensor.from_metric(metric) # Compute Weyl Tensor from AdS metric
wt.tensor()
```

$\displaystyle \left[\begin{matrix}\left[\begin{matrix}0 & 0 & 0 & 0\\0 & - \cos^{2}{\left(t \right)} & 0 & 0\\0 & 0 & - \cos^{2}{\left(t \right)} \sinh^{2}{\left(\chi \right)} & 0\\0 & 0 & 0 & - \sin^{2}{\left(\theta \right)} \cos^{2}{\left(t \right)} \sinh^{2}{\left(\chi \right)}\end{matrix}\right] & \left[\begin{matrix}0 & 0 & 0 & 0\\\cos^{2}{\left(t \right)} & 0 & 0 & 0\\0 & 0 & 0 & 0\\0 & 0 & 0 & 0\end{matrix}\right] & \left[\begin{matrix}0 & 0 & 0 & 0\\0 & 0 & 0 & 0\\\cos^{2}{\left(t \right)} \sinh^{2}{\left(\chi \right)} & 0 & 0 & 0\\0 & 0 & 0 & 0\end{matrix}\right] & \left[\begin{matrix}0 & 0 & 0 & 0\\0 & 0 & 0 & 0\\0 & 0 & 0 & 0\\\sin^{2}{\left(\theta \right)} \cos^{2}{\left(t \right)} \sinh^{2}{\left(\chi \right)} & 0 & 0 & 0\end{matrix}\right]\\\left[\begin{matrix}0 & \cos^{2}{\left(t \right)} & 0 & 0\\0 & 0 & 0 & 0\\0 & 0 & 0 & 0\\0 & 0 & 0 & 0\end{matrix}\right] & \left[\begin{matrix}- \cos^{2}{\left(t \right)} & 0 & 0 & 0\\0 & 0 & 0 & 0\\0 & 0 & \cos^{4}{\left(t \right)} \sinh^{2}{\left(\chi \right)} & 0\\0 & 0 & 0 & \sin^{2}{\left(\theta \right)} \cos^{4}{\left(t \right)} \sinh^{2}{\left(\chi \right)}\end{matrix}\right] & \left[\begin{matrix}0 & 0 & 0 & 0\\0 & 0 & 0 & 0\\0 & - \cos^{4}{\left(t \right)} \sinh^{2}{\left(\chi \right)} & 0 & 0\\0 & 0 & 0 & 0\end{matrix}\right] & \left[\begin{matrix}0 & 0 & 0 & 0\\0 & 0 & 0 & 0\\0 & 0 & 0 & 0\\0 & - \sin^{2}{\left(\theta \right)} \cos^{4}{\left(t \right)} \sinh^{2}{\left(\chi \right)} & 0 & 0\end{matrix}\right]\\\left[\begin{matrix}0 & 0 & \cos^{2}{\left(t \right)} \sinh^{2}{\left(\chi \right)} & 0\\0 & 0 & 0 & 0\\0 & 0 & 0 & 0\\0 & 0 & 0 & 0\end{matrix}\right] & \left[\begin{matrix}0 & 0 & 0 & 0\\0 & 0 & - \cos^{4}{\left(t \right)} \sinh^{2}{\left(\chi \right)} & 0\\0 & 0 & 0 & 0\\0 & 0 & 0 & 0\end{matrix}\right] & \left[\begin{matrix}- \cos^{2}{\left(t \right)} \sinh^{2}{\left(\chi \right)} & 0 & 0 & 0\\0 & \cos^{4}{\left(t \right)} \sinh^{2}{\left(\chi \right)} & 0 & 0\\0 & 0 & 0 & 0\\0 & 0 & 0 & \sin^{2}{\left(\theta \right)} \cos^{4}{\left(t \right)} \sinh^{4}{\left(\chi \right)}\end{matrix}\right] & \left[\begin{matrix}0 & 0 & 0 & 0\\0 & 0 & 0 & 0\\0 & 0 & 0 & 0\\0 & 0 & - \sin^{2}{\left(\theta \right)} \cos^{4}{\left(t \right)} \sinh^{4}{\left(\chi \right)} & 0\end{matrix}\right]\\\left[\begin{matrix}0 & 0 & 0 & \sin^{2}{\left(\theta \right)} \cos^{2}{\left(t \right)} \sinh^{2}{\left(\chi \right)}\\0 & 0 & 0 & 0\\0 & 0 & 0 & 0\\0 & 0 & 0 & 0\end{matrix}\right] & \left[\begin{matrix}0 & 0 & 0 & 0\\0 & 0 & 0 & - \sin^{2}{\left(\theta \right)} \cos^{4}{\left(t \right)} \sinh^{2}{\left(\chi \right)}\\0 & 0 & 0 & 0\\0 & 0 & 0 & 0\end{matrix}\right] & \left[\begin{matrix}0 & 0 & 0 & 0\\0 & 0 & 0 & 0\\0 & 0 & 0 & - \sin^{2}{\left(\theta \right)} \cos^{4}{\left(t \right)} \sinh^{4}{\left(\chi \right)}\\0 & 0 & 0 & 0\end{matrix}\right] & \left[\begin{matrix}- \sin^{2}{\left(\theta \right)} \cos^{2}{\left(t \right)} \sinh^{2}{\left(\chi \right)} & 0 & 0 & 0\\0 & \sin^{2}{\left(\theta \right)} \cos^{4}{\left(t \right)} \sinh^{2}{\left(\chi \right)} & 0 & 0\\0 & 0 & \sin^{2}{\left(\theta \right)} \cos^{4}{\left(t \right)} \sinh^{4}{\left(\chi \right)} & 0\\0 & 0 & 0 & 0\end{matrix}\right]\end{matrix}\right]$

```python

```


```python
import sympy as sym
import helpers
from IPython.display import display
from IPython.display import Code
```

## ST-VK Hyperelastic Model

```python
# Define input variables and constants.
Fv = sym.MatrixSymbol('Fv', 9, 1)
lame1, lame2 = sym.symbols('lame1 lame2', constants = True)

# Define expression to evaluate.
F = sym.Matrix([[ Fv[0], Fv[1], Fv[2] ], [ Fv[3], Fv[4], Fv[5] ], [ Fv[6], Fv[7], Fv[8] ]])
I = sym.eye(3)
E1 = (F.T * F - I) / 2
E2 = E1 * E1

trE1 = sym.Trace(E1).doit() # Explicitly evaluate trace. Sympy has problems differentiating
trE2 = sym.Trace(E2).doit() # it otherwise.

U = lame1 * (trE1 ** 2) / 2 + lame2 * trE2
dUdF = helpers.gradient(U, [ *Fv ])
d2UdF2 = helpers.hessian(U, [ *Fv ])

# Define specific routines to export to code.
export = [("U", U), ("dUdF", dUdF), ("d2UdF2", d2UdF2)]

# Generate code and save to disk.
code = helpers.generate(export)
helpers.save('generated/stvk-U.inc', code[0])
helpers.save('generated/stvk-dUdF.inc', code[1])
helpers.save('generated/stvk-d2UdF2.inc', code[2])

# Display.
#print('Energy')
#display(U)
#print('Gradient')
#display(dUdF)
#print('Hessian')
#display(d2UdF2)

for (name, _), c in zip(export, code):
    print(name)
    display(Code(c))
    print()
```

    U

double U(const VectorXd& Fv, double lame1, double lame2) {
    double x0[9];
    x0[0] = Fv[0];
    x0[1] = Fv[1];
    x0[2] = Fv[2];
    x0[3] = Fv[3];
    x0[4] = Fv[4];
    x0[5] = Fv[5];
    x0[6] = Fv[6];
    x0[7] = Fv[7];
    x0[8] = Fv[8];
    const double x1 = x0[0];
    const double x2 = x0[3];
    const double x3 = x0[6];
    const double x4 = pow(x1, 2) + pow(x2, 2) + pow(x3, 2);
    const double x5 = x0[1];
    const double x6 = x0[4];
    const double x7 = x0[7];
    const double x8 = pow(x5, 2) + pow(x6, 2) + pow(x7, 2);
    const double x9 = x0[2];
    const double x10 = x0[5];
    const double x11 = x0[8];
    const double x12 = pow(x10, 2) + pow(x11, 2) + pow(x9, 2);

    double out;
    out = (1.0/8.0)*lame1*pow(x12 + x4 + x8 - 3, 2) + (1.0/4.0)*lame2*(pow(x12 - 1, 2) + pow(x4 - 1, 2) + pow(x8 - 1, 2) + 2*pow(x1*x5 + x2*x6 + x3*x7, 2) + 2*pow(x1*x9 + x10*x2 + x11*x3, 2) + 2*pow(x10*x6 + x11*x7 + x5*x9, 2));
    return out;

}

    dUdF

void dUdF(const VectorXd& Fv, double lame1, double lame2, VectorXd& out) {
    double x0[9];
    x0[0] = Fv[0];
    x0[1] = Fv[1];
    x0[2] = Fv[2];
    x0[3] = Fv[3];
    x0[4] = Fv[4];
    x0[5] = Fv[5];
    x0[6] = Fv[6];
    x0[7] = Fv[7];
    x0[8] = Fv[8];
    const double x1 = x0[0];
    const double x2 = x0[3];
    const double x3 = x0[6];
    const double x4 = pow(x1, 2) + pow(x2, 2) + pow(x3, 2);
    const double x5 = x0[1];
    const double x6 = x0[4];
    const double x7 = x0[7];
    const double x8 = pow(x5, 2) + pow(x6, 2) + pow(x7, 2);
    const double x9 = x0[2];
    const double x10 = x0[5];
    const double x11 = x0[8];
    const double x12 = pow(x10, 2) + pow(x11, 2) + pow(x9, 2);
    const double x13 = (1.0/2.0)*lame1*(x12 + x4 + x8 - 3);
    const double x14 = x4 - 1;
    const double x15 = x1*x5 + x2*x6 + x3*x7;
    const double x16 = x1*x9 + x10*x2 + x11*x3;
    const double x17 = x8 - 1;
    const double x18 = x10*x6 + x11*x7 + x5*x9;
    const double x19 = x12 - 1;

    out.resize(9);
    out[0] = lame2*(x1*x14 + x15*x5 + x16*x9) + x1*x13;
    out[1] = lame2*(x1*x15 + x17*x5 + x18*x9) + x13*x5;
    out[2] = lame2*(x1*x16 + x18*x5 + x19*x9) + x13*x9;
    out[3] = lame2*(x10*x16 + x14*x2 + x15*x6) + x13*x2;
    out[4] = lame2*(x10*x18 + x15*x2 + x17*x6) + x13*x6;
    out[5] = lame2*(x10*x19 + x16*x2 + x18*x6) + x10*x13;
    out[6] = lame2*(x11*x16 + x14*x3 + x15*x7) + x13*x3;
    out[7] = lame2*(x11*x18 + x15*x3 + x17*x7) + x13*x7;
    out[8] = lame2*(x11*x19 + x16*x3 + x18*x7) + x11*x13;

}

    d2UdF2

void d2UdF2(const VectorXd& Fv, double lame1, double lame2, MatrixXd& out) {
    double x0[9];
    x0[0] = Fv[0];
    x0[1] = Fv[1];
    x0[2] = Fv[2];
    x0[3] = Fv[3];
    x0[4] = Fv[4];
    x0[5] = Fv[5];
    x0[6] = Fv[6];
    x0[7] = Fv[7];
    x0[8] = Fv[8];
    const double x1 = x0[0];
    const double x2 = pow(x1, 2);
    const double x3 = x0[1];
    const double x4 = pow(x3, 2);
    const double x5 = x0[3];
    const double x6 = pow(x5, 2);
    const double x7 = x4 + x6;
    const double x8 = x0[6];
    const double x9 = pow(x8, 2);
    const double x10 = x0[2];
    const double x11 = pow(x10, 2);
    const double x12 = x11 - 1;
    const double x13 = x12 + x9;
    const double x14 = x0[5];
    const double x15 = pow(x14, 2);
    const double x16 = x0[8];
    const double x17 = pow(x16, 2);
    const double x18 = x0[7];
    const double x19 = pow(x18, 2);
    const double x20 = x0[4];
    const double x21 = pow(x20, 2);
    const double x22 = x2 + x21;
    const double x23 = x19 + x22;
    const double x24 = (1.0/2.0)*lame1*(x11 + x15 + x17 + x23 + x7 + x9 - 3);
    const double x25 = x1*x3;
    const double x26 = x20*x5;
    const double x27 = x18*x8;
    const double x28 = lame1*x25 + lame2*(2*x25 + x26 + x27);
    const double x29 = x1*x10;
    const double x30 = x14*x5;
    const double x31 = x16*x8;
    const double x32 = lame1*x29 + lame2*(2*x29 + x30 + x31);
    const double x33 = x1*x5;
    const double x34 = x20*x3;
    const double x35 = x10*x14;
    const double x36 = lame1*x33 + lame2*(2*x33 + x34 + x35);
    const double x37 = lame1*x1;
    const double x38 = lame2*x5;
    const double x39 = x20*x37 + x3*x38;
    const double x40 = x10*x38 + x14*x37;
    const double x41 = x1*x8;
    const double x42 = x18*x3;
    const double x43 = x10*x16;
    const double x44 = lame1*x41 + lame2*(2*x41 + x42 + x43);
    const double x45 = lame2*x8;
    const double x46 = x18*x37 + x3*x45;
    const double x47 = x10*x45 + x16*x37;
    const double x48 = x10*x3;
    const double x49 = x14*x20;
    const double x50 = x16*x18;
    const double x51 = lame1*x48 + lame2*(2*x48 + x49 + x50);
    const double x52 = lame1*x3;
    const double x53 = lame2*x20;
    const double x54 = x1*x53 + x5*x52;
    const double x55 = lame1*x34 + lame2*(x33 + 2*x34 + x35);
    const double x56 = x10*x53 + x14*x52;
    const double x57 = lame2*x18;
    const double x58 = x1*x57 + x52*x8;
    const double x59 = lame1*x42 + lame2*(x41 + 2*x42 + x43);
    const double x60 = x10*x57 + x16*x52;
    const double x61 = x15 - 1;
    const double x62 = x17 + x2;
    const double x63 = lame1*x10;
    const double x64 = lame2*x14;
    const double x65 = x1*x64 + x5*x63;
    const double x66 = x20*x63 + x3*x64;
    const double x67 = lame1*x35 + lame2*(x33 + x34 + 2*x35);
    const double x68 = lame2*x16;
    const double x69 = x1*x68 + x63*x8;
    const double x70 = x18*x63 + x3*x68;
    const double x71 = lame1*x43 + lame2*(x41 + x42 + 2*x43);
    const double x72 = lame1*x26 + lame2*(x25 + 2*x26 + x27);
    const double x73 = lame1*x30 + lame2*(x29 + 2*x30 + x31);
    const double x74 = x5*x8;
    const double x75 = x18*x20;
    const double x76 = x14*x16;
    const double x77 = lame1*x74 + lame2*(2*x74 + x75 + x76);
    const double x78 = lame1*x5;
    const double x79 = x18*x78 + x20*x45;
    const double x80 = x14*x45 + x16*x78;
    const double x81 = lame1*x49 + lame2*(x48 + 2*x49 + x50);
    const double x82 = lame1*x20;
    const double x83 = x18*x38 + x8*x82;
    const double x84 = lame1*x75 + lame2*(x74 + 2*x75 + x76);
    const double x85 = x14*x57 + x16*x82;
    const double x86 = x17 + x21;
    const double x87 = lame1*x14;
    const double x88 = x16*x38 + x8*x87;
    const double x89 = x16*x53 + x18*x87;
    const double x90 = lame1*x76 + lame2*(x74 + x75 + 2*x76);
    const double x91 = lame1*x27 + lame2*(x25 + x26 + 2*x27);
    const double x92 = lame1*x31 + lame2*(x29 + x30 + 2*x31);
    const double x93 = lame1*x50 + lame2*(x48 + x49 + 2*x50);

    out.resize(9, 9);
    out(0, 0) = lame1*x2 + lame2*(x13 + 3*x2 + x7) + x24;
    out(0, 1) = x28;
    out(0, 2) = x32;
    out(0, 3) = x36;
    out(0, 4) = x39;
    out(0, 5) = x40;
    out(0, 6) = x44;
    out(0, 7) = x46;
    out(0, 8) = x47;
    out(1, 0) = x28;
    out(1, 1) = lame1*x4 + lame2*(x12 + x23 + 3*x4) + x24;
    out(1, 2) = x51;
    out(1, 3) = x54;
    out(1, 4) = x55;
    out(1, 5) = x56;
    out(1, 6) = x58;
    out(1, 7) = x59;
    out(1, 8) = x60;
    out(2, 0) = x32;
    out(2, 1) = x51;
    out(2, 2) = lame1*x11 + lame2*(3*x11 + x4 + x61 + x62) + x24;
    out(2, 3) = x65;
    out(2, 4) = x66;
    out(2, 5) = x67;
    out(2, 6) = x69;
    out(2, 7) = x70;
    out(2, 8) = x71;
    out(3, 0) = x36;
    out(3, 1) = x54;
    out(3, 2) = x65;
    out(3, 3) = lame1*x6 + lame2*(x22 + 3*x6 + x61 + x9) + x24;
    out(3, 4) = x72;
    out(3, 5) = x73;
    out(3, 6) = x77;
    out(3, 7) = x79;
    out(3, 8) = x80;
    out(4, 0) = x39;
    out(4, 1) = x55;
    out(4, 2) = x66;
    out(4, 3) = x72;
    out(4, 4) = lame1*x21 + lame2*(x19 + 3*x21 + x61 + x7) + x24;
    out(4, 5) = x81;
    out(4, 6) = x83;
    out(4, 7) = x84;
    out(4, 8) = x85;
    out(5, 0) = x40;
    out(5, 1) = x56;
    out(5, 2) = x67;
    out(5, 3) = x73;
    out(5, 4) = x81;
    out(5, 5) = lame1*x15 + lame2*(x12 + 3*x15 + x6 + x86) + x24;
    out(5, 6) = x88;
    out(5, 7) = x89;
    out(5, 8) = x90;
    out(6, 0) = x44;
    out(6, 1) = x58;
    out(6, 2) = x69;
    out(6, 3) = x77;
    out(6, 4) = x83;
    out(6, 5) = x88;
    out(6, 6) = lame1*x9 + lame2*(x19 + x6 + x62 + 3*x9 - 1) + x24;
    out(6, 7) = x91;
    out(6, 8) = x92;
    out(7, 0) = x46;
    out(7, 1) = x59;
    out(7, 2) = x70;
    out(7, 3) = x79;
    out(7, 4) = x84;
    out(7, 5) = x89;
    out(7, 6) = x91;
    out(7, 7) = lame1*x19 + lame2*(3*x19 + x4 + x86 + x9 - 1) + x24;
    out(7, 8) = x93;
    out(8, 0) = x47;
    out(8, 1) = x60;
    out(8, 2) = x71;
    out(8, 3) = x80;
    out(8, 4) = x85;
    out(8, 5) = x90;
    out(8, 6) = x92;
    out(8, 7) = x93;
    out(8, 8) = lame1*x17 + lame2*(x13 + x15 + 3*x17 + x19) + x24;

}

## ST-VK Dissipation Potential

```python
# Define input variables and constants.
Fv = sym.MatrixSymbol('Fv', 9, 1)
Gv = sym.MatrixSymbol('Gv', 9, 1)
lame1, lame2, beta = sym.symbols('lame1 lame2 beta', constants = True)

# Define expression to evaluate.
F = sym.Matrix([[ Fv[0], Fv[1], Fv[2] ], [ Fv[3], Fv[4], Fv[5] ], [ Fv[6], Fv[7], Fv[8] ]])
G = sym.Matrix([[ Gv[0], Gv[1], Gv[2] ], [ Gv[3], Gv[4], Gv[5] ], [ Gv[6], Gv[7], Gv[8] ]])
I = sym.eye(3)
E1 = (G.T * F + F.T * G) / 2
E2 = E1 * E1

trE1 = sym.Trace(E1).doit()
trE2 = sym.Trace(E2).doit()

R = beta * lame1 * (trE1 ** 2) / 2 + beta * lame2 * trE2
dRdFG = helpers.gradient(R, [ *Fv, *Gv ])
d2RdFG2 = helpers.hessian(R, [ *Fv, *Gv ])

# Define specific routines to export to code.
export = [("R", R), ("dRdFG", dRdFG), ("d2RdF2", d2RdFG2)]

# Generate code and save to disk.
code = helpers.generate(export)
helpers.save('generated/stvk-R.inc', code[0])
helpers.save('generated/stvk-dRdFG.inc', code[1])
helpers.save('generated/stvk-d2RdFG2.inc', code[2])

# Display.
#print('Dissipation potential')
#display(R)
#print('Gradient')
#display(dRdFG)
#print('Hessian')
#display(d2RdFG2)

for (name, _), c in zip(export, code):
    print(name)
    display(Code(c))
    print()
```

    R

double R(const VectorXd& Fv, const VectorXd& Gv, double beta, double lame1, double lame2) {
    double x0[9];
    x0[0] = Fv[0];
    x0[1] = Fv[1];
    x0[2] = Fv[2];
    x0[3] = Fv[3];
    x0[4] = Fv[4];
    x0[5] = Fv[5];
    x0[6] = Fv[6];
    x0[7] = Fv[7];
    x0[8] = Fv[8];
    const double x1 = x0[0];
    double x2[9];
    x2[0] = Gv[0];
    x2[1] = Gv[1];
    x2[2] = Gv[2];
    x2[3] = Gv[3];
    x2[4] = Gv[4];
    x2[5] = Gv[5];
    x2[6] = Gv[6];
    x2[7] = Gv[7];
    x2[8] = Gv[8];
    const double x3 = x2[0];
    const double x4 = x0[3];
    const double x5 = x2[3];
    const double x6 = x0[6];
    const double x7 = x2[6];
    const double x8 = x1*x3 + x4*x5 + x6*x7;
    const double x9 = x0[1];
    const double x10 = x2[1];
    const double x11 = x0[4];
    const double x12 = x2[4];
    const double x13 = x0[7];
    const double x14 = x2[7];
    const double x15 = x10*x9 + x11*x12 + x13*x14;
    const double x16 = x0[2];
    const double x17 = x2[2];
    const double x18 = x0[5];
    const double x19 = x2[5];
    const double x20 = x0[8];
    const double x21 = x2[8];
    const double x22 = x16*x17 + x18*x19 + x20*x21;

    double out;
    out = (1.0/2.0)*beta*(lame1*pow(x15 + x22 + x8, 2) + lame2*(2*pow(x15, 2) + 2*pow(x22, 2) + 2*pow(x8, 2) + pow(x1*x10 + x11*x5 + x12*x4 + x13*x7 + x14*x6 + x3*x9, 2) + pow(x1*x17 + x16*x3 + x18*x5 + x19*x4 + x20*x7 + x21*x6, 2) + pow(x10*x16 + x11*x19 + x12*x18 + x13*x21 + x14*x20 + x17*x9, 2)));
    return out;

}

    dRdFG

void dRdFG(const VectorXd& Fv, const VectorXd& Gv, double beta, double lame1, double lame2, VectorXd& out) {
    double x0[9];
    x0[0] = Gv[0];
    x0[1] = Gv[1];
    x0[2] = Gv[2];
    x0[3] = Gv[3];
    x0[4] = Gv[4];
    x0[5] = Gv[5];
    x0[6] = Gv[6];
    x0[7] = Gv[7];
    x0[8] = Gv[8];
    const double x1 = x0[0];
    double x2[9];
    x2[0] = Fv[0];
    x2[1] = Fv[1];
    x2[2] = Fv[2];
    x2[3] = Fv[3];
    x2[4] = Fv[4];
    x2[5] = Fv[5];
    x2[6] = Fv[6];
    x2[7] = Fv[7];
    x2[8] = Fv[8];
    const double x3 = x2[0];
    const double x4 = x2[3];
    const double x5 = x0[3];
    const double x6 = x2[6];
    const double x7 = x0[6];
    const double x8 = x1*x3 + x4*x5 + x6*x7;
    const double x9 = x2[1];
    const double x10 = x0[1];
    const double x11 = x2[4];
    const double x12 = x0[4];
    const double x13 = x2[7];
    const double x14 = x0[7];
    const double x15 = x10*x9 + x11*x12 + x13*x14;
    const double x16 = x2[2];
    const double x17 = x0[2];
    const double x18 = x2[5];
    const double x19 = x0[5];
    const double x20 = x2[8];
    const double x21 = x0[8];
    const double x22 = x16*x17 + x18*x19 + x20*x21;
    const double x23 = lame1*(x15 + x22 + x8);
    const double x24 = 2*x8;
    const double x25 = x1*x9 + x10*x3 + x11*x5 + x12*x4 + x13*x7 + x14*x6;
    const double x26 = x1*x16 + x17*x3 + x18*x5 + x19*x4 + x20*x7 + x21*x6;
    const double x27 = 2*x15;
    const double x28 = x10*x16 + x11*x19 + x12*x18 + x13*x21 + x14*x20 + x17*x9;
    const double x29 = 2*x22;

    out.resize(18);
    out[0] = beta*(lame2*(x1*x24 + x10*x25 + x17*x26) + x1*x23);
    out[1] = beta*(lame2*(x1*x25 + x10*x27 + x17*x28) + x10*x23);
    out[2] = beta*(lame2*(x1*x26 + x10*x28 + x17*x29) + x17*x23);
    out[3] = beta*(lame2*(x12*x25 + x19*x26 + x24*x5) + x23*x5);
    out[4] = beta*(lame2*(x12*x27 + x19*x28 + x25*x5) + x12*x23);
    out[5] = beta*(lame2*(x12*x28 + x19*x29 + x26*x5) + x19*x23);
    out[6] = beta*(lame2*(x14*x25 + x21*x26 + x24*x7) + x23*x7);
    out[7] = beta*(lame2*(x14*x27 + x21*x28 + x25*x7) + x14*x23);
    out[8] = beta*(lame2*(x14*x28 + x21*x29 + x26*x7) + x21*x23);
    out[9] = beta*(lame2*(x16*x26 + x24*x3 + x25*x9) + x23*x3);
    out[10] = beta*(lame2*(x16*x28 + x25*x3 + x27*x9) + x23*x9);
    out[11] = beta*(lame2*(x16*x29 + x26*x3 + x28*x9) + x16*x23);
    out[12] = beta*(lame2*(x11*x25 + x18*x26 + x24*x4) + x23*x4);
    out[13] = beta*(lame2*(x11*x27 + x18*x28 + x25*x4) + x11*x23);
    out[14] = beta*(lame2*(x11*x28 + x18*x29 + x26*x4) + x18*x23);
    out[15] = beta*(lame2*(x13*x25 + x20*x26 + x24*x6) + x23*x6);
    out[16] = beta*(lame2*(x13*x27 + x20*x28 + x25*x6) + x13*x23);
    out[17] = beta*(lame2*(x13*x28 + x20*x29 + x26*x6) + x20*x23);

}

    d2RdF2

void d2RdF2(const VectorXd& Fv, const VectorXd& Gv, double beta, double lame1, double lame2, MatrixXd& out) {
    double x0[9];
    x0[0] = Gv[0];
    x0[1] = Gv[1];
    x0[2] = Gv[2];
    x0[3] = Gv[3];
    x0[4] = Gv[4];
    x0[5] = Gv[5];
    x0[6] = Gv[6];
    x0[7] = Gv[7];
    x0[8] = Gv[8];
    const double x1 = x0[0];
    const double x2 = pow(x1, 2);
    const double x3 = x0[1];
    const double x4 = pow(x3, 2);
    const double x5 = x0[2];
    const double x6 = pow(x5, 2);
    const double x7 = beta*(lame1 + lame2);
    const double x8 = x1*x7;
    const double x9 = x3*x8;
    const double x10 = x5*x8;
    const double x11 = x0[3];
    const double x12 = x1*x11;
    const double x13 = x0[4];
    const double x14 = x13*x3;
    const double x15 = x0[5];
    const double x16 = x15*x5;
    const double x17 = beta*(lame1*x12 + lame2*(2*x12 + x14 + x16));
    const double x18 = lame1*x1;
    const double x19 = lame2*x11;
    const double x20 = beta*(x13*x18 + x19*x3);
    const double x21 = beta*(x15*x18 + x19*x5);
    const double x22 = x0[6];
    const double x23 = x1*x22;
    const double x24 = x0[7];
    const double x25 = x24*x3;
    const double x26 = x0[8];
    const double x27 = x26*x5;
    const double x28 = beta*(lame1*x23 + lame2*(2*x23 + x25 + x27));
    const double x29 = lame2*x22;
    const double x30 = beta*(x18*x24 + x29*x3);
    const double x31 = beta*(x18*x26 + x29*x5);
    double x32[9];
    x32[0] = Fv[0];
    x32[1] = Fv[1];
    x32[2] = Fv[2];
    x32[3] = Fv[3];
    x32[4] = Fv[4];
    x32[5] = Fv[5];
    x32[6] = Fv[6];
    x32[7] = Fv[7];
    x32[8] = Fv[8];
    const double x33 = x32[0];
    const double x34 = x1*x33;
    const double x35 = x32[3];
    const double x36 = x11*x35;
    const double x37 = 2*x36;
    const double x38 = x32[6];
    const double x39 = x22*x38;
    const double x40 = 2*x39;
    const double x41 = x32[1];
    const double x42 = x3*x41;
    const double x43 = x32[2];
    const double x44 = x43*x5;
    const double x45 = x42 + x44;
    const double x46 = x32[4];
    const double x47 = x13*x46;
    const double x48 = x32[5];
    const double x49 = x15*x48;
    const double x50 = x47 + x49;
    const double x51 = x32[7];
    const double x52 = x24*x51;
    const double x53 = x32[8];
    const double x54 = x26*x53;
    const double x55 = x52 + x54;
    const double x56 = lame1*(x34 + x36 + x39 + x45 + x50 + x55);
    const double x57 = beta*(lame1*x34 + lame2*(4*x34 + x37 + x40 + x45) + x56);
    const double x58 = x1*x41;
    const double x59 = x3*x33;
    const double x60 = x13*x35;
    const double x61 = x11*x46;
    const double x62 = x24*x38;
    const double x63 = x22*x51;
    const double x64 = x60 + x61 + x62 + x63;
    const double x65 = beta*(lame1*x58 + lame2*(x58 + 2*x59 + x64));
    const double x66 = x1*x43;
    const double x67 = x33*x5;
    const double x68 = x15*x35;
    const double x69 = x11*x48;
    const double x70 = x26*x38;
    const double x71 = x22*x53;
    const double x72 = x68 + x69 + x70 + x71;
    const double x73 = beta*(lame1*x66 + lame2*(x66 + 2*x67 + x72));
    const double x74 = x1*x35;
    const double x75 = x3*x46;
    const double x76 = x48*x5;
    const double x77 = beta*(lame1*x74 + lame2*(2*x74 + x75 + x76));
    const double x78 = lame2*x35;
    const double x79 = beta*(x18*x46 + x3*x78);
    const double x80 = beta*(x18*x48 + x5*x78);
    const double x81 = x1*x38;
    const double x82 = x3*x51;
    const double x83 = x5*x53;
    const double x84 = beta*(lame1*x81 + lame2*(2*x81 + x82 + x83));
    const double x85 = lame2*x38;
    const double x86 = beta*(x18*x51 + x3*x85);
    const double x87 = beta*(x18*x53 + x5*x85);
    const double x88 = x3*x5*x7;
    const double x89 = lame1*x3;
    const double x90 = lame2*x13;
    const double x91 = beta*(x1*x90 + x11*x89);
    const double x92 = beta*(lame1*x14 + lame2*(x12 + 2*x14 + x16));
    const double x93 = beta*(x15*x89 + x5*x90);
    const double x94 = lame2*x24;
    const double x95 = beta*(x1*x94 + x22*x89);
    const double x96 = beta*(lame1*x25 + lame2*(x23 + 2*x25 + x27));
    const double x97 = beta*(x26*x89 + x5*x94);
    const double x98 = beta*(lame1*x59 + lame2*(2*x58 + x59 + x64));
    const double x99 = 2*x47;
    const double x100 = 2*x52;
    const double x101 = beta*(lame1*x42 + lame2*(x100 + x34 + 4*x42 + x44 + x99) + x56);
    const double x102 = x3*x43;
    const double x103 = x41*x5;
    const double x104 = x15*x46;
    const double x105 = x13*x48;
    const double x106 = x26*x51;
    const double x107 = x24*x53;
    const double x108 = x104 + x105 + x106 + x107;
    const double x109 = beta*(lame1*x102 + lame2*(x102 + 2*x103 + x108));
    const double x110 = lame2*x46;
    const double x111 = beta*(x1*x110 + x35*x89);
    const double x112 = beta*(lame1*x75 + lame2*(x74 + 2*x75 + x76));
    const double x113 = beta*(x110*x5 + x48*x89);
    const double x114 = lame2*x51;
    const double x115 = beta*(x1*x114 + x38*x89);
    const double x116 = beta*(lame1*x82 + lame2*(x81 + 2*x82 + x83));
    const double x117 = beta*(x114*x5 + x53*x89);
    const double x118 = lame1*x5;
    const double x119 = lame2*x15;
    const double x120 = beta*(x1*x119 + x11*x118);
    const double x121 = beta*(x118*x13 + x119*x3);
    const double x122 = beta*(lame1*x16 + lame2*(x12 + x14 + 2*x16));
    const double x123 = lame2*x26;
    const double x124 = beta*(x1*x123 + x118*x22);
    const double x125 = beta*(x118*x24 + x123*x3);
    const double x126 = beta*(lame1*x27 + lame2*(x23 + x25 + 2*x27));
    const double x127 = beta*(lame1*x67 + lame2*(2*x66 + x67 + x72));
    const double x128 = beta*(lame1*x103 + lame2*(2*x102 + x103 + x108));
    const double x129 = 2*x49;
    const double x130 = 2*x54;
    const double x131 = beta*(lame1*x44 + lame2*(x129 + x130 + x34 + x42 + 4*x44) + x56);
    const double x132 = lame2*x48;
    const double x133 = beta*(x1*x132 + x118*x35);
    const double x134 = beta*(x118*x46 + x132*x3);
    const double x135 = beta*(lame1*x76 + lame2*(x74 + x75 + 2*x76));
    const double x136 = lame2*x53;
    const double x137 = beta*(x1*x136 + x118*x38);
    const double x138 = beta*(x118*x51 + x136*x3);
    const double x139 = beta*(lame1*x83 + lame2*(x81 + x82 + 2*x83));
    const double x140 = pow(x11, 2);
    const double x141 = pow(x13, 2);
    const double x142 = pow(x15, 2);
    const double x143 = x11*x7;
    const double x144 = x13*x143;
    const double x145 = x143*x15;
    const double x146 = x11*x22;
    const double x147 = x13*x24;
    const double x148 = x15*x26;
    const double x149 = beta*(lame1*x146 + lame2*(2*x146 + x147 + x148));
    const double x150 = lame1*x11;
    const double x151 = beta*(x13*x29 + x150*x24);
    const double x152 = beta*(x15*x29 + x150*x26);
    const double x153 = x11*x33;
    const double x154 = x13*x41;
    const double x155 = x15*x43;
    const double x156 = beta*(lame1*x153 + lame2*(2*x153 + x154 + x155));
    const double x157 = beta*(x150*x41 + x33*x90);
    const double x158 = beta*(x119*x33 + x150*x43);
    const double x159 = 2*x34;
    const double x160 = beta*(lame1*x36 + lame2*(x159 + 4*x36 + x40 + x50) + x56);
    const double x161 = x58 + x59 + x62 + x63;
    const double x162 = beta*(lame1*x61 + lame2*(x161 + 2*x60 + x61));
    const double x163 = x66 + x67 + x70 + x71;
    const double x164 = beta*(lame1*x69 + lame2*(x163 + 2*x68 + x69));
    const double x165 = x11*x38;
    const double x166 = x13*x51;
    const double x167 = x15*x53;
    const double x168 = beta*(lame1*x165 + lame2*(2*x165 + x166 + x167));
    const double x169 = beta*(x13*x85 + x150*x51);
    const double x170 = beta*(x15*x85 + x150*x53);
    const double x171 = x13*x15*x7;
    const double x172 = lame1*x13;
    const double x173 = beta*(x172*x22 + x19*x24);
    const double x174 = beta*(lame1*x147 + lame2*(x146 + 2*x147 + x148));
    const double x175 = beta*(x15*x94 + x172*x26);
    const double x176 = beta*(x172*x33 + x19*x41);
    const double x177 = beta*(lame1*x154 + lame2*(x153 + 2*x154 + x155));
    const double x178 = beta*(x119*x41 + x172*x43);
    const double x179 = beta*(lame1*x60 + lame2*(x161 + x60 + 2*x61));
    const double x180 = 2*x42;
    const double x181 = beta*(lame1*x47 + lame2*(x100 + x180 + x36 + 4*x47 + x49) + x56);
    const double x182 = x102 + x103 + x106 + x107;
    const double x183 = beta*(lame1*x105 + lame2*(2*x104 + x105 + x182));
    const double x184 = beta*(x172*x38 + x19*x51);
    const double x185 = beta*(lame1*x166 + lame2*(x165 + 2*x166 + x167));
    const double x186 = beta*(x114*x15 + x172*x53);
    const double x187 = lame1*x15;
    const double x188 = beta*(x187*x22 + x19*x26);
    const double x189 = beta*(x187*x24 + x26*x90);
    const double x190 = beta*(lame1*x148 + lame2*(x146 + x147 + 2*x148));
    const double x191 = beta*(x187*x33 + x19*x43);
    const double x192 = beta*(x187*x41 + x43*x90);
    const double x193 = beta*(lame1*x155 + lame2*(x153 + x154 + 2*x155));
    const double x194 = beta*(lame1*x68 + lame2*(x163 + x68 + 2*x69));
    const double x195 = beta*(lame1*x104 + lame2*(x104 + 2*x105 + x182));
    const double x196 = 2*x44;
    const double x197 = beta*(lame1*x49 + lame2*(x130 + x196 + x36 + x47 + 4*x49) + x56);
    const double x198 = beta*(x187*x38 + x19*x53);
    const double x199 = beta*(x187*x51 + x53*x90);
    const double x200 = beta*(lame1*x167 + lame2*(x165 + x166 + 2*x167));
    const double x201 = pow(x22, 2);
    const double x202 = pow(x24, 2);
    const double x203 = pow(x26, 2);
    const double x204 = x22*x7;
    const double x205 = x204*x24;
    const double x206 = x204*x26;
    const double x207 = x22*x33;
    const double x208 = x24*x41;
    const double x209 = x26*x43;
    const double x210 = beta*(lame1*x207 + lame2*(2*x207 + x208 + x209));
    const double x211 = lame1*x22;
    const double x212 = beta*(x211*x41 + x33*x94);
    const double x213 = beta*(x123*x33 + x211*x43);
    const double x214 = x22*x35;
    const double x215 = x24*x46;
    const double x216 = x26*x48;
    const double x217 = beta*(lame1*x214 + lame2*(2*x214 + x215 + x216));
    const double x218 = beta*(x211*x46 + x24*x78);
    const double x219 = beta*(x211*x48 + x26*x78);
    const double x220 = beta*(lame1*x39 + lame2*(x159 + x37 + 4*x39 + x55) + x56);
    const double x221 = x58 + x59 + x60 + x61;
    const double x222 = beta*(lame1*x63 + lame2*(x221 + 2*x62 + x63));
    const double x223 = x66 + x67 + x68 + x69;
    const double x224 = beta*(lame1*x71 + lame2*(x223 + 2*x70 + x71));
    const double x225 = x24*x26*x7;
    const double x226 = lame1*x24;
    const double x227 = beta*(x226*x33 + x29*x41);
    const double x228 = beta*(lame1*x208 + lame2*(x207 + 2*x208 + x209));
    const double x229 = beta*(x123*x41 + x226*x43);
    const double x230 = beta*(x226*x35 + x29*x46);
    const double x231 = beta*(lame1*x215 + lame2*(x214 + 2*x215 + x216));
    const double x232 = beta*(x110*x26 + x226*x48);
    const double x233 = beta*(lame1*x62 + lame2*(x221 + x62 + 2*x63));
    const double x234 = beta*(lame1*x52 + lame2*(x180 + x39 + 4*x52 + x54 + x99) + x56);
    const double x235 = x102 + x103 + x104 + x105;
    const double x236 = beta*(lame1*x107 + lame2*(2*x106 + x107 + x235));
    const double x237 = lame1*x26;
    const double x238 = beta*(x237*x33 + x29*x43);
    const double x239 = beta*(x237*x41 + x43*x94);
    const double x240 = beta*(lame1*x209 + lame2*(x207 + x208 + 2*x209));
    const double x241 = beta*(x237*x35 + x29*x48);
    const double x242 = beta*(x237*x46 + x48*x94);
    const double x243 = beta*(lame1*x216 + lame2*(x214 + x215 + 2*x216));
    const double x244 = beta*(lame1*x70 + lame2*(x223 + x70 + 2*x71));
    const double x245 = beta*(lame1*x106 + lame2*(x106 + 2*x107 + x235));
    const double x246 = beta*(lame1*x54 + lame2*(x129 + x196 + x39 + x52 + 4*x54) + x56);
    const double x247 = pow(x33, 2);
    const double x248 = pow(x41, 2);
    const double x249 = pow(x43, 2);
    const double x250 = x33*x7;
    const double x251 = x250*x41;
    const double x252 = x250*x43;
    const double x253 = x33*x35;
    const double x254 = x41*x46;
    const double x255 = x43*x48;
    const double x256 = beta*(lame1*x253 + lame2*(2*x253 + x254 + x255));
    const double x257 = lame1*x33;
    const double x258 = beta*(x257*x46 + x41*x78);
    const double x259 = beta*(x257*x48 + x43*x78);
    const double x260 = x33*x38;
    const double x261 = x41*x51;
    const double x262 = x43*x53;
    const double x263 = beta*(lame1*x260 + lame2*(2*x260 + x261 + x262));
    const double x264 = beta*(x257*x51 + x41*x85);
    const double x265 = beta*(x257*x53 + x43*x85);
    const double x266 = x41*x43*x7;
    const double x267 = lame1*x41;
    const double x268 = beta*(x110*x33 + x267*x35);
    const double x269 = beta*(lame1*x254 + lame2*(x253 + 2*x254 + x255));
    const double x270 = beta*(x110*x43 + x267*x48);
    const double x271 = beta*(x114*x33 + x267*x38);
    const double x272 = beta*(lame1*x261 + lame2*(x260 + 2*x261 + x262));
    const double x273 = beta*(x114*x43 + x267*x53);
    const double x274 = lame1*x43;
    const double x275 = beta*(x132*x33 + x274*x35);
    const double x276 = beta*(x132*x41 + x274*x46);
    const double x277 = beta*(lame1*x255 + lame2*(x253 + x254 + 2*x255));
    const double x278 = beta*(x136*x33 + x274*x38);
    const double x279 = beta*(x136*x41 + x274*x51);
    const double x280 = beta*(lame1*x262 + lame2*(x260 + x261 + 2*x262));
    const double x281 = pow(x35, 2);
    const double x282 = pow(x46, 2);
    const double x283 = pow(x48, 2);
    const double x284 = x35*x7;
    const double x285 = x284*x46;
    const double x286 = x284*x48;
    const double x287 = x35*x38;
    const double x288 = x46*x51;
    const double x289 = x48*x53;
    const double x290 = beta*(lame1*x287 + lame2*(2*x287 + x288 + x289));
    const double x291 = lame1*x35;
    const double x292 = beta*(x291*x51 + x46*x85);
    const double x293 = beta*(x291*x53 + x48*x85);
    const double x294 = x46*x48*x7;
    const double x295 = lame1*x46;
    const double x296 = beta*(x295*x38 + x51*x78);
    const double x297 = beta*(lame1*x288 + lame2*(x287 + 2*x288 + x289));
    const double x298 = beta*(x114*x48 + x295*x53);
    const double x299 = lame1*x48;
    const double x300 = beta*(x299*x38 + x53*x78);
    const double x301 = beta*(x110*x53 + x299*x51);
    const double x302 = beta*(lame1*x289 + lame2*(x287 + x288 + 2*x289));
    const double x303 = pow(x38, 2);
    const double x304 = pow(x51, 2);
    const double x305 = pow(x53, 2);
    const double x306 = x38*x7;
    const double x307 = x306*x51;
    const double x308 = x306*x53;
    const double x309 = x51*x53*x7;

    out.resize(18, 18);
    out(0, 0) = beta*(lame1*x2 + lame2*(2*x2 + x4 + x6));
    out(0, 1) = x9;
    out(0, 2) = x10;
    out(0, 3) = x17;
    out(0, 4) = x20;
    out(0, 5) = x21;
    out(0, 6) = x28;
    out(0, 7) = x30;
    out(0, 8) = x31;
    out(0, 9) = x57;
    out(0, 10) = x65;
    out(0, 11) = x73;
    out(0, 12) = x77;
    out(0, 13) = x79;
    out(0, 14) = x80;
    out(0, 15) = x84;
    out(0, 16) = x86;
    out(0, 17) = x87;
    out(1, 0) = x9;
    out(1, 1) = beta*(lame1*x4 + lame2*(x2 + 2*x4 + x6));
    out(1, 2) = x88;
    out(1, 3) = x91;
    out(1, 4) = x92;
    out(1, 5) = x93;
    out(1, 6) = x95;
    out(1, 7) = x96;
    out(1, 8) = x97;
    out(1, 9) = x98;
    out(1, 10) = x101;
    out(1, 11) = x109;
    out(1, 12) = x111;
    out(1, 13) = x112;
    out(1, 14) = x113;
    out(1, 15) = x115;
    out(1, 16) = x116;
    out(1, 17) = x117;
    out(2, 0) = x10;
    out(2, 1) = x88;
    out(2, 2) = beta*(lame1*x6 + lame2*(x2 + x4 + 2*x6));
    out(2, 3) = x120;
    out(2, 4) = x121;
    out(2, 5) = x122;
    out(2, 6) = x124;
    out(2, 7) = x125;
    out(2, 8) = x126;
    out(2, 9) = x127;
    out(2, 10) = x128;
    out(2, 11) = x131;
    out(2, 12) = x133;
    out(2, 13) = x134;
    out(2, 14) = x135;
    out(2, 15) = x137;
    out(2, 16) = x138;
    out(2, 17) = x139;
    out(3, 0) = x17;
    out(3, 1) = x91;
    out(3, 2) = x120;
    out(3, 3) = beta*(lame1*x140 + lame2*(2*x140 + x141 + x142));
    out(3, 4) = x144;
    out(3, 5) = x145;
    out(3, 6) = x149;
    out(3, 7) = x151;
    out(3, 8) = x152;
    out(3, 9) = x156;
    out(3, 10) = x157;
    out(3, 11) = x158;
    out(3, 12) = x160;
    out(3, 13) = x162;
    out(3, 14) = x164;
    out(3, 15) = x168;
    out(3, 16) = x169;
    out(3, 17) = x170;
    out(4, 0) = x20;
    out(4, 1) = x92;
    out(4, 2) = x121;
    out(4, 3) = x144;
    out(4, 4) = beta*(lame1*x141 + lame2*(x140 + 2*x141 + x142));
    out(4, 5) = x171;
    out(4, 6) = x173;
    out(4, 7) = x174;
    out(4, 8) = x175;
    out(4, 9) = x176;
    out(4, 10) = x177;
    out(4, 11) = x178;
    out(4, 12) = x179;
    out(4, 13) = x181;
    out(4, 14) = x183;
    out(4, 15) = x184;
    out(4, 16) = x185;
    out(4, 17) = x186;
    out(5, 0) = x21;
    out(5, 1) = x93;
    out(5, 2) = x122;
    out(5, 3) = x145;
    out(5, 4) = x171;
    out(5, 5) = beta*(lame1*x142 + lame2*(x140 + x141 + 2*x142));
    out(5, 6) = x188;
    out(5, 7) = x189;
    out(5, 8) = x190;
    out(5, 9) = x191;
    out(5, 10) = x192;
    out(5, 11) = x193;
    out(5, 12) = x194;
    out(5, 13) = x195;
    out(5, 14) = x197;
    out(5, 15) = x198;
    out(5, 16) = x199;
    out(5, 17) = x200;
    out(6, 0) = x28;
    out(6, 1) = x95;
    out(6, 2) = x124;
    out(6, 3) = x149;
    out(6, 4) = x173;
    out(6, 5) = x188;
    out(6, 6) = beta*(lame1*x201 + lame2*(2*x201 + x202 + x203));
    out(6, 7) = x205;
    out(6, 8) = x206;
    out(6, 9) = x210;
    out(6, 10) = x212;
    out(6, 11) = x213;
    out(6, 12) = x217;
    out(6, 13) = x218;
    out(6, 14) = x219;
    out(6, 15) = x220;
    out(6, 16) = x222;
    out(6, 17) = x224;
    out(7, 0) = x30;
    out(7, 1) = x96;
    out(7, 2) = x125;
    out(7, 3) = x151;
    out(7, 4) = x174;
    out(7, 5) = x189;
    out(7, 6) = x205;
    out(7, 7) = beta*(lame1*x202 + lame2*(x201 + 2*x202 + x203));
    out(7, 8) = x225;
    out(7, 9) = x227;
    out(7, 10) = x228;
    out(7, 11) = x229;
    out(7, 12) = x230;
    out(7, 13) = x231;
    out(7, 14) = x232;
    out(7, 15) = x233;
    out(7, 16) = x234;
    out(7, 17) = x236;
    out(8, 0) = x31;
    out(8, 1) = x97;
    out(8, 2) = x126;
    out(8, 3) = x152;
    out(8, 4) = x175;
    out(8, 5) = x190;
    out(8, 6) = x206;
    out(8, 7) = x225;
    out(8, 8) = beta*(lame1*x203 + lame2*(x201 + x202 + 2*x203));
    out(8, 9) = x238;
    out(8, 10) = x239;
    out(8, 11) = x240;
    out(8, 12) = x241;
    out(8, 13) = x242;
    out(8, 14) = x243;
    out(8, 15) = x244;
    out(8, 16) = x245;
    out(8, 17) = x246;
    out(9, 0) = x57;
    out(9, 1) = x98;
    out(9, 2) = x127;
    out(9, 3) = x156;
    out(9, 4) = x176;
    out(9, 5) = x191;
    out(9, 6) = x210;
    out(9, 7) = x227;
    out(9, 8) = x238;
    out(9, 9) = beta*(lame1*x247 + lame2*(2*x247 + x248 + x249));
    out(9, 10) = x251;
    out(9, 11) = x252;
    out(9, 12) = x256;
    out(9, 13) = x258;
    out(9, 14) = x259;
    out(9, 15) = x263;
    out(9, 16) = x264;
    out(9, 17) = x265;
    out(10, 0) = x65;
    out(10, 1) = x101;
    out(10, 2) = x128;
    out(10, 3) = x157;
    out(10, 4) = x177;
    out(10, 5) = x192;
    out(10, 6) = x212;
    out(10, 7) = x228;
    out(10, 8) = x239;
    out(10, 9) = x251;
    out(10, 10) = beta*(lame1*x248 + lame2*(x247 + 2*x248 + x249));
    out(10, 11) = x266;
    out(10, 12) = x268;
    out(10, 13) = x269;
    out(10, 14) = x270;
    out(10, 15) = x271;
    out(10, 16) = x272;
    out(10, 17) = x273;
    out(11, 0) = x73;
    out(11, 1) = x109;
    out(11, 2) = x131;
    out(11, 3) = x158;
    out(11, 4) = x178;
    out(11, 5) = x193;
    out(11, 6) = x213;
    out(11, 7) = x229;
    out(11, 8) = x240;
    out(11, 9) = x252;
    out(11, 10) = x266;
    out(11, 11) = beta*(lame1*x249 + lame2*(x247 + x248 + 2*x249));
    out(11, 12) = x275;
    out(11, 13) = x276;
    out(11, 14) = x277;
    out(11, 15) = x278;
    out(11, 16) = x279;
    out(11, 17) = x280;
    out(12, 0) = x77;
    out(12, 1) = x111;
    out(12, 2) = x133;
    out(12, 3) = x160;
    out(12, 4) = x179;
    out(12, 5) = x194;
    out(12, 6) = x217;
    out(12, 7) = x230;
    out(12, 8) = x241;
    out(12, 9) = x256;
    out(12, 10) = x268;
    out(12, 11) = x275;
    out(12, 12) = beta*(lame1*x281 + lame2*(2*x281 + x282 + x283));
    out(12, 13) = x285;
    out(12, 14) = x286;
    out(12, 15) = x290;
    out(12, 16) = x292;
    out(12, 17) = x293;
    out(13, 0) = x79;
    out(13, 1) = x112;
    out(13, 2) = x134;
    out(13, 3) = x162;
    out(13, 4) = x181;
    out(13, 5) = x195;
    out(13, 6) = x218;
    out(13, 7) = x231;
    out(13, 8) = x242;
    out(13, 9) = x258;
    out(13, 10) = x269;
    out(13, 11) = x276;
    out(13, 12) = x285;
    out(13, 13) = beta*(lame1*x282 + lame2*(x281 + 2*x282 + x283));
    out(13, 14) = x294;
    out(13, 15) = x296;
    out(13, 16) = x297;
    out(13, 17) = x298;
    out(14, 0) = x80;
    out(14, 1) = x113;
    out(14, 2) = x135;
    out(14, 3) = x164;
    out(14, 4) = x183;
    out(14, 5) = x197;
    out(14, 6) = x219;
    out(14, 7) = x232;
    out(14, 8) = x243;
    out(14, 9) = x259;
    out(14, 10) = x270;
    out(14, 11) = x277;
    out(14, 12) = x286;
    out(14, 13) = x294;
    out(14, 14) = beta*(lame1*x283 + lame2*(x281 + x282 + 2*x283));
    out(14, 15) = x300;
    out(14, 16) = x301;
    out(14, 17) = x302;
    out(15, 0) = x84;
    out(15, 1) = x115;
    out(15, 2) = x137;
    out(15, 3) = x168;
    out(15, 4) = x184;
    out(15, 5) = x198;
    out(15, 6) = x220;
    out(15, 7) = x233;
    out(15, 8) = x244;
    out(15, 9) = x263;
    out(15, 10) = x271;
    out(15, 11) = x278;
    out(15, 12) = x290;
    out(15, 13) = x296;
    out(15, 14) = x300;
    out(15, 15) = beta*(lame1*x303 + lame2*(2*x303 + x304 + x305));
    out(15, 16) = x307;
    out(15, 17) = x308;
    out(16, 0) = x86;
    out(16, 1) = x116;
    out(16, 2) = x138;
    out(16, 3) = x169;
    out(16, 4) = x185;
    out(16, 5) = x199;
    out(16, 6) = x222;
    out(16, 7) = x234;
    out(16, 8) = x245;
    out(16, 9) = x264;
    out(16, 10) = x272;
    out(16, 11) = x279;
    out(16, 12) = x292;
    out(16, 13) = x297;
    out(16, 14) = x301;
    out(16, 15) = x307;
    out(16, 16) = beta*(lame1*x304 + lame2*(x303 + 2*x304 + x305));
    out(16, 17) = x309;
    out(17, 0) = x87;
    out(17, 1) = x117;
    out(17, 2) = x139;
    out(17, 3) = x170;
    out(17, 4) = x186;
    out(17, 5) = x200;
    out(17, 6) = x224;
    out(17, 7) = x236;
    out(17, 8) = x246;
    out(17, 9) = x265;
    out(17, 10) = x273;
    out(17, 11) = x280;
    out(17, 12) = x293;
    out(17, 13) = x298;
    out(17, 14) = x302;
    out(17, 15) = x308;
    out(17, 16) = x309;
    out(17, 17) = beta*(lame1*x305 + lame2*(x303 + x304 + 2*x305));

}


# Apollo Lunar Module

```python
import numpy as np
import sympy as sy
sy.init_printing()
```

```python
s = sy.symbols('s', real=False)
J, g = sy.symbols('J, g', real=True, positive=True)

```

```python
A = sy.Matrix([[0,0,0],[1,0,0],[0,g,0]])
B = sy.Matrix([[1/J],[0],[0]])
C = sy.Matrix([[0,0,1]])
A, A*A
```

## Controllability

```python
Ctr = sy.Matrix([[B.T], [B.T*A.T], [B.T*A.T*A.T]]).T
Ctr
```

```python
l0, l1, l2, l3 = sy.symbols('l_0, l_1, l_2, l_3')
L = sy.Matrix([[l1, l2, l3]])
L
```

```python
M = s*sy.eye(3) - (A-B*L)
M
```

```python
char_pol = sy.poly(sy.det(M), s)
```

## Desired closed-loop poles

```python
tauc = sy.symbols('tau_c')
wc = 1/tauc
p1 = -wc
p2 = wc/np.sqrt(2)*complex(-1,1)
p3 = wc/np.sqrt(2)*complex(-1, -1)
p3
```

```python
char_pol_des = sy.poly((s-p1)*(s-p2)*(s-p3), s)
```

```python
char_pol_des
```

```python
eqns = [c_ - cd_ for c_, cd_ in zip(char_pol.coeffs(), char_pol_des.coeffs())]
eqns
```

```python
sol = sy.solve(eqns, [l1, l2, l3])
```

```python
sol
```

```python
2*np.sqrt(2)
```

## The forward gain $l_0$

From transfer function
$$ G_c(s) = l_0 \frac{\frac{g}{J}}{s^3 + \frac{1+\sqrt{2}}{\tau_c}s^2 + \frac{1+\sqrt{2}}{\tau_c^2}s + \frac{1}{\tau_c^3}}$$
and
$$ G_c(0) = 1 $$

```python
l0 = J/g/tauc**3
l0
```

From steady-state solution
$$ 0 = (A-BL)x + l_0Br $$
$$ x = -l_0(A-BL)^{-1}Br $$

```python
dcgainx = -(A-B*L).inv()*B
dcgainx = dcgainx.subs([(l3, sol[l3])])
dcgainx
```

```python
dcgainy = C*dcgainx
l0 = 1/dcgainy[0]
l0
```

## Observer design

```python
k1,k2,k3 = sy.symbols('k_1, k_2, k_3')
K = sy.Matrix([k1,k2,k3])
K
```

```python
o_char_pol = sy.poly(sy.det(s*sy.eye(3) - (A - K*C) ), s)
o_char_pol
```

### Desired observer poles
Separation of n, hence the poles are at n times the distance from the origin of the closed-loop poles

```python
sep = sy.symbols('n', real=True, positive=True)
wc_o = sep*wc
p1_o = -wc_o
p2_o = wc_o/np.sqrt(2)*complex(-1,1)
p3_o = wc_o/np.sqrt(2)*complex(-1, -1)
p3_o
```

```python
o_char_pol_des = sy.poly((s-p1_o)*(s-p2_o)*(s-p3_o), s)
o_char_pol_des
```

```python
eqns = [c_ - cd_ for c_, cd_ in zip(o_char_pol.coeffs(), o_char_pol_des.coeffs())]
eqns
```

```python
sol_o = sy.solve(eqns, [k1, k2, k3])
sol_o
```

```python
C
```

```python
A
```

```python
B
```

```python

```


# SIR model example
\begin{align}
\dot{S} & = \alpha\beta\frac{SI}{N} + \rho_{RS} R + \phi_S\\
\dot{I} & = \alpha\beta\frac{SI}{N} - I\frac{1}{\tau_{IR}} + \phi_I\\
\dot{R} & = I\frac{1}{\tau_{IR}} - \rho_{IR} R + \phi_R\\
\dot{\Phi} & = \sum_{i} (\phi_{i}) \\
N & = N_0 + \Phi \\
N_0 & = S_0+E_0+I_0+R_0  \\
\end{align}

Where:
* $S:$ Susceptible Individuals
* $I:$ Infectious Individuals
* $R:$ Removed Individuals
* $\alpha:$ Average number of contacts per person per time
* $\beta:$ Probability of disease transmission in a contact between a susceptible and an infectious subject
* $N:$ Total population
* $\tau_{EI}:$ Transition time between exposed and infectious
* $\tau_{IR}:$ Transition time between infectious and recovered
* $\rho_{RS}:$ Immunity loss rate. Inverse of Immunity loss rate medium time ($\frac{1}{t_{RS}}$)
* $\phi_S,\phi_E,\phi_I,\phi_R:$ External flux per compartment
* $\Phi$: Total external population flux

```python
# Util libraries
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime

# cv19 libraries
from cv19gm.models.sir import SIR
import cv19gm.utils.cv19functions as cv19functions
```

```python
# For pop-up plots execute this code (optional)
import platform
OS = platform.system()

if OS == 'Linux':
    %matplotlib tk
    print('Linux')
elif OS == 'Windows':
    %matplotlib qt
    print('Windows')
elif OS == 'Darwin':
    %matplotlib tk
    print('Mac (Funciona?)')
```

# Build a basic SIR model using a configuration file

```python
# Input configuration file
config = 'cfg/SIR.toml'
```

```python
# Build simulation object
model = SIR(config = config,beta=0.3)
```

```python
model.solve()
```

```python
# Plot matplotlib
plt.plot(model.t,model.S,label='S')
plt.plot(model.t,model.I,label='I')
plt.plot(model.t,model.R,label='R')
plt.xlim(0,150)
plt.legend(loc=0)
plt.title('SIR states over time')
plt.show()
```

```python
# Show results
model.results
```

```python

```


# Simplification

Boilerplate to make the doctester work.

```
import sys
import os
sys.path.insert(1, os.path.join(os.path.pardir, "ipython_doctester"))
from sympy import *
from ipython_doctester import test
x, y, z = symbols('x y z')
```

For each exercise, fill in the function according to its docstring. Execute the cell to see if you did it right.

## Polynomial/Rational Function Simplification

In each exercise, apply specific simplification functions to get the desired result.

```
@test
def polysimp1(expr):
    """
    >>> polysimp1(cos(x)*sin(x) + cos(x))
    (sin(x) + 1)*cos(x)
    >>> polysimp1(cos(x)*sin(x) + cos(x) + 1)
    (sin(x) + 1)*cos(x) + 1
    """

```

```
@test
def polysimp2(expr):
    """
    >>> polysimp2((2*x + 1)/(x**2 + x))
    1/(x + 1) + 1/x
    >>> polysimp2((x**2 + 3*x + 1)/(x**3 + 2*x**2 + x))
    1/(x**2 + 2*x + 1) + 1/x
    """

```

## Powers

In each exercise, apply specific simplification functions to get the desired result.

```
@test
def powersimp1(expr):
    """
    >>> powersimp1(exp(x)*(exp(y) + 1))
    exp(x) + exp(x + y)
    """

```

```
@test
def powersimp2(expr):
    """
    >>> powersimp2(2**x*x**x)
    (2*x)**x
    >>> powersimp2(x**x*x**x)
    (x**2)**x
    """

```

```
@test
def powersimp3(expr):
    """
    >>> a, b, c = symbols('a b c')
    >>> powersimp3((a**b)**c)
    a**(b*c)
    >>> powersimp3((a**b)**(c + 1))
    a**(b*c + b)
    """

```

## Logs

```
@test
def logsimp1(expr):
    """
    >>> a, b = symbols('a b', positive=True)
    >>> logsimp1(log(x**y*a**b))
    y*log(x) + log(a**b)
    >>> logsimp1(log(x*y*a*b))
    log(x) + log(y) + log(a*b)
    """

```

## Miscellaneous

```
@test
def miscsimp1(expr):
    """
    >>> miscsimp1(sin(x + y))
    2*(-tan(x/2)**2 + 1)*tan(y/2)/((tan(x/2)**2 + 1)*(tan(y/2)**2 + 1)) + 2*(-tan(y/2)**2 + 1)*tan(x/2)/((tan(x/2)**2 + 1)*(tan(y/2)**2 + 1))
    """

```

```
@test
def miscsimp2(expr):
    """
    >>> miscsimp2(gamma(x + 4))
    x**4*gamma(x) + 6*x**3*gamma(x) + 11*x**2*gamma(x) + 6*x*gamma(x)
    """

```

## Continued Fractions

```
def list_to_frac(l):
    expr = Integer(0)
    for i in reversed(l[1:]):
        expr += i
        expr = 1/expr
    return l[0] + expr
```

```
a0, a1, a2, a3, a4 = symbols('a0:5')
```

Determine the list used to create the continued fraction $$\frac{a_{0} a_{1} a_{2} a_{3} a_{4} + a_{0} a_{1} a_{2} + a_{0} a_{3} a_{4} + a_{0} + a_{1} a_{2} a_{3} + a_{1} a_{3} a_{4} + a_{1} + a_{3}}{a_{0} a_{1} a_{2} a_{4} + a_{0} a_{4} + a_{1} a_{2} + a_{1} a_{4} + 1}.$$

```
@test
def continued_frac():
    """
    Determine the original list used to create the fraction.

    Return the original list from this function.

    >>> orig_frac = (a0*a1*a2*a3*a4 + a0*a1*a2 + a0*a3*a4 + a0 + a1*a2*a3 + a1*a3*a4 + a1 + a3)/(a0*a1*a2*a4 + a0*a4 + a1*a2 + a1*a4 + 1)
    >>> pprint(orig_frac, use_unicode=False, wrap_line=False)
    a0*a1*a2*a3*a4 + a0*a1*a2 + a0*a3*a4 + a0 + a1*a2*a3 + a1*a3*a4 + a1 + a3
    -------------------------------------------------------------------------
                     a0*a1*a2*a4 + a0*a4 + a1*a2 + a1*a4 + 1
    >>> cancel(list_to_frac(continued_frac())) == orig_frac
    True
    """

```

```

```


# PRAKTIKUM 12
`Solusi Persamaan Differensial Biasa (PDB) 1`

# Metode Euler
Metode Euler merupakan metode paling klasik yang digunakan untuk menghitung solusi numerik dari persamaan differensial biasa dengan masalah nilai awal. Metode Euler memiliki kompleksitas galat $ O(h) $. Bentuk umum iterasi __metode Euler__ adalah

\begin{equation}\label{eq:12 euler}
t_{k+1} = t_k+h\ ,
\ \ \ \ \
y_{k+1}=y_k+h f(t_k,y_k)
\ \ \ \ \
\text{untuk }
k=0,1,\dots, M-1
\end{equation}

```julia
#%METODE EULER
# sol = euler(f,a,b,y0,M)
# Input  : f    -> fungsi f(t,y)
#          a,b  -> batas ujung selang I(t) : a<=t<=b
#          y0   -> nilai awal y(a)=y0
#          M    -> banyaknya sub-interval
# Output : sol  -> solusi PD, sol=[T,Y]
function euler(f,a,b,y0,M)
    M = Int(M)
    h = (b-a)/M;
    T = a:h:b;
    Y = Array{Float64}(undef,length(T),1)
    Y[1] = y0;
    # Mulai langkah iterasi euler
    for k = 1:M
        # Rumus iterasi euler
        Y[k+1] = Y[k]+h*f(T[k],Y[k])
    end
    sol = [T Y]
end
```

### Contoh 1
Diberikan persamaan differensial

$$ \dfrac{dy}{dt} = \dfrac{t-y}{2} ,\ \text{ dengan } y(0)=1 $$

Berikut merupakan langkah-langkah untuk menghitung solusi numerik dari persamaan di atas dengan $ h=1/2 $ dan $ h=1/4 $ dan galat hampiran terhadap solusi eksaknya, yaitu $ y(t)=3e^{-t/2}-2+t $ menggunakan metode Euler pada interval $[0,3]$.

```julia
a = 0;
b = 3;
f(t,y) = (t-y)/2;
y0 = 1;
h = 1/2;
M = (b-a)/h;
sol1 = euler(f,a,b,y0,M)
```

```julia
h = 1/4;
M = (b-a)/h;
sol2 = euler(f,a,b,y0,M)
```

```julia
using Plots
```

```julia
y(t) = 3*exp(-t/2)-2+t;
t1 = sol1[:,1];
y1 = sol1[:,2];
t2 = sol2[:,1];
y2 = sol2[:,2];
t = a:0.01:b
plot(t1,y1,label="solusi dengan h = 0.5",legend=:topleft)
plot!(t2,y2,label="solusi dengan h=0.25")
plot!(t,y.(t),label="solusi eksak")
title!("solusi PDB dengan metode Euler")
```

```julia
y(t) = 3*exp(-t/2)-2+t;
e1 = abs.(y1 - y.(t1));
e2 = abs.(y2 - y.(t2));
plot(t1,e1,label="galat dengan h = 0.5",legend=:bottomright)
plot!(t2,e2,label="galat dengan h=0.25")
title!("galat solusi PDB dengan metode Euler")
```

Berdasarkan hasil penghitungan galat, nilai galat akhir global untuk hampiran dengan $ h=1/2 $ dan $ h=1/4 $ masing-masing adalah $ 0.13545 $ dan $ 0.065139 $. Dengan demikian, kompleksitas komputasi untuk metode Euler adalah $ O(h) $.

# Metode Heun
Metode Heun merupakan pengembangan dari metode Euler. Metode Heun memiliki kompleksitas galat yang lebih baik daripada metode Euler, yaitu $ O(h^2) $. Bentuk umum iterasi __metode Heun__ adalah
\begin{align}\label{eq:12 heun}
\begin{split}
t_{k+1}&=t_k+h \\
p_{k+1}&=y_k+h\ f(t_k,y_k) \\
y_{k+1}&=y_k+\dfrac{h}{2}(f(t_k,y_k)+f(t_{k+1},p_{k+1}))
\end{split}
\end{align}

```julia
#%%METODE HEUN
#% sol = heun(f,a,b,y0,M)
#% Input  : f    -> fungsi f(t,y)
#%          a,b  -> batas ujung selang I(t) : a<=t<=b
#%          y0   -> nilai awal y(a)=y0
#%          M    -> banyaknya sub-interval
#% Output : sol  -> solusi PD, sol=[T,Y]
function heun(f,a,b,y0,M)
    M = Int(M)
    h = (b-a)/M;
    T = a:h:b;
    Y = Array{Float64}(undef,length(T),1)
    P = Array{Float64}(undef,length(T),1)
    Y[1] = y0;
    # Mulai langkah iterasi Heun
    for k = 1:M
        #% Rumus iterasi Heun
        P[k+1]=Y[k]+h*f(T[k],Y[k]);
        Y[k+1]=Y[k]+h/2*(f(T[k],Y[k])+f(T[k+1],P[k+1]));
    end
    sol = [T Y];
end

```

### Contoh 2
Diberikan persamaan differensial
$$ \dfrac{dy}{dt} = \dfrac{t-y}{2} ,\ \text{ dengan } y(0)=1 $$
Berikut merupakan langkah-langkah untuk menghitung solusi numerik dari persamaan di atas dengan $ h=1/2 $ dan $ h=1/4 $ dan galat hampiran terhadap solusi eksaknya, yaitu $ y(t)=3e^{-t/2}-2+t $ menggunakan metode Heun.

```julia
a = 0;
b = 3;
f(t,y) = (t-y)/2;
y0 = 1;
h = 1/2;
M = (b-a)/h;
sol1 = heun(f,a,b,y0,M)
```

```julia
h = 1/4;
M = (b-a)/h;
sol2 = heun(f,a,b,y0,M)
```

```julia
y(t) = 3*exp(-t/2)-2+t;
t1 = sol1[:,1];
y1 = sol1[:,2];
t2 = sol2[:,1];
y2 = sol2[:,2];
t = a:0.01:b
plot(t1,y1,label="solusi dengan h = 0.5",legend=:topleft)
plot!(t2,y2,label="solusi dengan h = 0.25")
plot!(t,y.(t),label="solusi eksak")
title!("solusi PDB dengan metode Heun")
```

```julia
y(t) = 3*exp(-t/2)-2+t;
e1 = abs.(y1 - y.(t1));
e2 = abs.(y2 - y.(t2));
plot(t1,e1,label="galat dengan h = 0.5",legend=:bottomright)
plot!(t2,e2,label="galat dengan h=0.25")
title!("galat solusi PDB dengan metode Heun")
```

Berdasarkan hasil penghitungan galat, nilai galat akhir global untuk hampiran dengan $ h=1/2 $ dan $ h=1/4 $ masing-masing adalah $ 0.012731 $ dan $ 0.0028783 $. Dengan demikian, kompleksitas komputasi untuk metode Heun adalah $ O(h^2) $.

# Metode Deret Taylor
Metode Euler dan Heun yang telah dipelajari masih kompleksitas yang rendah yaitu $ O(h) $ dan $ O(h^2) $. Dengan memanfaatkan teorema deret Taylor, kompleksitas komputasi untuk mencari solusi numerik persamaan differensial dapat ditingkatkan sesuai dengan orde yang dipilih. Pada praktikum ini, akan digunakan deret Taylor orde-4 untuk mencari solusi numerik persamaan differensial dengan kompleksitas komputasi $ O(h^4) $.

Bentuk umum dari metode Taylor ordo $ N $ adalah

\begin{equation}\label{eq:12 taylor}
y_{k+1}=y_k+d_1h+\dfrac{d_2h^2}{2!}+\dfrac{d_3h^3}{3!}+\dots+\dfrac{d_Nh^N}{N!}
\end{equation}

dengan $ d_j=y^{(j)}(t_k) $ untuk $ j=1,2,\dots,N $ pada setiap langkah $ k=0,1,\dots,M-1 $.

```julia
function taylor(df,a,b,y0,M)
  M = Int(M)
  h = (b-a)/M;
  T = a:h:b;
  Y = Array{Float64}(undef,length(T),1)
  Y[1]=y0;
  for j = 1:M
    D = df(T[j],Y[j]);
    Y[j+1]=Y[j]+h*(D[1]+h*(D[2]/2+h*(D[3]/6+h*D[4]/24)));
  end
  sol = [T Y];
end
```

### Contoh 3
Diberikan persamaan differensial

$$ \dfrac{dy}{dt} = \dfrac{t-y}{2} ,\ \text{ dengan } y(0)=1 $$

Berikut merupakan langkah-langkah untuk menghitung solusi numerik dari persamaan di atas dengan $ h=1/2 $ dan $ h=1/4 $ dan galat hampiran terhadap solusi eksaknya, yaitu $ y(t)=3e^{-t/2}-2+t $ menggunakan metode deret Taylor orde-4.

**Langkah 1**. Pendefinisian fungsi `df` yang berisi $ y',\ y'',\ y^{(3)},$ dan $y^{(4)} $.

Perhatikan bahwa

$\begin{align*}
	y'(t) &= \dfrac{t-y}{2} \\
	y^{(2)}(t) &= \dfrac{d}{dt}\left( \dfrac{t-y}{2} \right) =\dfrac{1-y'}{2}=\dfrac{2-t+y}{4}\\
	y^{(3)}(t) &= \dfrac{d}{dt}\left( \dfrac{2-t+y}{4} \right) =\dfrac{0-1+y'}{4}=\dfrac{-2+t-y}{8}\\
	y^{(4)}(t) &= \dfrac{d}{dt}\left( \dfrac{-2+t-y}{8} \right) =\dfrac{-0+1-y'}{8}=\dfrac{2-t+y}{16}&&
\end{align*}$

Dengan demikian diperoleh fungsi `df` yaitu

```julia
function df(t,y)
	y1 = (t-y)/2;
	y2 = (2-t+y)/4;
	y3 = (-2+t-y)/8;
	y4 = (2-t+y)/16;
	z = [y1,y2,y3,y4];
end
```

**Langkah 2**. Penghitungan solusi PD pada selang $ [0,3] $ dengan ukuran langkah $ h=1/2 $ dan $ 1/4 $ menggunakan metode deret Taylor orde-4.

```julia
a = 0;
b = 3;
y0 = 1;
h = 1/2;
M = (b-a)/h;
sol1 = taylor(df,a,b,y0,M)
```

```julia
h = 1/4;
M = (b-a)/h;
sol2 = taylor(df,a,b,y0,M)
```

```julia
y(t) = 3*exp(-t/2)-2+t;
t1 = sol1[:,1];
y1 = sol1[:,2];
t2 = sol2[:,1];
y2 = sol2[:,2];
t = a:0.01:b
plot(t1,y1,label="solusi dengan h = 0.5",legend=:topleft)
plot!(t2,y2,label="solusi dengan h = 0.25")
plot!(t,y.(t),label="solusi eksak")
title!("solusi PDB dengan metode deret Taylor")
```

```julia
y(t) = 3*exp(-t/2)-2+t;
e1 = abs.(y1 - y.(t1));
e2 = abs.(y2 - y.(t2));
plot(t1,e1,label="galat dengan h = 0.5",legend=:right)
plot!(t2,e2,label="galat dengan h=0.25")
title!("galat solusi PDB dengan metode deret Taylor")
```

# Metode Runge-Kutta Orde-4
Salah satu metode yang populer dan sering digunakan untuk menyelesaikan masalah PDB adalah metode Runge-Kutta. Metode ini memiliki kompleksitas komputasi sesuai orde yang digunakan. Untuk Runge-Kutta orde-4, kompleksitas galat yang dihasilkan adalah $ O(h^4) $.

Kelemahan terbesar mencari solusi numerik menggunakan deret Taylor adalah untuk menghasilkan metode dengan kompleksitas $ O(h^4) $, dibutuhkan persamaan fungsi turunan hingga orde ke-4. Sementara itu, banyak persamaan differensial yang sulit untuk dicari turunannya. Salah satu metode yang dapat mengatasinya adalah metode Runge-Kutta orde-4. Metode ini memiliki kompleksitas komputasi $ O(h^4) $ tanpa harus mendefinisikan fungsi turunan hingga orde-4.

Bentuk umum metode Runge-Kutta ordo 4 untuk menyelesaikan masalah nilai awal $ y'(t)=f(t,y) $ pada $ [t_0,t_M] $ adalah

\begin{equation}\label{eq:12 rk}
y_{k+1}=y_k+\dfrac{h}{6}(f_1+2f_2+2f_3+f_4)
\end{equation}
dengan
\begin{align}\label{eq:12 rk 1}
\begin{split}
f_1 &= f(t_k,y_k) \\
f_2 &= f\left( t_k+\dfrac{h}{2},\ y_k+\dfrac{h}{2}f_1 \right) \\
f_3 &= f\left( t_k+\dfrac{h}{2},\ y_k+\dfrac{h}{2}f_2 \right) \\
f_4 &= f\left( t_k+h,\ y_k+h\ f_3 \right) \\
\end{split}
\end{align}

```julia
#%%METODE RUNGE-KUTTA ORDE 4
#% sol = rungekutta(f,a,b,y0,M)
#% Input  : f    -> fungsi f(t,y)
#%          a,b  -> batas ujung selang I(t) : a<=t<=b
#%          y0   -> nilai awal y(a)=y0
#%          M    -> banyaknya sub-interval
#% Output : sol  -> solusi PD, sol=[T,Y]
function rungekutta(f,a,b,y0,M)
    M = Int(M)
    h = (b-a)/M;
    T = a:h:b;
    Y = Array{Float64}(undef,length(T),1)
    Y[1] = y0;
    for k = 1:M
        f1 = f(T[k]     ,Y[k]        );
        f2 = f(T[k]+h/2 ,Y[k]+f1*h/2 );
        f3 = f(T[k]+h/2 ,Y[k]+f2*h/2 );
        f4 = f(T[k]+h   ,Y[k]+f3*h   );
        Y[k+1] = Y[k] + h/6*(f1+2*f2+2*f3+f4);
    end
    sol = [T Y];
end

```

### Contoh 4
Diberikan persamaan differensial
$$ \dfrac{dy}{dt} = \dfrac{t-y}{2} ,\ \text{ dengan } y(0)=1 $$
Berikut merupakan langkah-langkah untuk menghitung solusi numerik dari persamaan di atas dengan $ h=1/2 $ dan $ h=1/4 $ dan galat hampiran terhadap solusi eksaknya, yaitu $ y(t)=3e^{-t/2}-2+t $ menggunakan metode Runge-Kutta orde-4.

```julia
a = 0;
b = 3;
f(t,y) = (t-y)/2;
y0 = 1;
h = 1/2;
M = (b-a)/h;
sol1 = rungekutta(f,a,b,y0,M)
```

```julia
h = 1/4;
M = (b-a)/h;
sol2 = rungekutta(f,a,b,y0,M)
```

```julia
y(t) = 3*exp(-t/2)-2+t;
t1 = sol1[:,1];
y1 = sol1[:,2];
t2 = sol2[:,1];
y2 = sol2[:,2];
t = a:0.01:b
plot(t1,y1,label="solusi dengan h = 0.5",legend=:topleft)
plot!(t2,y2,label="solusi dengan h = 0.25")
plot!(t,y.(t),label="solusi eksak")
title!("solusi PDB dengan metode Runge-Kutta")
```

```julia
y(t) = 3*exp(-t/2)-2+t;
e1 = abs.(y1 - y.(t1));
e2 = abs.(y2 - y.(t2));
plot(t1,e1,label="galat dengan h = 0.5",legend=:right)
plot!(t2,e2,label="galat dengan h=0.25")
title!("galat solusi PDB dengan metode Runge-Kutta")
```

Berdasarkan hasil penghitungan galat, nilai galat akhir global untuk hampiran dengan $ h=1/2 $ dan $ h=1/4 $ masing-masing adalah $ 0.000040281 $ dan $ 0.0000022674 $. Dengan demikian, kompleksitas komputasi untuk metode Rungke-Kutta Orde-4 adalah $ O(h^4) $.

# Soal Latihan
Kerjakan soal berikut pada saat kegiatan praktikum berlangsung.

`Nama: ________`

`NIM: ________`

### Soal 1
Diberikan persamaan differensial
$$ \dfrac{dy}{dt} = -y-2t-1 ,\ \text{ dengan } y(0)=2 $$
Gunakan metode Euler untuk menyelesaikan PD di atas untuk $ h=1/2 $, $ h=1/4 $ dan $ h=1/8 $, kemudian bandingkan dengan solusi eksaknya adalah $ y(t)=e^{-t}-2t+1 $ dan hitung nilai galat akhir global masing-masing hampiran dengan langkah-langkah seperti pada **Contoh 1**.

```julia

```

### Soal 2
Diberikan persamaan differensial

$$ \dfrac{dy}{dt} = -y-2t-1 ,\ \text{ dengan } y(0)=2 $$

Gunakan metode Heun untuk menyelesaikan PD di atas untuk $ h=1/2 $, $ h=1/4 $ dan $ h=1/8 $, kemudian bandingkan dengan solusi eksaknya adalah $ y(t)=e^{-t}-2t+1 $ dan hitung nilai galat akhir global masing-masing hampiran dengan langkah-langkah seperti pada **Contoh 2**.

```julia

```

### Soal 3
Diberikan persamaan differensial

$$ \dfrac{dy}{dt} = -y-2t-1 ,\ \text{ dengan } y(0)=2 $$

Gunakan metode deret Taylor Orde-4 untuk menyelesaikan PD di atas untuk ukuran langkah $ h=1/2 $, $ h=1/4 $ dan $ h=1/8 $, kemudian bandingkan dengan solusi eksaknya yaitu $ y(t)=e^{-t}-2t+1 $ dan hitung pula nilai galat akhir global masing-masing hampiran dengan langkah-langkah seperti pada **Contoh 3**.

```julia

```

### Soal 4
Diberikan persamaan differensial
$$ \dfrac{dy}{dt} = -y-2t-1 ,\ \text{ dengan } y(0)=2 $$
Gunakan metode Runge-Kutta Orde-4 untuk menyelesaikan PD di atas untuk ukuran langkah $ h=1/2 $, $ h=1/4 $ dan $ h=1/8 $, kemudian bandingkan dengan solusi eksaknya yaitu $ y(t)=e^{-t}-2t+1 $ dan hitung nilai galat akhir global masing-masing hampiran dengan langkah-langkah seperti pada **Contoh 4**.

```julia

```


```python
import numpy as np
from scipy import io
from scipy import sparse
from scipy.sparse import csgraph
from scipy import signal
from scipy import linalg
from matplotlib import pyplot as plt
from matplotlib import collections
from sklearn.cluster import KMeans
import mpl_toolkits.mplot3d.art3d as art3d
import seaborn as sns

%matplotlib inline
```

```python
sns.set_palette('husl')
```

# Load data

```python
minn = io.loadmat('../data/minnesota.mat')
Aroads = minn['Problem'][0][0][2]
xy = minn['Problem'][0][0][8][0][0][0]

numcomp, components = csgraph.connected_components(Aroads)
compsize = np.max(np.bincount(components))
biggestcomp = np.argmax(np.bincount(components))
in_component = np.where(components == biggestcomp)[0]

Aroads = Aroads.todense()
Aroads = Aroads[in_component][:, in_component]
Aroads = sparse.csc_matrix(Aroads)

xy = xy[in_component]

links = np.where(Aroads.todense())
startcoords = xy[links[0]]
endcoords = xy[links[1]]
lines = [[tuple(x), tuple(y)] for x, y in zip(startcoords, endcoords)]
```

# Use a Ricker wavelet

```python
tau = 11
hat = signal.ricker(1000, 100)[500:]
```

```python
fig, ax = plt.subplots(figsize=(8, 4))
plt.plot(hat)
plt.xlabel('k')
plt.ylabel(r'$\psi$')
plt.title('Ricker filter')
plt.savefig('../img/wavelet_filter_0.pdf', bbox_inches='tight')
```

# Implement wavelet transform

The wavelet function $\Psi$ at scale k and center vertex i is defined by:

\begin{equation}
 \Psi_{k,i}^{CKWT} (j) = \frac{a_{k, \tau}}{|\partial N_{(i, \tau)}|}, \forall j \in \partial N(i, \tau)
\end{equation}

Where $\partial N_{(i, \tau)}$ is the set of all vertices $j \in N$ such that the geodesic distance between $i$ and $j$ is $\tau$. $a_{k, \tau}$ is a set of coefficients that approximate the continuous wavelet function.

The full wavelet transform is given by stacking each individual wavelet function:

\begin{equation}
  \mathbf{\Psi}_{k}^{CKWT} = [\Psi_{k,1}^{CKWT} ; \Psi_{k,2}^{CKWT} \cdots \Psi_{k,N}^{CKWT}]
\end{equation}

```python
def ckwt_wavelet(A, tau, h):
    '''
    A : adjacency matrix
    tau: scale factor
    h: continuous wavelet filter
    '''

    # Construct graph wavelet filter, W
    W = np.zeros(A.shape).astype(float)
    # Generate geodesic distances
    spath = csgraph.shortest_path(A, directed=False, unweighted=True).astype(int)
    # Resample filter
    if not (h.size % tau):
        hm = h.reshape(tau, -1).mean(axis=1)
    else:
        hm = h[:-(h.size % tau)].reshape(tau, -1).mean(axis=1)

    for i in range(W.shape[0]):
        # N: histogram of distances from i
        # N_t: Number of vertices within k hops of i for all k < tau
        N = np.bincount(spath[i, :])
        N_t = np.where(spath[i, :] < tau, N[spath[i, :]], i)
        mask = (spath[i, :] < tau)
        # a : wavelet coefficients
        a = np.zeros_like(spath[i, :]).astype(float)
        a[mask] = hm[spath[i, :][mask]] / N_t[mask].astype(float)
        W[:, i] = a
    return W
```

```python
# Generate wavelet transform for the roads graph
W = ckwt_wavelet(Aroads, tau, hat)
```

```python
# Generate localized piecewise graph signal
L = csgraph.laplacian(Aroads).todense()
l, u = np.linalg.eigh(L)

# Signal g with strong discontinuity
kmeans = KMeans(n_clusters=3, random_state=1).fit(u[:, :3])
g = np.where(kmeans.labels_ == 0, 1, -1) + 0.1*np.random.randn(Aroads.shape[0])

# Fourier transform of g
ghat = np.asarray(u.T @ g)
```

# Plot filtering result with a scaling factor of 10

```python
# gg is the result of applying the wavelet transform to the signal
gg = W.T @ g
```

```python
fig, ax = plt.subplots(1, 2, figsize=(12, 4))

lc = collections.LineCollection(lines, linewidths=1, colors='k')

li = ax[0].add_collection(lc)
sc = ax[0].scatter(xy[:, 0], xy[:, 1], s=20, c=g, alpha=0.5,
                zorder=2, cmap='seismic', vmin=g.min(), vmax=g.max())
plt.colorbar(sc, ax=ax[0])

lc = collections.LineCollection(lines, linewidths=1, colors='k')

li = ax[1].add_collection(lc)
sc = ax[1].scatter(xy[:, 0], xy[:, 1], s=20, c=gg, alpha=1,
                zorder=2, cmap='seismic', vmin=-0.1, vmax=0.1)
plt.colorbar(sc, ax=ax[1])

ax[0].set_title(r'$f$')
ax[0].set_xlabel('Longitude')
ax[0].set_ylabel('Latitude')

ax[1].set_title(r'$\Psi_{10}^{CKWT} f$')
ax[1].set_xlabel('Longitude')
ax[1].set_ylabel('Latitude')
plt.savefig('../img/wavelet_filter_1.pdf', bbox_inches='tight')
```

# Plot filtering result with a scaling factor of 4

```python
tau = 5
W = ckwt_wavelet(Aroads, tau, hat)
gg = W.T @ g
```

```python
fig, ax = plt.subplots(1, 2, figsize=(12, 4))

lc = collections.LineCollection(lines, linewidths=1, colors='k')

li = ax[0].add_collection(lc)
sc = ax[0].scatter(xy[:, 0], xy[:, 1], s=20, c=g, alpha=0.8,
                zorder=2, cmap='seismic', vmin=g.min(), vmax=g.max())
plt.colorbar(sc, ax=ax[0])

lc = collections.LineCollection(lines, linewidths=1, colors='k')

li = ax[1].add_collection(lc)
sc = ax[1].scatter(xy[:, 0], xy[:, 1], s=20, c=gg, alpha=0.8,
                zorder=2, cmap='seismic', vmin=-0.1, vmax=0.1)
plt.colorbar(sc, ax=ax[1])

ax[0].set_title(r'$f$')
ax[0].set_xlabel('Longitude')
ax[0].set_ylabel('Latitude')

ax[1].set_title(r'$\Psi_{4}^{CKWT} f$')
ax[1].set_xlabel('Longitude')
ax[1].set_ylabel('Latitude')
plt.savefig('../img/wavelet_filter_2.pdf', bbox_inches='tight')
```

# Compute spatial and spectral spreads of signal

#### Spatial spread of a signal around a vertex

The spatial spread of the signal $f$ around a center vertex $i$ is given by:

\begin{equation}
\Delta_{G, i}^2(f) = \frac{1}{||f||_2^2} \sum_{j \in V} [d_G(i,j)]^2 [f(j)]^2
\end{equation}

Where $d_G(i,j)$ is the geodesic (shortest path) distance between vertices i and j.

#### Spatial spread of a graph signal

The spatial spread of the graph signal is the minimum spatial spread across the vertices:

\begin{equation}
\Delta_{G}^2(f) = \min_{i \in V} \{ \Delta_{G, i}^2(f) \}
\end{equation}

#### Spectral spread of a graph signal

The spectral spread of the graph signal is given by:

\begin{equation}
\Delta_{\sigma}^2(f) = \min_{i \in V} \bigg\{ \frac{1}{||f||_2^2} \sum_{\lambda \in \sigma(L)} [\sqrt{\lambda} - \sqrt{\mu}]^2 [\hat{f}(\lambda)]^2 \bigg\}
\end{equation}

```python
# Compute geodesic distances for all vertices
spath = csgraph.shortest_path(Aroads, directed=False, unweighted=True).astype(int)
```

```python
def spatial_spreads(g, spath=None, A=None):
    '''
    g : input signal
    spath : pairwise geodesic distances (distance matrix of A)
    A : adjacency matrix
    '''
    if spath is None:
        spath = csgraph.shortest_path(A, directed=False, unweighted=True).astype(int)
    return ((spath**2 * g**2).T).sum(axis=0) / np.linalg.norm(g)**2

def spectral_spreads(ghat, l, mu=None):
    '''
    ghat : input kernel
    l : eigenvalues of the laplacian
    mu : initial estimates for mean
    '''
    if mu is None:
        mu = np.linspace(0, l.max(), len(l))
    return ((ghat.reshape(ghat.size, 1)**2 * (np.sqrt((l.reshape(l.size, 1)))
             - np.sqrt(mu.reshape(1, mu.size)))**2).sum(axis=0) /
            np.linalg.norm(ghat)**2)
```

```python
# Compute the spatial spreads for each vertex, i
spatial_spreads_g = spatial_spreads(g, spath)
# Compute graph spatial spread
spatial_ix_min = np.argmin(spatial_spreads_g)
graph_spatial_spread = spatial_spreads_g[spatial_ix_min]
print("Min vertex: ", spatial_ix_min)
print("Graph spatial spread: ", graph_spatial_spread)
```

    Min vertex:  1353
    Graph spatial spread:  799.739558509

```python
# Compute the spectral spreads for each candidate mu
mu = np.linspace(0, l.max(), 10000)
spectral_spreads_ghat = spectral_spreads(ghat, l, mu)
# Compute the graph spectral spread
spectral_ix_min = np.argmin(spectral_spreads_ghat)
graph_spectral_spread = spectral_spreads_ghat[spectral_ix_min]
print("Min lambda: ", spectral_ix_min)
print("Graph spectral spread: ", graph_spectral_spread)
```

    Min lambda:  16
    Graph spectral spread:  0.0636308311967

```python
fig, ax = plt.subplots(figsize=(8, 4))

lc = collections.LineCollection(lines, linewidths=1, colors='k')

li = ax.add_collection(lc)
sc = ax.scatter(xy[:, 0], xy[:, 1], s=20, c=spatial_spreads_g, alpha=0.5,
                zorder=2, cmap='seismic', vmin=spatial_spreads_g.min(),
                   vmax=spatial_spreads_g.max())
plt.colorbar(sc, ax=ax)

lc = collections.LineCollection(lines, linewidths=1, colors='k')

ax.set_title(r'$\Delta^2_G(f)$')
ax.set_xlabel('Longitude')
ax.set_ylabel('Latitude')
plt.savefig('../img/wavelet_filter_3.pdf', bbox_inches='tight')
```

# Compute spatial spread of wavelet transform

Compute the spatial spread of a transform $\Psi_k$ by taking an average over all scale k wavelet functions of the spatial spreads around each center vertex i.

```python
# Spatial spreads of wavelet transform when k=4
# Note that because it will always be minimum at i, we can simplify the calculation
spatial_spreads_w = (spath**2 * W**2).sum(axis=0) / np.sum(W**2, axis=0)
```

```python
fig, ax = plt.subplots(figsize=(8, 4))

lc = collections.LineCollection(lines, linewidths=1, colors='k')

li = ax.add_collection(lc)
sc = ax.scatter(xy[:, 0], xy[:, 1], s=20, c=spatial_spreads_w, alpha=0.5,
                zorder=2, cmap='seismic', vmin=spatial_spreads_w.min(),
                   vmax=spatial_spreads_w.max())
plt.colorbar(sc, ax=ax)

lc = collections.LineCollection(lines, linewidths=1, colors='k')

ax.set_title(r'$\Delta^2_G(\Psi_{4}^{CKWT})$')
ax.set_xlabel('Longitude')
ax.set_ylabel('Latitude')
plt.savefig('../img/wavelet_filter_4.pdf', bbox_inches='tight')
```

# Compute spatial and spectral spreads of wavelet transform at multiple scales

```python
# Compute spatial spreads for varying k
all_spatial_spreads = []
k_range = np.arange(8, 30)

for tau in k_range:
    W = ckwt_wavelet(Aroads, tau, hat)
    spatial_spreads_w = (spath**2 * W**2).sum(axis=0) / np.sum(W**2, axis=0)
    mean_spatial_spread = spatial_spreads_w.mean()
    all_spatial_spreads.append(mean_spatial_spread)

all_spatial_spreads = np.asarray(all_spatial_spreads)
```

```python
plt.plot(k_range - 1, all_spatial_spreads, marker='o', markersize=4)
plt.xlabel(r'$k$')
plt.ylabel('Spatial spread')
plt.title('Spatial spread vs. k')
xt = plt.xticks(np.arange(k_range.min(), k_range.max(), 2))
plt.savefig('../img/wavelet_filter_5.pdf', bbox_inches='tight')
```

```python
# Compute spectral spreads for varying k
all_spectral_spreads = []
k_range = np.arange(8, 30)

for tau in k_range:
    W = ckwt_wavelet(Aroads, tau, hat)
    What = np.asarray((np.conj(u).T @ W))
    Psihat = (What**2).sum(axis=1) / len(What)
    spectral_spreads_what = spectral_spreads(Psihat, l, mu)
    all_spectral_spreads.append(spectral_spreads_what.min())

all_spectral_spreads = np.asarray(all_spectral_spreads)
```

```python
plt.plot(k_range - 1, all_spectral_spreads, marker='o', markersize=4)
plt.xlabel(r'$k$')
plt.ylabel('Spectral spread')
plt.title('Spectral spread vs. k')
xt = plt.xticks(np.arange(k_range.min(), k_range.max(), 2))
plt.savefig('../img/wavelet_filter_6.pdf', bbox_inches='tight')
```

```python
plt.plot(all_spectral_spreads, all_spatial_spreads, marker='o', markersize=4)
plt.xlabel(r'Spectral spread')
plt.ylabel('Spatial spread')
plt.title('Spectral spread vs. spatial spread')
plt.savefig('../img/wavelet_filter_7.pdf', bbox_inches='tight')
```

```python

```


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

#### Détails de la transformation d'équation partielle en équation différentielle ordinaire.

#### Pour commencer la partie b), je fais calculer les dérivées qui me serviront à faire la transformation, je ferai ensuite le reste à la main.

```python
#
# Pierre Proulx
#
#
# Préparation de l'affichage et des outils de calcul symbolique
#
import sympy as sp
from IPython.display import *
sp.init_printing(use_latex=True)
%matplotlib inline
eta,x,z=sp.symbols('eta x z')
f=sp.Function('f')(eta)
C_A=sp.Function('C_A')(x,z)
C_A0,x,z,D_AB,V_m=sp.symbols('C_A0,x,z,D_AB,V_m')
eq18511=sp.Derivative(C_A,z)-D_AB/V_m*sp.Derivative(C_A,x,x)
display(eq18511)
dfdeta=sp.diff(f,eta)
d2fdeta2=sp.diff(f,eta,eta)
eta=x/(4*D_AB/V_m*z)**(1/2)
display('eta',eta)
detadx=eta.diff(x)
display('deta/dx',detadx)
detadx=eta/x
detadz=eta.diff(z)
display('deta/dz',detadz)
detadz=-1/2*eta/z
display('deta/dz',detadz)
display('df/deta',dfdeta*detadz)
display('d2f/deta2',d2fdeta2*detadx**2)
display("en substituant dans l'équation originale")
eq=-D_AB/V_m*d2fdeta2*detadx**2+dfdeta*detadz
display(eq)
display("en substituant dans l'équation originale")
eq=-D_AB/V_m*d2fdeta2*detadx**2+dfdeta*detadz
display(eq)
```

#### Je fais la suite à la main, cela me semble plus facile.
### $- \frac {D_{AB}}{V_m} \frac {\partial^2 C_A} {\partial x^2} + \frac {\partial C_A} {\partial z}= 0$

### $- \frac {D_{AB}}{V_m}  \frac {\partial^2 C_A} {\partial \eta ^2} \left ( \frac {d \eta}{ dx} \right )^2 \frac {1}{ C_{A0}} + \frac {\partial C_A} {\partial \eta } \frac {d \eta}{ dz} \frac {1}{ C_{A0}}= 0$

### $- \frac {D_{AB}}{V_m} \frac {d^2 f} {d \eta^2} \left ( \frac {d \eta}{ dx} \right )^2 + \frac {df} {d \eta} \frac {d \eta}{ dz} = 0$

```python
eta=sp.symbols('eta')   # redéfinir pour avoir l'affichage de l'équation différentielle
f=sp.Function('f')(eta)
eq=sp.diff(f,eta,eta)+2*eta**2*sp.diff(f,eta)
display(eq)
f=sp.dsolve(eq,f)
display(f)
f=f.rhs
cl1=sp.Eq(f.subs(eta,0)-1,0)
cl2=sp.Eq(f.subs(eta,sp.oo))
display(cl1,cl2)
constantes=sp.solve([cl1,cl2],sp.symbols('C1 C2'))
display(constantes)
fsolution=f.subs(constantes)
display(fsolution)
import matplotlib.pyplot as plt
%matplotlib inline
sp.plot(fsolution,(eta,0,2))
```


```
import sympy
from google.colab.output._publish import javascript
from IPython.display import Math
javascript(url="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=default")

def disp(s):
    display(Math(sympy.latex(s)))

from sympy import Symbol,symbols, Matrix

def sec01():
    a,b,x = symbols("a b x")
    z = a * x + b
    disp(z)
    A = Matrix([122, 127, 101])
    B = Matrix([144, 138, 116])
    Z = A * x + B
    disp(Z)

    z = sum(Z) - 600
    disp(z)
    disp([k*60.0 for k in sympy.solve(z)][0])

sec01()

```