# Resumo, Teoria e Prática - Metódo de Newton-Raphson
> Autor: Gil Miranda

> Contato: gilsmneto@gmail.com

> Repo: http://www.gilmiranda.me

> GitHub: [@mirandagil](https://github.com/mirandagil)

> Fontes bibliográficas:
   * Scientific Computing with MATLAB and Octave, Quarteroni, Saleri, Gervasio. 3th ed. 2003. Springer
   * http://www.im.ufrj.br/bernardofpc/cursos/cientificaI/2017/Semana3-Parte2-Newton.ipynb
   * http://www.im.ufrj.br/bernardofpc/cursos/cientificaI/2017/Semana3-Parte3-GraficosConvergenciaNewton.ipynb
   * http://www.im.ufrj.br/bernardofpc/cursos/cientificaI/2017/Semana3-Parte4-AnaliseNewton.ipynb
   * https://www.youtube.com/watch?v=lh5kuaf8jjo&t=3650s
   * https://www.researchgate.net/publication/268666466_Three_variations_on_Newton%27s_method
   * https://math.stackexchange.com/questions/350740/why-does-newtons-method-work
   * https://medium.com/@ruhayel/an-intuitive-and-physical-approach-to-newtons-method-86a0bd812ec3

`last update: 13/10/2018`

### A forma mais simples do metódo de Newton-Raphson

Vamos tomar uma equação:

$$4x^3 + x+1 = 0 \\
(2x+1)\cdot(2x^2-x+1)=0 \\ $$
A raíz da equação é então obtida resolvendo $(2x+1 = 0)$ ou $(2x^2-x+1=0)$

$$2x_0+1 = 0 \\
2x_0 = -1 \\
x_0 = -1/2$$

$$
2x_1^2 - x_1 + 1 = 0 \\
\Delta = 1 - 4(2) \implies \Delta < 0
$$

Logo nossa equação pois apenas $x = -1/2$ como raíz real, sendo as outras duas raízes imaginárias, portanto nos atentaremos apenas na raíz real como facilitadora das contas, embora o metódo funcione tranquilamente para raízes complexas

Olhando para uma equação parecida: $x^3+x+1.1 =0$, agora já se torna mais complicado fatorar e extrair uma raíz. Um metódo de aproximar a solução dessa raíz é o metódo de Newton, primeiro observemos que a raíz com certeza está próxima da raíz da equação anterior, pois são curvas muito próximas.

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

```python
def f_1(x):
    return 4*x**3+x+1
def f_2(x):
    return 4*x**3+x+1.1

xis = np.arange(-0.8,0,0.01)

fig, ax1 = plt.subplots(nrows=1,ncols=1,figsize=(8,5))
ax1.plot(xis,f_1(xis),label='$f_1(x)$')
ax1.plot(xis,f_2(xis),label='$f_2(x)$')
ax1.plot(-1/2,0,'bo')
ax1.plot(-0.525,0,'ro')

ax1.spines['right'].set_color('none')
ax1.spines['top'].set_color('none')
ax1.spines['bottom'].set_position(('data',0))
ax1.spines['left'].set_position(('data',0))

ax1.legend(loc=0)
```

Pelas curvas percebemos que a raíz de $f_2$ está próxima da raíz de $f_1$

Chamaremos a raíz de $f_1$ de $x_0$, nosso ponto inicial
Um bom chute para a raíz de $f_2$ então é: $x_1 = x_0 + \epsilon$, com $|\epsilon|\ll 1$

Substituindo então nosso chute com $x_0 = -\frac{1}{2} $ para a raíz em $f_2$

$$\begin{align}
f_2(x_1) &= 4(x_0+\epsilon)^3 + (x_0+\epsilon) + 1.1\\
&= 4x_0^3 + 4\cdot3x_0^2\epsilon + 4\cdot3x_0\epsilon^2+4\cdot\epsilon^3 + x_0 + \epsilon + 1.1\\
&= -\frac{4}{8} - 3\epsilon - 6\epsilon^2 + 3\epsilon^3 - \frac{1}{2} + \epsilon + 1.1 \\
&= 4\epsilon^3 - 6\epsilon^2 - 3\epsilon - \frac{1}{2} - \frac{1}{2} + \epsilon + 1.1 \\
&= 4\epsilon^3 - 6\epsilon^2 - 4\epsilon + 0.1 \\
\end{align}
$$

A ideia do metódo consiste em poder ignorar erros 'despreziveis', como $|\epsilon|\ll 1$, então $|\epsilon^n| \ll \epsilon$, ignorando os termos onde $\epsilon$ tem uma potência maior que $1$, sobra uma equação linear: $4\epsilon + 0.1$
$$
4\epsilon + 0.1 \approx 0\\
\epsilon \approx -\frac{1}{40}
$$

Substituindo no chute pra raíz $x_1 \approx x_0 + \epsilon $, teremos:
$$
x_1 \approx -1/2 - \frac{1}{40}\\
x_1 \approx -\frac{21}{40} = -0.525
$$

Que é um chute muito próximo da raíz de $f_2$

A idéia central do metódo desenvolvido por Newton é, a partir de um ponto inicial $x_0$  encontrar um ponto $x_{1}$ 'próximo' a uma raíz jogando fora erros que podem ser desprezados por serem muito pequenos e num processo iterativo, encontrar outros pontos $x_{n+1}$ até que tenhamos $f(x_{n}) \approx 0$

### O metódo de Newton-Raphson

Iremos construir o ponto $x_{n+1}$ e o $\epsilon$ baseando em ideias de derivadas.

É fato sabido do Cálculo I que a reta tangente a uma curva definida por $f(x)$ em um ponto $x_0$ é uma boa aproximação de $f$ em $x_0$

A equação da reta tangente $y$ é dada por:
$$
y - y_0 = f'(x_0)(x - x_0) \\
y \approx f(x_0) \text{ e } y_0 = f(x_0)$$
Vamos tomar $x = x_{1}$, uma raíz da equação da reta tangente
$$
0 - y_0 = f'(x_0)(x_1-x_0) \\
x_1 - x_0 = -\frac{f(x_0)}{f'(x_0)} \\
\therefore x_1 = x_0 - \frac{f(x_0)}{f'(x_0)}
$$

Fazendo um paralelo com a forma mais 'simples' descrita acima, vemos que $\epsilon = - \frac{f(x_0)}{f'(x_0)}$
Então $x_1$ é um chute para a raíz, para completar o metódo é necessário definir uma tolerância para $f(x_n) \approx 0$ e testar os valores $x_{ns}$ gerados pela iteração do metódo com o passo descrito acima

Portanto, de forma geral, o metódo de Newton gera possíveis valores $x_n$ para a raíz de $f$ a partir de um $x_0$, que é um primeiro chute para a raíz, ou seja, está 'próximo' da raíz, o metódo é dado pela iteração do passo:

$$
x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)}
$$

A fórmula geral do passo nos mostra que o metódo funciona mal quando $x_n$ está próximo de uma raíz de $f'(x_n)$, pois o denominador tende a 0 e a fração inteira tende ao infinito, fazendo com que o metódo 'exploda'

##### newton(f,df,x,tol,maxiter)

```python
def newton(f, df, x, prec=1e-8, tol=1e-8, maxiter=100):
    if maxiter == 0: return none
    h = f(x)/df(x) ## passo de newton
    x_n = x - h ## gerando o próximo x
    if abs(h) < prec or abs(f(x)) < tol: return x_n ## testando se já estamos suficientemente próximos da raíz
    else: return newton(f,df, x_n, prec, tol, maxiter-1) ## chamando a função recursivamente para gerar um novo ponto
```

```python

```

Vamos utilizar o metódo criado para encontrar uma melhor aproximação da raíz de $f_2$

```python
def d_f2(x): return 12*x**2 +1 ## derivada de f_2

newton(f_2, d_f2, -1/2)
```

    -0.5241137677810039

## Visualizando o metódo graficamente

Faremos uma pequena modificação para que o metódo retorne uma lista dos pontos gerados, ao invés de apenas o último

```python
def newton_list(f, df, x, xis=[], prec=1e-8, tol=1e-8, maxiter=100):
    if maxiter == 0: return none
    h = f(x)/df(x) ## passo de newton
    x_n = x - h ## gerando o próximo x
    xis = xis[:] ## Faz uma cópia da lista, para não trabalhar com o ponteiro de memória
    xis.append(x_n)
    if abs(h) < prec or abs(f(x)) < tol: return xis ## testando se já estamos suficientemente próximos da raíz
    else: return newton_list(f,df, x_n, xis, prec, tol, maxiter-1) ## chamando a função recursivamente para gerar um novo ponto
```

```python
## testando
newton_list(f_2,d_f2,-1/2)
```

    [-0.525, -0.5241149158444574, -0.5241137677829334, -0.5241137677810039]

```python
phi = (1+5**0.5)/2
xis = np.arange(-2,3/2,0.01)
def f(x): return np.e**x -x**3 - phi
def d_f(x): return np.e**x - 3*x**2
```

```python
plt.plot(xis,f(xis))
ax = plt.gca()
ax.spines['top'].set_color('none')
ax.spines['right'].set_color('none')
ax.spines['left'].set_position(('data',0))
ax.spines['bottom'].set_position(('data',0))
ax.set_ylim(-1,1)
ax.grid()
ax.set_title('Gráfico da $f(x)$',fontsize=14)
```

$$
f(x) = e^x - x^3 + \phi \\
f'(x) = e^x - 3x^2
$$

```python
#work in progress
pontos = newton_list(f,d_f,-2,prec=1e-15, tol=1e-12)
qt_axes = len(pontos)
qt_rows = int(qt_axes/2+1)
fig, (ax) = plt.subplots(nrows=qt_rows, ncols=2, figsize=(15,15))

for i in range(0,qt_rows):
    for j in range(0,2):
        ax[i][j].plot(xis, f(xis))
        ax[i][j].spines['top'].set_color('none')
        ax[i][j].spines['right'].set_color('none')
        ax[i][j].spines['left'].set_position(('data',0))
        ax[i][j].spines['bottom'].set_position(('data',0))
        ax[i][j].set_ylim(-1,1)
        ax[i][j].grid()
        ax[i][j].set_title('Iteração $n_' + str(i+1) + '$')
        ax[i][j].plot(xis[0],0,'o', label='Ponto inicial $x_0$')
        ax[i][j].plot(pontos[i],0,'o', label='Ponto $x_'+ str(i)+ '$')
        ax[i][j].legend()
```

```python

```

    0.0

```python

```


# "Monte Carlo Methods 3: Vine Copula Methods"
> "In this blog post we extend the copula ideas from the previous blog post to look at the vine copula. Another flexible method for efficiently sampling from multi-variate distributions."
- toc: true
- author: Lewis Cole (2020)
- branch: master
- badges: false
- comments: false
- categories: [Monte-Carlo, Statistics, Probability, Computational-Statistics, Theory, Computation, Copula, Vine-Copula]
- hide: false
- search_exclude: false
- image: https://github.com/lewiscoleblog/blog/raw/master/images/Monte-Carlo/vine-copula.png

___
This is the third blog post in a series - you can find the previous blog post [here](https://lewiscoleblog.com/monte-carlo-methods-2)
___

In this blog post we continue to look at the problem of sampling from multi-variate distributions. Recall from the previous blog post that a copula $C(.)$ is a multivariate function:

$$ C(U_1, \, ... \, , U_N) = \mathbb{P}(X_1 \leq F_1(U_1), \, ... \, , X_N \leq F_N(U_N)) $$

We note that this defines a multivariate CDF. We can find the corresponding density function (PDF) $c(.)$ in the usual way:

$$ c(u_1, \, ... \, , u_N) = \frac{\partial^N}{\partial u_1 \, ... \, \partial u_N} C(u_1, \, ... \, , u_N) $$

Using this representation of the copula we can denote the joint distribution density $f(.)$ via a factorization of the copula density above and the marginal densities $f_i(.)$ (with corresponding CDF $F_i(x_i)$) via:

$$ \underbrace{f(x_1, \, ... \, x_N)}_\text{Joint density} = \underbrace{f_1(x_1) \, ... \, f_N(x_N)}_\text{Marginal density} \, \, \, \underbrace{c(F_1(x_1), \, ... \, ,F_N(x_N))}_\text{Copula density}$$

This highlights the power of the copula method showing that in modelling we are able to separate the marginal and joint behaviours into independent parts.

## Problems with the Copula Method
Things are not as rosy as they seem however.  We can use data to inform the marginal density functions, this is well established and we have a wide number of techniques at our disposal. However the copula density remains problematic - in anymore than the bi-variate case we are unlikely to ever have enough data inform model selection and calibration. We thus have to rely quite heavily on "trial and error" to find the correct structure to use and furthermore calibrate the parameters of that structure to give the desired result.

We also have issues in higher dimension copula based modelling. Typically an archimedean copula will not be suitable in high dimensions. For an archimedean copula to be used it typically the generator needs to be well understood mathematically which results in only a handful being widely used. Of those used typically there will only be a handful of parameters and so they are not always flexible. For example the usual implemenation of the multi-variate Gumbel copula (used for modelling upper-tail dependence in high dimension) has a single parameter controlling the degree of tail dependence (and overall dependency) - this is not very useful where we think some subset of variables will be more strongly tied than others.

As a result high-dimension multi-variate modelling typically relies on the joint-elliptical methods (typically the Gaussian/rank-normal or the student-t). In their standard forms these themselves have a number of issues:
- We need to specify an entire depedency matrix. This doesn't scale well with size and often adding a few new variables requires us to completely re-calibrate the matrix from scratch in order to ensure sensible behaviour, positive definite-ness and so on
- The Gaussian shows no tail dependence and the standard student-t copula allows only for 1 parameter controlling the tail dependence of all variables
- The tail dependence on the student-t is symmetrical which is not always desired
- With the student-t copula we cannot enforce independence

To overcome this we can apply various techniques, generalizations and extensions to (at least partially) overcome the issues above (e.g. see my [insurance aggregation model](https://lewiscoleblog.com/insurance-aggregation-model)). However these methods still lack the flexibility to model certain relations. For example suppose we have 3 variables $(X, Y, Z)$ we want to apply some dependence structure such that $X$ and $Y$ have a tie and $X$ and $Z$ have a tie - but given $Z$ then $X$ and $Y$ are independent. This can arise in various scenarios and yet it is very difficult to capture this through a joint-elliptical copula. We therefore require a more powerful method.

## Vine Copula
This is where the vine copula comes in. As with many computational statistical methods we want to produce a general tool that will work in a wide variety of situations. In the case of multi-variate sampling we want something that scales nicely with dimension. In order to do this we will look ot specify the dependency structure using a probabilistic graphical model (PGM).

We will not get into all the details surrounding PGMs since that is a field of study all to itself. Instead we will just note that a graph such as that seen below:

Can be used to specify a joint density by factorising over cliques in the undirected graph:

$$ f(X_A, X_B, X_C, X_D, X_E, X_F) = f_{ABC}(X_A, X_B, X_C) f_{CD}(X_C, X_D) f_{DEF}(X_D, X_E, X_F) $$

We can note that this has more than a passing resemblence to our joint distribution decomposition using copulae above. The main crux of the vine copula is finding a decomposition of a copula using a graphical model, we can then specify individual copulae on each clique (e.g. in the example above clique $(A,B,C)$ could be defined with a rank-normal while clique $(C,D)$ via a gumbel, and so on). Of course this offers us tremendous flexibility, to the point of leading to option paralysis! Certain simplifications have been specified to aid this.

In vine copulae the graphical model used is, as the name suggests, a vine (or cantor tree). By enforcing this requirement we bypass the need for worrying about loops and all the other technical gubbins that make working with graphical models generally quite difficult.

### Regular Vines
We start by defining vines in a more precise way. A vine $\mathcal{V}$ defined on $N$ variables is a nested set of connected trees: $\mathcal{V} = \{T_1 \, , \, ... \, , \, T_{N-1} \}$. The nodes of $T_{j+1}$ being the edges of $T_{j}$. A regular vine is a vine where two edges in $T_j$ are joined by an edge in $T_{j+1}$ only if the two edges share a common node. It is much easier to work with regular vines than non-regular vines. We can define this more formally:

____
**Definition: Regular Vine**

$\mathcal{V}$ is a regular vine on $N$ elements with edge set: $E(\mathcal{V}) = E_1 \cup E_2 \cup \, ... \, \cup E_{N-1}$ if:
1. $\mathcal{V} = \{T_1 \, , \, ... \, , \, T_{N-1} \}$
2. $T_1$ is a connected tree with nodes $N_1 = \{1 \, , \, ... \, , \, N-1 \}$ and edges $E_1$. For $i=2, ... N-1$ : $T_i$ is a tree with nodes $N_i = E_{i-1}$
3. For $i=2, ... N-1$ : for $\{a,b\} \in E_i$ we have: $|a \triangle b| = 2$ for  $\triangle$ the symmetric difference operator
____

We can further delineate regular vines into **C-vines** (canonical vines) where for each tree $T_i$ there is a unique node with $n-i$ connecting edges (degree) - this is the maximum degree for any node at that level. A regular vine is called a **D-vine** if all nodes in $T_1$ have a degree no higher than 2. Of course we can also have a vine that is not a C-vine nor a D-vine while still satisfies the definition above - we call these **R-vines**. In some sense an R-vine is "inbetween" a C-vine and a D-vine - the C-vine and D-vine being "boundary conditions" in some sense.

It is often easier to see examples than battle with formal definitions (especially with PGMs) so lets look at an example in each class:

Alternatively we can look at the sequence of trees separately:

If we expressed this vine in the form from before it would look like:

We can also make some further definitions:

____
**Defintion: Constraint Set, Conditioning Set, Conditioned Set**

1. For $e \in E_i$ ($i \leq N-1$) the **constraint set** associated with $e$ is the complete union $U_e^*$ - the subset of $\{1, \, ... \, , N\}$ reachable from $e$ by the membership relation
2. For $i \leq N-1$ and $e \in E_i$ : if $e=\{j, k\}$ then the **conditioning set** associated with $e$ is:
$$ D_e = U_j^* \cap U_k^*$$
    The **conditioned set** associated with $e$ is:
$$ \{ C_{e,j} , C_{e,k} \} = \{ U_j^* \backslash D_e, U_k^* \backslash D_e \}$$
_____

In the example above the edge XXX has conditioning set XXX and conditioned set XXX - hence the choice of naming convention!

For a regular vine $\mathcal{V} = \{T_1 \, , \, ... \, , \, T_{N-1} \}$ we have the following properties:
1. The number of edges is $N(N-1)/2$
2. Each pair of variables occurs exactly once as a conditioned set
3. If two edges have the same conditioning set then they are the same edge

The scaling of regular vines works as so:
1. For any regular vine on $(N-1)$ elements there are $2^{N-3}$ choices of $N$-dimensional regular vine that extend it
2. There are ${N \choose 2} (N-2)!2^{(N-2)(N-3)/2}$ labelled regular vines

We can thus see that model selection for regular vine copula methods can quickly become unweildly. We often need to rely on sophisticated methods to do this (we will not discuss these here). We just note that we can set up "equivalence classes" of regular vines where two regular vines are in the same class if there is a permutation of the conditional distribution variables that maps one to the other. For example if $N=3$ then for a D-vine we have conditional distributions for: $\{ (1,2), (2,3), (1,3)|2 \}$ and for a C-vine we have: $\{ (1,2), (1,3), (2,3)|1 \}$ by taking permutation: $\pi(1,2,3)=(2,1,3)$ we have equivalence - that is C-vines and D-vines are equivalent for $N=3$ (of course this is not true in general).

The real power of the regular vine copula comes through the following theorem from Bedford and Cooke (shown without proof).

___
**Theorem:**

Let $\mathcal{V} = \{T_1 \, , \, ... \, , \, T_{N-1} \}$ be a regular vine over $N$ elements. For any edge $e = \{e_1, e_2\} \in E(\mathcal{V})$ with conditioning set $D_e$, let a bivariate conditional copula be denoted: $C_{e_1,e_2 | D_e}$ with density: $c_{e_1,e_2 | D_e}$. Let marginal distributions be denoted as $F_i$ with corresponding densities $f_i$. Then the vine-dependent multivariate distribution is uniquely determined and has density:
$$f_{X_1...X_N}(x_1,...x_N) = \left( \prod_{i=1}^N f_i(x_i) \right) \left( \prod_{e \in  E(\mathcal{V})} c_{e_1,e_2 | D_e} ( F_{e_1 | D_e}(x_{e_1}) , F_{e_2| D_e}(x_{e_2})) \right) $$
___

Just as Sklar defined a bijection between multi-variate uniform distributions and copulae, this theorem does the equivalent for regular vine copulae. From before we know that we have many options for bivariate relations (for example we looked at the Gaussian, student-t, Frank, Clayton and Gumbel copulae in the previous blog post) - whereas multi-variate relations are harder to specify (with typically only Gaussian and student-t used in practice). The regular vine copula method allows us more flexibility in modelling multi-variate distributions. Moreover we can make use of conditioning arguments to help specify fairly specialised relations.

Through construction we can notice the following properties:
- If each bivariate copula specified is of Gaussian type then resulting multi-variate copula is also a Gaussian. This also has the property that conditional correlation does not depend on the conditioning varibles. We find similar results for the student-t
- If edge $e = \{e_1, e_2 \} \in E_l$ with $l > 1$. Then if copula $C_e$ is more concordant than $C_e'$ then the margin $F_e$ is more concordant than the margin $F_e'$
- Of $C_e$ has upper/lower tail ependence for all $e \in E_1$ and the remaining copulae have support in $[0,1]^2$ then all bivariate margins of the joint distribution have upper/lower tail dependence
- For parametric copulae $C_e(.; \theta_e)$ then each marginal copulae exists within the Frechet upper/lower bounds (see previous blog post for details)

These points are a rather fancy way of saying that through the regular-vine construction we get a multi-variate distribution that behaves how we would like/expect.

We also now take a brief diversion into the definition of a **partial correlation**.

___
**Definition: Partial Correlation**

Let $X_i$ be a variable with zero mean and unit variance. For $i=1,...,N$ define numbers: $b_{ij:\{1,...,,N\} \backslash \{i,j\}}$ that minimise:

$$ \mathbb{E} \left[ \left( X_i - \sum_{j \neq i} b_{ij:\{1,...,,N\} \backslash \{i,j\}} X_j  \right)^2 \right]$$

We then define the parital correlation coefficient: $\rho_{ij:\{1,...,,N\} \backslash \{i,j\}}$ as:

$$ \rho_{ij:\{1,...,,N\} \backslash \{i,j\}} = \operatorname{sgn}\left(b_{ij:\{1,...,,N\} \backslash \{i,j\}}\right)\left(b_{ij:\{1,...,,N\} \backslash \{i,j\}} b_{j,i:\{1,...,,N\} \backslash \{i,j\}}\right)^{1/2} $$

We can think of this as the correlation between the projections of $X_i$ and $X_j$ onto a plane orthogonal to the space spanned by all other variables. We can equivalently write this as:

$$ \rho_{ij:\{1,...,,N\} \backslash \{i,j\}} = - \frac{K_{ij}}{\sqrt{K_{ii}K_{jj}}} $$

Where $K_{ij}$ is the $(i,j)$th co-factor of the correlation matrix.
___

There is nothing special about this definition that is unique to the vine copula methods we are describing. We could equally define this for any Gaussian or student-t or other elliptical multivariate distribution. For those a little rusty on their linear algebra the $(i,j)$th co-factor of a matrix is equal to the determinant of the matrix after removing the $i$th row and $j$th column multiplied by the factor: $(-1)^{i+j}$.

We mention this here since we have the following theorem:

___
**Theorem:**

The regular vine provides a bijective mapping from $(-1,1)^{N \choose 2}$ to the set of positive definite matrices with $1$ diagonals (i.e. valid joint-elliptical correlation matrices)
___

This means that we can use regular vines as a parameterization tool for joint elliptical (e.g. Gaussian) multi-variate distributions. We do this by specifying partial correlations on each edge in $E(\mathcal{V})$.

### Dependency Between Discrete Variables
So far we have not differentiated between sampling continuous and discrete variables (e.g. we generate our copula and then use a generalize inverse transform to return dependent variables of arbitrary distribution be they Gaussian or Poisson). Unfortunately things are not quite that simple if we are targetting a specific level of Pearson correlation (note that previously we showed why Pearson correlation is an imperfect measure, but in practical situations it is sometimes the only metric we have). Let's use the following definitions of variables:

1. $(Z_1,...,Z_N)$ are sample from a multivariate distribution with marginal distribution functions $G_i(.)$
2. $(U_1,...,U_N) = (G_1(Z_1),...,G_N(Z_N))$ are rank dependent uniforms corresponding to $Z_i$
3. $(X_1,...,X_N) = (F^{-1}_1(U_1),...,F^{-1}_2(U_2))$ are our discrete variable transforms

We then have that in general:

$$Corr_{Pearson}(Z_i, Z_j) \neq Corr_{Pearson}(U_i, U_j) \neq Corr_{Pearson}(X_i, X_j)$$

This is not always rue for continuous variables e.g. when a Gaussian multi-variate and suitably chosen $F(.)$. To overcome this for discrete variables we often have to adjust our input parameters and sensitivity test until a desired output correlation is reached. In the case of large $N$ this quickly becomes prohibitive and we simply have to accept the biases our modelling procedure has, in some cases this can lead to large mis-statements in the model and thus we can draw incorrect conclusions.

It has been shown (in the Gaussian dependency, Poisson/eneralized Poisson/negative-binomial marginal cases) that regular vines have lower biases and do a better job at representing the input calibration. This is especially useful in cases of high dispersion where the typical approaches can lead to large inaccuracies.

### Sampling from Regular Vines

#### Sampling the D-Vine
We will now move onto the problem of sampling from the D-Vine, we start with the D-Vine since it's structure makes it easier to sample from and provides a good foundation and further explanation of what we have seen so far.

##### Inverse Transform Method
Let $(u_1, u_2, u_3, u_4)$ be 4 iid samples from a uniform distribution. We use the notaion: $F_{i | j:x_j}$ to be the cumulative distribution function of $X_j$ given that $X_i = i$. Then we can produce a sample $(x_1, x_2, x_3, x_4)$ from the joint distribution $(X_1, X_2, X_3, X_4)$ specified by our D-Vine as:

\begin{align}
x_1 &= u_1      \\
x_2 &= F^{-1}_{2|1:x_1}(u_2)        \\
x_3 &= F^{-1}_{3|2:x_2}(F^{-1}_{3|12:F_{1|2}(x_1)}(u_3))     \\
x_4 &= F^{-1}_{4|3:x_3}(F^{-1}_{4|23:F_{2|3}(x_2)}(F^{-1}_{4|123:F_{1|23}(x_1)}(u_4)))
\end{align}

We can simply repeat this to arbitrarily high dimension. A similar approach exists for regular vines but we need to be more careful about the labelling and ordering of variables.

##### Acceptance-Rejection Method
From before recall that:

$$c(x_1,...,x_N) = \prod_{e \in  E(\mathcal{V})} c_{e_1,e_2 | D_e} ( F_{e_1 | D_e}(x_{e_1}) , F_{e_2| D_e}(x_{e_2}))$$

We can use this in an acceptance-rejection scheme in the usual way: we generate multi-variate uniform variates and then accept/reject samples based on this density above. This is no different from what we have seen before and the usual observations about acceptance rates, etc. still apply.

We can see that while the specification of the vine copulae are slightly different to the copulae we looked at previously we can see that we can sample using the same tools as before.

#### Generic Sampling Method
We now move onto looking at sampling from vine copulas with general dimensions. We will present these in pseudocode. In all examples the "output" will be the variables $(u_1,...,u_N)$ which are multi-variate uniform with the vine copula induced dependency. Other interim values will be calculated.

____
**Sampling from the C-vine**

1. Generate $w_1,...,w_N$ iid $U[0,1]$ variates
2. Set: $u_1 = w_1$
3. Set: $u_2 = C^{-1}_{2|1}(w_2 |w_1)$
4. For $i > 2$:
    1. Set $t=w_i$
    2. For $j = i - 1, i-2,...,1$:
        1. $t = C^{-1}_{i |j:1,...,j-1}(t|w_j)$
    3. $u_i = t$
5. Return: $(u_1,...,u_N)$
_____

_____
**Sampling from the D-vine**

1. Generate $w_1,...,w_N$ iid $U[0,1]$ variates. Initialize $NxN$ matrices $A=(a_{i,j})$ and $B(b_{i,j})$
2. Set: $u_1 = a_{1,1} = b_{1,1} = w_1$
3. For $i \geq 2$:
    1. $a_{i,1} = w_i$
    2. For $j=2...i$:
        1. $a_{i,j} = C^{-1}_{i |j-1:j,...,i-1}(a_{i,j-1}|b{i-1,j-1})$
    3. u_i = b_{i,i} = a_{i,i}
    4. For $j=i-1,..,1$:
        1. $b_{i,j} =C_{j|i:j+1,...,i-1}(b_{i-1,j}|b{i,j+1})$
4. Return $(u_1,...,u_N)$
_____

Sampling from an R-vine requires a little more work in labelling the variables due to the lack of structure. We use indexing:

$$ 12, k_{3,1}3; k_{3,2}3|k_{3,1}; ... ; k_{i,1}i; k_{i,2}i | k_{i,1}; ... ; k_{i,i-1}i|k_{i1}; ... ; k_{n,1}n' ...; k_{n,n-1}n|k_{n,1}; ... k_{n,n-2} $$

With: $(k_{i,1}, ... , k_{i,i-1})$ some pemutation on $(1,...,i-1)$ (e.g. C-vine $k_{i,j} =j$ and D-vine $k_{i,j} = i-j$). With this in mind we can move on to general R-vine sampling.

_____
**Sampling from the R-vine**

1. Generate $w_1,...,w_N$ iid $U[0,1]$ variates. Initialize $NxN$ matrices $A=(a_{i,j})$ and $B(b_{i,j})$
2. Set: $u_1 = a_{1,1} = b_{1,1} = w_1$
3. For $i \geq 2$:
    1. $M= \{ k_{i,1}, ... , k_{i,i-2} \}$
    2. $v_{i,k_{i,i-1},M} = v_{i, \{ k_{i,i-1} \cup M \}} = w_i$
    3. $a_{i,1} = w_i$
    4. For $j \geq 2$
        1. $M= \{ k_{i,1}, ... , k_{i,i-j} \}$
        2. $v_{i,M} = C^{-1}_{i |k_{i,i+1-j}:M}(a_{i,j-1}|v_{i,i+1-j,M})$
        3. $a_{i,j} = v_{i,M}$
    5. $u_i = a_{i,i}$
    6. $b_{i,i} = a_{i,i}$
    7. For $j=i-1,...1$:
        1. $M= \{ k_{i,1}, ... , k_{i,i-1-j} \}$
        2. $v_{k_{i,i-j},i,M} = C_{k_{i,i-j}|i:M}(v_{k_{i,i-j},M}|a_{i,j+1})$
        3. $b_{i,j} = v_{k_{i,i-j},i,M}$
4. Return $(u_1,...,u_N)$
_____

### Vine-Copula packages
Thankfully there are pre-canned packages that we can use to sample from vines (as well as performing calibration procedures and other things we have not looked at in this blog). The R language perhaps has the most widely used (given it is used by statistiticians) in the "VineCopula" package. In python we have the following: [pyvine](https://pypi.org/project/pyvine/), [pyvinecopulalib](https://github.com/vinecopulib/pyvinecopulib) and others.

## Conclusion
In this blog post we have seen a gentle introduction to the concept of vine copulae, what they are, how to classify them and how to sample from them. In general vine copulae are useful in modelling high dimension multi-variate systems. They tend to perform best (in this authors experience) when the vine structure is sparse (i.e. lots of pairwise relations are independent) and on occasion very tail dependent behaviour can cause instability (although this is also true of other copula methods also). Although we have not covered the process of calibrating vine copulae there are a lot of powerful methods available to us in order to do this which is very useful. Vine copulae are increasingly used as a way of simulating "synthetic data" in order to feed the ever more data-hungry deep machine learning methodologies.

___
## References
The homepage for all things vine copula is: [homepage](https://www.groups.ma.tum.de/en/statistics/home/)

In preparing this blog post I relied heavily on the book:

Dependence Modelling: Vine Copula Handbook - Dorota Kurowicka and Harry Joe


```python
import numpy as np
import numba
import matplotlib.pyplot as plt
import sympy as sym
import scipy.optimize as sopt
#plt.style.use('presentation')
%matplotlib notebook

def d2np(d):

    names = []
    numbers = ()
    dtypes = []
    for item in d:
        names += item
        if type(d[item]) == float:
            numbers += (d[item],)
            dtypes += [(item,float)]
        if type(d[item]) == int:
            numbers += (d[item],)
            dtypes += [(item,int)]
        if type(d[item]) == np.ndarray:
            numbers += (d[item],)
            dtypes += [(item,np.float64,d[item].shape)]
    return np.array([numbers],dtype=dtypes)
```

```python
alpha = np.exp(2.0/3*np.pi*1j)
A_0a =  np.array([[1, 1, 1],
                  [1, alpha**2, alpha],
                  [1, alpha, alpha**2]])

A_a0 = 1/3* np.array([[1, 1, 1],
                      [1, alpha, alpha**2],
                      [1, alpha**2, alpha]])
```

```python
V_0 = 0.0*np.exp(1j*0)
V_1 = 400/np.sqrt(3)*np.exp(1j*0)
V_2 = 0.0*np.exp(1j*0)
V_012 = np.array([V_0,V_1,V_2]).reshape(3,1)
```

```python
V_abc = A_0a @ V_012
```

```python
V_abc
```

    array([[ 230.94010768  +0.j],
           [-115.47005384-200.j],
           [-115.47005384+200.j]])

```python
I_0 = 0.0*np.exp(1j*0)
I_1 = 10*np.exp(1j*0)
I_2 = 0.0*np.exp(1j*0)
I_012 = np.array([I_0,I_1,I_2]).reshape(3,1)
```

```python
I_abc = A_0a @ I_012
```

```python
Z_abc = np.eye(3)*30
Y_abc = np.linalg.inv(Z_abc)
```

```python
#V_abc = Z_abc @ I_abc
```

```python
I_abc = Y_abc @ V_abc
```

```python
I_abc
```

    array([[ 7.69800359+0.j        ],
           [-3.84900179-6.66666667j],
           [-3.84900179+6.66666667j]])

```python
Y_abc
```

    array([[ 0.03333333,  0.        ,  0.        ],
           [ 0.        ,  0.03333333,  0.        ],
           [ 0.        ,  0.        ,  0.03333333]])

```python
Y_0 = 0.3
Y_1 = 0.0
Y_2 = 0.0

Y_012 = np.array([[ Y_0,  0. ,  0. ],
                   [ 0. , Y_1,  0. ],
                   [ 0. ,  0. ,  Y_2]])

```
I_012 = Y_012 @ V_012
A_a0 @ I_abc = Y_012 @ (A_a0 @ V_abc)
I_abc = A_0a @ Y_012 @ A_a0 @ V_abc
Y_abc = A_0a @ Y_012 @ A_a0

```python
Y_abc = A_0a @ Y_012 @ A_a0
```

```python
I_abc = Y_abc @ V_abc
```

```python
I_abc
```

    array([[ -1.77635684e-15 +7.10542736e-15j],
           [ -1.77635684e-15 +7.10542736e-15j],
           [ -1.77635684e-15 +7.10542736e-15j]])

```python
V_0 = 0.0*np.exp(1j*0)
V_1 = 400/np.sqrt(3)*np.exp(1j*0)
V_2 = 0.0*np.exp(1j*0)
V_012 = np.array([V_0,V_1,V_2]).reshape(3,1)

I_abc = Y_abc @ V_abc

```

```python
Y_abc
```

    array([[ 0.1+0.j,  0.1+0.j,  0.1+0.j],
           [ 0.1+0.j,  0.1+0.j,  0.1+0.j],
           [ 0.1+0.j,  0.1+0.j,  0.1+0.j]])

```python
Z_gnd = 32.0
Y_gnd = 1.0/Z_gnd
Y_abcn = np.array([[ 1,  1,  1, -Y_gnd],
                   [ 1,  1,  1, -Y_gnd],
                   [ 1,  1,  1, -Y_gnd],
                   [ 1,  1,  1,  3*Y_gnd]])

V_0 = 0.0*np.exp(1j*0)
V_1 = 20e3/np.sqrt(3)*np.exp(1j*0)
V_2 = 0.0*np.exp(1j*0)
V_012 = np.array([V_0,V_1,V_2]).reshape(3,1)
V_abc = A_0a @ V_012

V_abcn = np.vstack((V_abc,[11e3+1j*0.0]))

I_abcn = Y_abcn @ V_abcn
I_abcn
```

    array([[ -343.75 +3.63797881e-12j],
           [ -343.75 +3.63797881e-12j],
           [ -343.75 +3.63797881e-12j],
           [ 1031.25 +3.63797881e-12j]])

```python
V_abcn
```

    array([[ 11547.00538379    +0.j],
           [ -5773.50269190-10000.j],
           [ -5773.50269190+10000.j],
           [ 11000.00000000    +0.j]])

```python
## Kron reduction
```

```python
Z_gnd = 32.0*3
Y_gnd = 1.0/Z_gnd
Y_full = -Y_gnd/3+Y_gnd
Y_gnd1 = Y_gnd
Y_gnd2 = Y_gnd*3
Y_abcn = np.array([[  Y_full+1e-6,  Y_full,  Y_full, -Y_gnd1],
                   [  Y_full,  Y_full+1e-6,  Y_full, -Y_gnd1],
                   [  Y_full,  Y_full,  Y_full+1e-6, -Y_gnd1],
                   [ -Y_gnd1, -Y_gnd1, -Y_gnd1,  Y_gnd2]])
Y_pp = Y_abcn[0:3,0:3]
Y_pn = Y_abcn[0:3,3:4]
Y_np = Y_abcn[3:4,0:3]
Y_nn = Y_abcn[3:4,3:4]
Y_abc = Y_pp - Y_pn @ np.linalg.inv(Y_nn) @ Y_np
Z_abc = np.linalg.inv(Y_abc)
V_0 = 0e3/np.sqrt(3)*np.exp(1j*0)
V_1 = 0e3/np.sqrt(3)*np.exp(1j*0)
V_2 = 20.0*np.exp(1j*0)
V_012 = np.array([V_0,V_1,V_2]).reshape(3,1)
V_abc = A_0a @ V_012

I_abc = Y_abc @ V_abc
I_abc
```

    array([[  2.00000000e-05 +2.77555756e-17j],
           [ -1.00000000e-05 +1.73205081e-05j],
           [ -1.00000000e-05 -1.73205081e-05j]])

```python
20e3/np.sqrt(3)/32
```

    360.8439182435161

```python
360.0*2
```

    720.0

```python
360*3
```

    1080

```python
np.linalg.inv(Y_abc)
```

    array([[ 666698.66359481, -333301.33640496, -333301.33640496],
           [-333301.33640496,  666698.66359486, -333301.33640501],
           [-333301.33640496, -333301.33640501,  666698.66359486]])

```python

```

```python

```

```python

```

```python

```


# Surface Codes Tutorial II
---

 The idea we implement here is to realize graphs on surfaces such as the following found in the papers [Feynman Diagrams and Rooted Maps](https://arxiv.org/pdf/1312.0934.pdf) and [Minors and planar embeddings of digraphs](https://www.researchgate.net/publication/37985042_Minors_and_planar_embeddings_of_digraphs), in order to use and study *surface codes*:

Below, we are going to create a class called `SurfaceCodeGraph` which implements a combinatorial object defining a graph which is cellularly embedded in a compact $2$-manifold, i.e. a Riemann surface. This will give us the necessary tools we need to start implementing and studying surface codes on arbitrary surfaces of any genus, and with any qubit connectivity we want. This combinatorial construction is very closely related to quantum gravity, string theory, lattice gauge theories, and number theory (dessins d'enfants), where certain questions in quantum complexity involving partition functions of Ising type models can be translated into questions about L-functions. We will provide references to relevant research papers at the end.

In building the graph, *bipartite graphs* from NetworkX are used to create *black nodes*, `bipartite=1`, and *white nodes*, `bipartite=0`. For more info on creating bipartite graphs see the [NetworkX documentation](https://networkx.github.io/documentation/stable/reference/algorithms/bipartite.html).

```python
from collections import Counter
from typing import Tuple, List

from networkx import MultiGraph
from networkx import nx
from networkx.algorithms import bipartite
from sympy.combinatorics import Permutation

import matplotlib.pyplot as plt

# from SurfaceCodes.utilites import permlist_to_tuple

class SurfaceCodeGraph(MultiGraph):
    def __init__(self, sigma: Tuple[Tuple[int]], alpha: Tuple[Tuple[int]]):
        super().__init__()
        self.sigma = sigma  # should include singletons corresponding to fixed points
        self.alpha = alpha  # should include singletons corresponding to fixed points
        f = self.compute_phi()
        self.phi = self.permlist_to_tuple(f)
        self.build_node_info()  # print dictionary for [sigma, alpha, phi]
        self.node_dict = self.sigma_dict, self.alpha_dict, self.phi_dict

        self.node_info = ["sigma:", self.sigma_dict,
                          "alpha:", self.alpha_dict,
                          "phi:", self.phi_dict]

        self.code_graph = nx.MultiGraph()

        # Create black nodes for each cycle in sigma along with white nodes
        # representing "half edges" around the black nodes
        for cycle in self.sigma:
            self.code_graph.add_node(cycle, bipartite=1)
            for node in cycle:
                self.code_graph.add_node(node, bipartite=0)
                self.code_graph.add_edge(cycle, node)

        # Create black nodes for each cycle in phi along with white nodes
        # representing "half edges" around the black nodes
        for cycle in self.phi:
            self.code_graph.add_node(cycle, bipartite=1)
            for node in cycle:
                self.code_graph.add_edge(cycle, node)

        # Create nodes for each cycle in alpha then
        # glue the nodes corresponding to a the pairs
        for pair in self.alpha:
            self.code_graph.add_node(pair)
            self.code_graph = nx.contracted_nodes(self.code_graph, pair[0], pair[1], self_loops=True)
            # Now contract pair with pair[0] to make sure edges (white nodes) are labeled
            # by the pairs in alpha to keep track of the gluing from the previous step
            self.code_graph = nx.contracted_nodes(self.code_graph, pair, pair[0], self_loops=True)

        # Define the white and black nodes. White correspond to edges labeled by
        # cycles in alpha. Black correspond to nodes labeled by cycles in sigma
        # (vertices) and phi (faces)
        self.black_nodes, self.white_nodes = bipartite.sets(self.code_graph)

    def permlist_to_tuple(self, perms):
        """
        convert list of lists to tuple of tuples in order to have two level iterables
        that are hashable for the dictionaries used later
        """
        return tuple(tuple(perm) for perm in perms)

    def compute_phi(self):
        """compute the list of lists full cyclic form of phi (faces of dessin [sigma, alpha, phi])"""
        s = Permutation(self.sigma)
        a = Permutation(self.alpha)
        f = ~(a * s)
        f = f.full_cyclic_form  # prints permutation as a list of lists including all singletons (fixed points)
        return f

    def build_node_info(self):
        count = -1

        self.sigma_dict = dict()
        for count, cycle in enumerate(self.sigma):
            self.sigma_dict[cycle] = count

        self.phi_dict = dict()
        for count, cycle in enumerate(self.phi, start=count + 1):
            self.phi_dict[cycle] = count

        self.alpha_dict = dict()
        for count, pair in enumerate(self.alpha, start=count + 1):
            self.alpha_dict[pair] = count
        return tuple([self.sigma_dict, self.alpha_dict, self.phi_dict])

    def boundary_1(self, edge):
        """
        compute boundary of a single edge given by a white node (cycle in alpha)
        """
        boundary1 = [node for node in self.code_graph.neighbors(edge) if node in self.sigma_dict]
        return boundary1

    def del_1(self, edges: List[Tuple[int]]):
        """
        boundary of a list of edges, i.e. an arbitrary 1-chain over Z/2Z
        """

        boundary_list = [self.boundary_1(edge) for edge in edges]
        a = Counter([y for x in boundary_list for y in x])
        boundary_list = [x[0] for x in a.items() if x[1] % 2 == 1]
        return boundary_list

    def boundary_2(self, face):
        """
        compute boundary of a single face
        """
        boundary = self.code_graph.neighbors(face)
        return boundary

    def del_2(self, faces: List[Tuple[int]]):
        """
        boundary of a list of faces, i.e. an arbitrary 2-chain over Z/2Z
        """

        boundary_list = [self.boundary_2(face) for face in faces]
        a = Counter([y for x in boundary_list for y in x])
        boundary_list = [x[0] for x in a.items() if x[1] % 2 == 1]
        return boundary_list

    def coboundary_1(self, star):
        """
        compute coboundary of a single star
        """
        coboundary = self.code_graph.neighbors(star)
        return coboundary

    def delta_1(self, stars: List[Tuple[int]]):
        """
        coboundary of a list of stars, i.e. an arbitrary 0-cochain over Z/2Z
        """
        coboundary_list = [self.coboundary_1(star) for star in stars]
        a = Counter([y for x in coboundary_list for y in x])
        coboundary_list = [x[0] for x in a.items() if x[1] % 2 == 1]
        return coboundary_list

    def coboundary_2(self, edge):
        """
        compute coboundary of a single edge given by a white node (cycle in alpha)
        """
        coboundary2 = [node for node in self.code_graph.neighbors(edge) if node in self.phi_dict]
        return coboundary2

    def delta_2(self, edges: List[Tuple[int]]):
        """
        coboundary of a list of edges, i.e. an arbitrary 1-cochain over Z/2Z
        given by a list of cycles in alpha
        """
        coboundary_list = [self.coboundary_2(edge) for edge in edges]
        a = Counter([y for x in coboundary_list for y in x])
        coboundary_list = [x[0] for x in a.items() if x[1] % 2 == 1]
        return coboundary_list

    def euler_characteristic(self):
        """
        Compute the Euler characteristic of the surface in which the graph is embedded
        """
        chi = len(self.phi) - len(self.alpha) + len(self.sigma)
        return (chi)

    def genus(self):
        """
        Compute the genus of the surface in which the graph is embedded
        """
        g = int(-(len(self.phi) - len(self.alpha) + len(self.sigma) - 2) / 2)
        return (g)

    def draw(self, node_type='', layout = ''):
        """
        Draw graph with vertices, edges, and faces labeled by colored nodes and their integer indices
        corresponding to the qubit indices for the surface code
        """
        if not node_type in ['cycles', 'dict']:
            raise ValueError('node_type can be "cycles" or "dict"')

        if layout == 'spring':
            pos=nx.spring_layout(self.code_graph)
        if layout == 'spectral':
            pos=nx.spectral_layout(self.code_graph)
        if layout == 'planar':
            pos=nx.planar_layout(self.code_graph)
        if layout == 'shell':
            pos=nx.shell_layout(self.code_graph)
        if layout == 'circular':
            pos=nx.circular_layout(self.code_graph)
        if layout == 'spiral':
            pos=nx.spiral_layout(self.code_graph)
        if layout == 'random':
            pos=nx.random_layout(self.code_graph)
        # white nodes
        nx.draw_networkx_nodes(self.code_graph, pos,
                       nodelist=list(self.alpha),
                       node_color='c',
                       node_size=500,
                    alpha=0.3)
        # vertex nodes
        nx.draw_networkx_nodes(self.code_graph, pos,
                       nodelist=list(self.sigma),
                       node_color='b',
                       node_size=500,
                    alpha=0.6)
         # face nodes
        nx.draw_networkx_nodes(self.code_graph, pos,
                       nodelist=list(self.phi),
                       node_color='r',
                       node_size=500,
                    alpha=0.6)
        # edges
        nx.draw_networkx_edges(self.code_graph, pos, width=1.0, alpha=0.5)

        labels={}

        if node_type == 'cycles':
            '''
            label nodes the cycles of sigma, alpha, and phi
            '''
            for node in self.alpha_dict:
                # stuff = self.alpha_dict[node]
                labels[node]=f'$e$({node})'
            for node in self.sigma_dict:
                # something = self.sigma_dict[node]
                labels[node]=f'$v$({node})'
            for node in self.phi_dict:
                # something2 = self.phi_dict[node]
                labels[node]=f'$f$({node})'
            nx.draw_networkx_labels(self.code_graph, pos, labels, font_size=12)

        if node_type == 'dict':
            '''
            label nodes with v, e, f and indices given by node_dict corresponding to
            qubit indices of surface code
            '''

            for node in self.alpha_dict:
                # stuff = self.alpha_dict[node]
                labels[node]=f'$e$({self.alpha_dict[node]})'
            for node in self.sigma_dict:
                # something = self.sigma_dict[node]
                labels[node]=f'$v$({self.sigma_dict[node]})'
            for node in self.phi_dict:
                # something2 = self.phi_dict[node]
                labels[node]=f'$f$({self.phi_dict[node]})'
            nx.draw_networkx_labels(self.code_graph, pos, labels, font_size=12)

        # plt.axis('off')
        # plt.savefig("labels_and_colors.png") # save as png
        plt.show() # display

```

# Example 1
---

Here we define two permutations,

\begin{align}
\sigma &= (0,1,2)(3,4,5)(6,7) \\
\alpha &= (0,3)(1,6)(2,4)(5,7).
\end{align}

The cycles in $\sigma$ will correspond to vertices with *half-edges* attached to them in the cyclic ordering specified by each cycle. In this example, there are three vertices. One is labeled by $(0,1,2)$, the next is labeled by $(3,4,5)$, and the third is labeled by $(6,7)$. The cycles labeling each vertex tell us there are three half-edges connected to the first vertex, three on the second vertex, and two on the third vertex.

The permutation $\alpha$ gives us a way of *gluing* the half-edges to each other. So the first cycle of $\alpha$, $(0,3)$, tells us to glue the half-edges $0$ and $3$. Similarly, we glue the half-edges $1$ and $6$ since the second cycle of $\alpha$ is $(1,6)$. So, we can take the cycles in $\alpha$ as labels of the edges of the graph we are creating.

Our class `SurfaceCodeGraph` will create *white* nodes on these edges which are labeled by the cycles of $\alpha$. These wil be important later when we create the class `SurfaceCodeCircuit`. The next thing to note is that once we have this graph glued together, there is a minimal genus surface into which it naturally embeds. The genus of this surface can be computed very easily once we know what the faces are. To compute the faces we need to compute a third permutation $\phi = (\sigma \alpha)^{-1}$. This is done automatically by the class, and can be printed off. In our example

\begin{align}
\phi = (0, 4) (1, 3, 7) (2, 6, 5).
\end{align}

In order to perform computations with permutations we use SymPy. The documentation on permutations can be found [here](https://docs.sympy.org/latest/modules/combinatorics/permutations.html). Let's have a look at our example by creating an instance of `SurfaceCodeGraph` corresponding to the permutations

\begin{align}
\sigma &= (0,1,2)(3,4,5)(6,7) \\
\alpha &= (0,3)(1,6)(2,4)(5,7).
\end{align}

```python
sigma = ((0,1,2),(3,4,5),(6,7))
alpha = ((0,3),(1,6),(2,4),(5,7))
SCG = SurfaceCodeGraph(sigma, alpha)
SCG
```

    <__main__.SurfaceCodeGraph at 0x7fc8b6def190>

Now, we can draw the graph with the `draw` method with option `'cycles'` to label the vertex, edge, and face nodes by the cycles from `sigma`, `alpha`, and `phi` respectively, along with a `v`, `e`, or `f`, depending on whether they correspond to a vertex, edge, or face. We can also use the following layout options,

- `'spring'`
- `'spectral'`
- `'planar'`
- `'shell'`
- `'circular'`
- `'spiral'`
- `'random'`

```python
SCG.draw('cycles', 'spring')
```

To see which nodes correspond to *faces*, we can print off $\phi$.

```python
SCG.phi
```

    ((0, 4), (1, 3, 7), (2, 6, 5))

Now, we can print off the `node_info`, which is a dictionary built by `SurfaceCodeGraph`. The dictionary enumerates the vertices of the graph corresponding to $\sigma$, $\alpha$, and $\phi$, so that we have a complete enumeration of all vertices of the graph created.

```python
SCG.node_info
```

    ['sigma:',
     {(0, 1, 2): 0, (3, 4, 5): 1, (6, 7): 2},
     'alpha:',
     {(0, 3): 6, (1, 6): 7, (2, 4): 8, (5, 7): 9},
     'phi:',
     {(0, 4): 3, (1, 3, 7): 4, (2, 6, 5): 5}]

This graph has three *black* vertices corresponding to $\sigma$, `0`, `1`, and `2`. There are four *white* vertices corresponding to $\alpha$, `6`, `7`, `8`, and `9`. It also has three more *black* vertices corresponding to $\phi$, `3`, `4`, and `5`. We can print off all of the nodes at once with their original labels given by the cycles of the permutations $\sigma, \alpha$, and $\phi$ as follows.

```python
SCG.code_graph.nodes
```

    NodeView(((0, 1, 2), (3, 4, 5), (6, 7), (0, 4), (1, 3, 7), (2, 6, 5), (0, 3), (1, 6), (2, 4), (5, 7)))

To see the bipartite sets we can use the following methods, which print off the `white_nodes` and `black_nodes`.

```python
bipartite.sets(SCG.code_graph)
```

    ({(0, 1, 2), (0, 4), (1, 3, 7), (2, 6, 5), (3, 4, 5), (6, 7)},
     {(0, 3), (1, 6), (2, 4), (5, 7)})

```python
SCG.white_nodes
```

    {(0, 3), (1, 6), (2, 4), (5, 7)}

```python
SCG.black_nodes
```

    {(0, 1, 2), (0, 4), (1, 3, 7), (2, 6, 5), (3, 4, 5), (6, 7)}

There is an alternative method for drawing the graph which colors the vertices, edges, and faces with blue, cyan, and red, resepctively. It also prints `v`, `e`, or `f` depending on whether the node corresponds to a vertex, edge, or face, along with the dictionary value corresponding to the integer index that labels the qubits in the surface code (which we discuss a bit later).

```python
SCG.draw('dict', 'spring')
```

We can compute the *Euler characteristic*, which is give by the formula

\begin{align}
\chi &= V-E+F \\
&= |\sigma| - |\alpha| + |\phi|,
\end{align}

which in our case is

\begin{align}
3 - 4 + 3 = 2.
\end{align}

`SurfaceCodeGraph` has a method for computing the Euler characteristic.

```python
SCG.euler_characteristic()
```

    2

The next natural thing we might want to know is the *genus* of the surface the graph is naturally embedded in. This can be computed using the formula

\begin{align}
\chi &= 2-2g \\
& \implies 2 = 2-2g \\
& \implies 0 = -2g \\
& \implies 0 = g.
\end{align}

So, we have a genus zero surface, a sphere. `SurfaceCodeGraph` has a method for this as well.

```python
SCG.genus()
```

    0

Now, something that is important for working with surface codes is computing the *boundary operator* applied to a list of faces. The boundaries are computed over $\mathbb{Z}/2\mathbb{Z}$-homology. This is in general a map that takes faces to edges, and edges to vertices,

\begin{align}
\partial_2: & F \to E \\
\partial_1: & E \to V.
\end{align}

So, the way we have constructed our graph, this amounts to computing the edge nodes which are connected to a node corresponding to a face. We can do this for a single face, or for a list of faces. Since we are working with $\mathbb{Z}/2\mathbb{Z}$-homology, which is typical in surface code calculations, we only work with lists of faces. In more general settings, such as $\mathbb{Z}$-homology, we can have a vector with integer entries representing that we have multiple copies of faces, but this is more general than we need here, and the coefficients are just zero or one, so lists of faces will suffice.

As an example, if we compute the boundary of the face $(1,3,7)$, we get get a list of *white* nodes (always from $\alpha$) which are the boundary. The list will be $[(0, 3), (1, 6), (5, 7)]$. There is a method in `SurfaceCodeGraph` for computing the boundary of arbitrary lists of faces.

```python
SCG.del_2([(1,3,7)])
```

    [(0, 3), (1, 6), (5, 7)]

Now, let's compute the boundary of $[(0, 4), (1,3,7)]$.

```python
SCG.del_2([(0, 4), (1,3,7)])
```

    [(2, 4), (1, 6), (5, 7)]

Something important to notice here is that while $e = (0,3)$ is in the boundary of both $(0,4)$ and of $(1,3,7)$, since we are working over $\mathbb{Z}/2\mathbb{Z}$, $2e = 0e = 0$. In other words, two copies is the same as zero copies of an edge since we are working **mod** $2$. Mathematically this can be written as follows,

\begin{align}
\partial_2((0, 4) + (1,3,7)) &= \partial_2((0, 4)) + \partial_2((1,3,7))\\
&= [(2, 4)+(0, 3)] + [(0, 3) + (1, 6) + (5, 7)] \\
&= 2(0,3) + (2, 4) + (1, 6) + (5, 7)\\
&= (2, 4) + (1, 6) + (5, 7).
\end{align}

Now, we can also compute *coboundaries* of vertices (always given by lists of elements from $\sigma$). Coboundaries of vertices are the edges attached to a vertex. Coboundaries of edges are the faces on either side of the edge. So the coboundary maps are

\begin{align}
\delta_1: & V \to E \\
\delta_2: & E \to F.
\end{align}

As an example, let's use the method of `SurfaceCodeGraph` to compute the coboundary of the vertex $(0,1,2)$.

```python
SCG.delta_1([(0,1,2)])
```

    [(0, 3), (1, 6), (2, 4)]

This is a list of edges (always given by cycles of $\alpha$, i.e. *white* nodes). Now, Let's compute the *coboundary* of the vertices $[(0,1,2), (3, 4, 5)]$.

```python
SCG.delta_1([(0,1,2), (3,4,5)])
```

    [(1, 6), (5, 7)]

Notice, here again we have the white nodes $(0,3)$ and $(2,4)$ are both in the coboundary of the vertex $(0,1,2)$ and of the vertex $(3,4,5)$. However, since they show up twice, their coefficient is $2$, which is $0$ **mod** $2$.

Now, let's use the method `boundary_1` which computes the boundary of a single edge (given by a cycle in alpha, which corresponds to a white node in the graph). This should return a list of vertices labeled by cycles of $\sigma$.

```python
SCG.boundary_1((0,3))
```

    [(0, 1, 2), (3, 4, 5)]

```python
SCG.boundary_1((1,6))
```

    [(0, 1, 2), (6, 7)]

Next, we compute the boundary of a list of edges, i.e. an arbitrary $1$-chain. Here we use the method `del_1` ($\partial_1: E \to V$) and the method should return a list of vertices labeled by cycles in $\sigma$. Again, remember we are working over $\mathbb{Z}/2\mathbb{Z}$ so if there is an overlap in boundaries between two edges, they cancel **mod** $2$.

```python
SCG.del_1([(0,3), (1,6)])
```

    [(3, 4, 5), (6, 7)]

Notice, the boundary vertex $(0,1,2)$ does not appear since we are working **mod** $2$, i.e. because it is in the boundary of both $(0,3)$ and $(1,6)$. Mathematically we have

\begin{align}
\partial_1((0,3) + (1,6)) &= \partial_1((0,3)) + \partial_1((1,6)) \\
&= [(0,1,2)+(3,4,5)] + [(0,1,2)+(6,7)] \\
&= 2(0,1,2)+(3,4,5)+(6, 7)\\
&= (3, 4, 5) + (6, 7).
\end{align}

Of course, we can use the `coboundary_2` method to compute coboundaries of a single edge too. This will return two faces labeled by cycles in $\phi$ that lie on either side of an edge.

```python
SCG.coboundary_2((0,3))
```

    [(0, 4), (1, 3, 7)]

```python
SCG.coboundary_2((1,6))
```

    [(1, 3, 7), (2, 6, 5)]

Next, let's combute the coboundary of a list of edges, i.e. an arbitrary $1$-cochain. This is done using the method `delta_2`:

\begin{align}
\delta_2:E \to F.
\end{align}

This will return a list of faces lying on either side of the edges, and as usual it will be over $\mathbb{Z}/2\mathbb{Z}$.

```python
SCG.delta_2([(0,3), (1,6)])
```

    [(0, 4), (2, 6, 5)]

Mathematically, this is:

\begin{align}
\delta_2((0,3) + (1,6)) &= \delta_2((0,3)) + \delta_2((1,6)) \\
&= [(0, 4) + (1, 3, 7)] + [(1, 3, 7) + (2, 6, 5)] \\
&= (0, 4) + 2(1, 3, 7) + (2, 6, 5)\\
&= (0, 4) + (2, 6, 5),
\end{align}

since we are working over $\mathbb{Z}/2\mathbb{Z}$.

Now, we are going to create a class `SurfaceCodeCircuit`, which builds a quantum circuit in Qiskit corresponding to a `SurfaceCodeGraph`, which is built from the input data $\sigma, \alpha$.

```python
from typing import Tuple

from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister

# from SurfaceCodes.surface_code_class import SurfaceCodeGraph
# from SurfaceCodes.utilites import permlist_to_tuple

class SurfaceCodeCircuit(QuantumCircuit):

    def __init__(self, sigma: Tuple[Tuple[int]], alpha: Tuple[Tuple[int]]):
        super().__init__()
        self.sigma = sigma
        self.alpha = alpha

        self.scgraph = SurfaceCodeGraph(self.sigma, self.alpha)

        '''
        Compute the permutation corresponding to phi and create a
        'surface code circuit' based on a (multi)graph 'surface_code_graph'
        given by sigma, alpha, and phi
        Create quantum and classical registers based on the number of nodes in G
        '''
        # f = self.scgraph.compute_phi()
        self.phi = self.scgraph.phi

        self.qr = QuantumRegister(len(self.scgraph.code_graph.nodes))
        self.cr = ClassicalRegister(len(self.scgraph.code_graph.nodes))
        self.circ = QuantumCircuit(self.qr, self.cr)

        self.node_info = self.scgraph.node_dict
        self.sigma_dict, self.alpha_dict, self.phi_dict = self.node_info

        for cycle in self.sigma:
            self.circ.h(self.sigma_dict[cycle])

        for cycle in self.phi:
            self.circ.h(self.phi_dict[cycle])

    def x_measurement(self, qubit: int, cbit: int):
        """Measure 'qubit' in the X-basis, and store the result in 'cbit'
        :param qubit, cbit:
        :return None
        """
        # circuit.measure = measure  # fix a bug in qiskit.circuit.measure
        self.circ.h(qubit)
        self.circ.measure(qubit, cbit)
        self.circ.h(qubit)

    def star_syndrome_measure(self, vertex: Tuple[int]):
        """
        Applies CX gates to surrounding qubits of a star then measures star qubit in X-basis
        :param vertex:
        :return:  self.circ, self.scgraph, self.node_info
        """

        for node in self.scgraph.code_graph.neighbors(vertex):
            self.circ.cx(self.sigma_dict[vertex], self.alpha_dict[node])

        self.circ.barrier()
        self.x_measurement(self.sigma_dict[vertex], self.sigma_dict[vertex])
        self.circ.barrier()

        return self.circ, self.scgraph, self.node_info

    def face_syndrome_measure(self, vertex: Tuple[int]):
        """
        Applies CZ gates to surrounding qubits of a face then measures face qubit in X-basis
        :param vertex:
        :return:
        """

        for node in self.scgraph.code_graph.neighbors(vertex):
            self.circ.cz(self.phi_dict[vertex], self.alpha_dict[node])

        self.circ.barrier()
        self.x_measurement(self.phi_dict[vertex], self.phi_dict[vertex])
        self.circ.barrier()

        return self.circ, self.scgraph, self.node_info

    def product_Z(self, faces):
        """
        Pauli product Z operator for arbitrary 2-chain boundary
        """

        boundary_nodes = self.scgraph.del_2(faces)
        for node in boundary_nodes:
            self.circ.z(self.alpha_dict[node])

    def product_X(self, stars):
        """
        Pauli product X operator for arbitrary 0-cochain coboundary
        """
        coboundary_nodes = self.scgraph.delta_1(stars)
        for node in coboundary_nodes:
            self.circ.x(self.alpha_dict[node])

    def draw_graph(self, node_type='', layout = ''):
        if layout == 'spring':
            pos=nx.spring_layout(self.scgraph.code_graph)
        if layout == 'spectral':
            pos=nx.spectral_layout(self.scgraph.code_graph)
        if layout == 'planar':
            pos=nx.planar_layout(self.scgraph.code_graph)
        if layout == 'shell':
            pos=nx.shell_layout(self.scgraph.code_graph)
        if layout == 'circular':
            pos=nx.circular_layout(self.scgraph.code_graph)
        if layout == 'spiral':
            pos=nx.spiral_layout(self.scgraph.code_graph)
        if layout == 'random':
            pos=nx.random_layout(self.scgraph.code_graph)
        if node_type == 'cycles':
            self.scgraph.draw('cycles', layout)
        if node_type == 'dict':
            self.scgraph.draw('dict', layout)
```

This class will create a Qiskit circuit, with all black nodes of its `SurfaceCodeGraph` (corresponding to $\sigma$ and $\phi$) initialized in the $|+\rangle = H|0\rangle$ state, by applying a Hadamard gate. The *white* nodes, correspnding to the edges of the graph constructed by `SurfaceCodeGraph` (i.e. by cycles of $\alpha$), will be left alone and so will be in the $|0\rangle$ state.

```python
SCC = SurfaceCodeCircuit(sigma, alpha)
```

```python
SCC.circ.draw('mpl')
```

Now, we can draw the corresponding graph, which in this case is the same as the graph we created above for our examples of `SurfaceCodeGraph`.

```python
nx.draw(SCC.scgraph.code_graph, with_labels = True)
```

We can also use the `draw_graph()` method with either `'cycles'` or `'dict'` as the `node_type`. This will draw the same graph as the `SurfaceCodeGraph` method `draw()`. The nodes of the graph will be labeled by a `v`, `e`, or `f` depending on whether the nodes correspond to a vertex, edge, or face. If the option `cycles` is chosen the nodes are labeled by the corresponding cycles from `sigma`, `alpha`, and `phi`. If the option `dict` is chosen for `node_type` they will be labeled by the corresponding qubit indices (integers).

```python
SCC.draw_graph('cycles', 'spring')
```

```python
SCC.draw_graph('dict', 'spring')
```

We can again print out the `node_info`, which is a dictionary from the node lables to integers. The first row is for $\sigma$, the second row is for $\alpha$, and the third row is for $\phi$.

```python
SCC.node_info
```

    ({(0, 1, 2): 0, (3, 4, 5): 1, (6, 7): 2},
     {(0, 3): 6, (1, 6): 7, (2, 4): 8, (5, 7): 9},
     {(0, 4): 3, (1, 3, 7): 4, (2, 6, 5): 5})

We can print $\sigma, \alpha$, and $\phi$.

```python
SCC.sigma
```

    ((0, 1, 2), (3, 4, 5), (6, 7))

```python
SCC.alpha
```

    ((0, 3), (1, 6), (2, 4), (5, 7))

```python
SCC.phi
```

    ((0, 4), (1, 3, 7), (2, 6, 5))

We can also print of all of the nodes which are labeles by the cycles of $\sigma, \alpha$, and $\phi$.

```python
SCC.scgraph.code_graph.nodes
```

    NodeView(((0, 1, 2), (3, 4, 5), (6, 7), (0, 4), (1, 3, 7), (2, 6, 5), (0, 3), (1, 6), (2, 4), (5, 7)))

Now, there is a `star_syndrome_measure` method in SurfaceCodeCircuit which implements the generalization of the following *syndrome measurement* found on page 108 of [Quantum Computation with Topological Codes: from qubit to topological fault-tolerance](https://arxiv.org/pdf/1504.01444.pdf):

The picture above (on the right) is for *star* syndrome measurements of the *toric code*, however our implementation will work for *arbitrary* surface codes given by *arbitrary* graphs embedded in surfaces. This will apply Controlled-$Z$ gates from a qubit corresponding to a vertex (given by a cycle in $\sigma$) to the target qubits given by the coboundary of that vertex, which are edges connected to it labeled by *white* nodes (corresponding to cycles in $\alpha$). The picture above (on the left) also has *plaquette* measurements. Again, our implementation will work for arbitrary surface codes (for arbitrary graphs embedded in surfaces). The *plaquette syndrome measurement* will apply Controlled-$NOT$ gates with source qubit given by a qubit corresponding to a face, and target qubits given by the boundary of that face. The boundary will correspond to *white* nodes (given by cycles in $\alpha$). Let's apply the star syndrome measurement for the vertex $(0,1,2)$.

```python
SCC.star_syndrome_measure(((0,1,2)))
SCC.circ.draw('mpl')
```

Now, let's apply the plaquette syndrome measurement corresponding to the face $(2,6,5)$.

```python
SCC.face_syndrome_measure(((2, 6, 5)))
SCC.circ.draw('mpl')
```

Again, it is important to note that if our list of faces or stars is given as input for the above two functions has faces with edges in common, an even number of occurences of the same edge means that edge doesn't show up in the output. On the other hand if there are an odd number of occurence of an edge, that edge does show up in the output. This reflects the $\mathbb{Z}/2\mathbb{Z}$-chain complex structure. This can all be phrased in terms of linear algebra and treating $V$, $E$, and $F$ as vector spaces over $\mathbb{Z}/2\mathbb{Z}$. For more background on the math involved see Chapter 3 of [Quantum Computation with Topological Codes: from qubit to topological fault-tolerance](https://arxiv.org/pdf/1504.01444.pdf).

At this point we should mention that a list of faces will form a $2$-chain, and a list of stars (vertices in $\sigma$) forms a $0$-cochain. From this, along with the boundary and coboundary operators we just defined, we can define the Pauli product operators. The first kind of Pauli product operators we define are

\begin{align}
W(c_1) = \bigotimes_{k}W(e_k)^{n(k)},
\end{align}

where $n(k) \in \{0,1\}$, and $c_1$ is a "$1$-chain" given by a list of edges (each edge corresponding to a cycle in $\alpha$). Each $W_k \in \{X_k, Y_k, Z_k\}$ is a Pauli operator that operates on the $k^{th}$ qubit, where $k$ is the index of the qubit corresponding to an edge. We can get this index using `node_info`, in particular `alpha_dict`, the middle dictionary, which gives us the indices of qubits corresponding to each edge (white node).

Now, if we compute the boundary $\partial_2(c_2)$ of a $2$-chain (given by a list of faces), we will get a $1$-chain. This $1$-chain will be a list of edges that form the boundary of the faces. From this, we can compute the "**plaquette operators**", i.e. **face operators**,

\begin{align}
Z(\partial_2f_k) = \bigotimes_{e_j \in \partial_2f_k} Z(e_j)
\end{align}

where $\partial_2f_k$ is the edge list forming the boundary of the face $f_k \in F$. We can also computer the "**star operators**",

\begin{align}
X(\delta_1 v_m) = \bigotimes_{e_j \in \delta_1v_m} X(e_j),
\end{align}

where $\delta_1 v_m$ is the edge list forming the *coboundary* of the vertex $v_m$ (which corresponds to some cycle permutation in $\sigma$). These form the **stabilizer generators** for the surface code. For a refresher on stabilizer generators we refer to the notebooks on [error correction](https://github.com/The-Singularity-Research/error-correction) and on [graph states](https://github.com/The-Singularity-Research/graph-state-quantum-cryptography/blob/master/certifying_graph_states.ipynb). Now, let's define a Pauli product $Z$ operator $Z(\partial_2 c_2)$ for the boundary of an arbitray $2$-chain $c_2$, as well as a Pauli product $X$ operator $X(\delta_1 c_0)$ for the coboundary of an arbitrary $0$-cochain $c_0$. The following image can be found on page 76 of [Quantum Computation with Topological Codes: from qubit to topological fault-tolerance](https://arxiv.org/pdf/1504.01444.pdf), and the table below can be found on page 68.

We can apply the *Pauli product*-$Z$ operator corresponding to arbitrary $2$-chain boundaries. Let's do this for the list of two faces $[(0, 4), (2, 6, 5)]$.

```python
SCC.product_Z([(0, 4), (2, 6, 5)])
SCC.circ.draw('mpl')
```

We can also apply the *Pauli product*-$X$ operator corresponding to arbitrary $0$-cochain coboundaries. Let's do this for the list of two vertices $[(3, 4, 5), (6, 7)]$.

```python
SCC.product_X([(3, 4, 5), (6, 7)])
SCC.circ.draw('mpl')
```

The two methods `product_Z` and `product_X` use the methods `del_2` and `delta_1` of `SurfaceCodeGraph`, respectively, in order to compute boundaries of faces and coboundaries of vertices to apply the Pauli-$Z$ and Pauli-$X$ gates to.

---
## Example 2
---

The following choice for $[\sigma, \alpha]$ will construct a graph on a sphere, which we can check by computing the genus of the surface.

```python
sigma = ((0,1),(2,3,4),(5,6,7),(8,9),(10,11,12),(13,14,15,16),(17,18,19,20),(21,22,23),(24,25,26),(27,28,29,30),(31,32,33,34),(35,36,37),(38,39),(40,41,42),(43,44,45),(46,47))
alpha = ((0,2),(1,10),(3,5),(4,14),(6,8),(7,18),(9,22),(11,13),(12,24),(15,17),(16,28),(19,21),(20,32),(23,36),(25,27),(26,38),(29,31),(30,41),(33,35),(34,44),(37,47),(39,40),(42,43),(45,46))
```

```python
SCG = SurfaceCodeGraph(sigma, alpha)
SCG.genus()
```

    0

Now, we know this graph has a planar embedding since its genus is $g = 0$. So, let's attempt to use the `planar` drawing option to draw the graph in the plane.

```python
SCG.draw('dict', 'planar')
```

Clearly, this is a confusing drawing. So, what other options do we have? We can plot with the layout options:

- `'spring'`
- `'spectral'`
- `'planar'`
- `'shell'`
- `'circular'`
- `'spiral'`
- `'random'`

So, let's try the `'spectral'` option.

```python
SCG.draw('dict', 'spectral')
```

This is a little better, but it's still quite messy. Let's find out how many nodes the graph has.

```python
len(SCG.code_graph.nodes())
```

    50

The number of vertices is just the number of cycles in `sigma`.

```python
len(SCG.sigma)
```

    16

The number of edges is the number of cycles in `alpha`.

```python
len(SCG.alpha)
```

    24

The number of faces is the number of cycles in `phi`.

```python
len(SCG.phi)
```

    10

Let's print off the faces.

```python
SCG.phi
```

    ((0, 10, 24, 38, 40, 43, 46, 37, 23, 9, 6, 3),
     (1, 2, 14, 11),
     (4, 5, 18, 15),
     (7, 8, 22, 19),
     (12, 13, 28, 25),
     (16, 17, 32, 29),
     (20, 21, 36, 33),
     (26, 27, 41, 39),
     (30, 31, 44, 42),
     (34, 35, 47, 45))

Now, let's print off all of the nodes, including those labeling the edges given by the cycles in `alpha`.

```python
SCG.code_graph.nodes()
```

    NodeView(((0, 1), (2, 3, 4), (5, 6, 7), (8, 9), (10, 11, 12), (13, 14, 15, 16), (17, 18, 19, 20), (21, 22, 23), (24, 25, 26), (27, 28, 29, 30), (31, 32, 33, 34), (35, 36, 37), (38, 39), (40, 41, 42), (43, 44, 45), (46, 47), (0, 10, 24, 38, 40, 43, 46, 37, 23, 9, 6, 3), (1, 2, 14, 11), (4, 5, 18, 15), (7, 8, 22, 19), (12, 13, 28, 25), (16, 17, 32, 29), (20, 21, 36, 33), (26, 27, 41, 39), (30, 31, 44, 42), (34, 35, 47, 45), (0, 2), (1, 10), (3, 5), (4, 14), (6, 8), (7, 18), (9, 22), (11, 13), (12, 24), (15, 17), (16, 28), (19, 21), (20, 32), (23, 36), (25, 27), (26, 38), (29, 31), (30, 41), (33, 35), (34, 44), (37, 47), (39, 40), (42, 43), (45, 46)))

Now, we're going to remove one of the face nodes and all of the edges connected to it.

```python
SCG.code_graph.remove_node((0, 10, 24, 38, 40, 43, 46, 37, 23, 9, 6, 3))
```

```python
len(SCG.code_graph.nodes())
```

    49

Now, let's try using the NetworkX methods for drawing the spectral layout of the graph without the newly deleted node.

```python
nx.draw_spectral(SCG.code_graph, with_labels = False)
```

Clearly, this graph can be drawn as a grid in the plane. The way we have constructed it, it is actually isomorphic to a $7 \times 7$ grid of qubits with nearest neighbor connectivity. Let's check this by defining such a graph in NetworkX.

```python
G = nx.Graph()
```

```python
pos = dict()

for x in range(7):
    for y in range(7):
        G.add_node((x,y))
        pos[(x,y)] = (x,y)
        if x>0:
            G.add_edge((x-1,y),(x,y))
        if y>0:
            G.add_edge((x,y),(x,y-1))
nx.draw(G, pos=pos, with_labels = True)

```

We can check that the two graphs are isomorphic as follows.

```python
nx.is_isomorphic(SCG.code_graph, G)
```

    True

Now, we are going to use a NetworkX method to find the actual isomorphism.

```python
G1, G2 = SCG.code_graph, G
```

```python
GM = nx.isomorphism.GraphMatcher(G1,G2)
GM.is_isomorphic()
```

    True

The following gives us the mapping of the actual nodes. The keys in the dictionary are the nodes of the graph `SCG.code_graph` (without the deleted face node), the values are the nodes of the graph we just constructed.

```python
GM.mapping
```

    {(0, 1): (0, 0),
     (1, 10): (0, 1),
     (10, 11, 12): (0, 2),
     (12, 24): (0, 3),
     (24, 25, 26): (0, 4),
     (26, 38): (0, 5),
     (38, 39): (0, 6),
     (0, 2): (1, 0),
     (1, 2, 14, 11): (1, 1),
     (11, 13): (1, 2),
     (12, 13, 28, 25): (1, 3),
     (25, 27): (1, 4),
     (26, 27, 41, 39): (1, 5),
     (39, 40): (1, 6),
     (2, 3, 4): (2, 0),
     (4, 14): (2, 1),
     (13, 14, 15, 16): (2, 2),
     (16, 28): (2, 3),
     (27, 28, 29, 30): (2, 4),
     (30, 41): (2, 5),
     (40, 41, 42): (2, 6),
     (3, 5): (3, 0),
     (4, 5, 18, 15): (3, 1),
     (15, 17): (3, 2),
     (16, 17, 32, 29): (3, 3),
     (29, 31): (3, 4),
     (30, 31, 44, 42): (3, 5),
     (42, 43): (3, 6),
     (5, 6, 7): (4, 0),
     (7, 18): (4, 1),
     (17, 18, 19, 20): (4, 2),
     (20, 32): (4, 3),
     (31, 32, 33, 34): (4, 4),
     (34, 44): (4, 5),
     (43, 44, 45): (4, 6),
     (6, 8): (5, 0),
     (7, 8, 22, 19): (5, 1),
     (19, 21): (5, 2),
     (20, 21, 36, 33): (5, 3),
     (33, 35): (5, 4),
     (34, 35, 47, 45): (5, 5),
     (45, 46): (5, 6),
     (8, 9): (6, 0),
     (9, 22): (6, 1),
     (21, 22, 23): (6, 2),
     (23, 36): (6, 3),
     (35, 36, 37): (6, 4),
     (37, 47): (6, 5),
     (46, 47): (6, 6)}

So, we could represent the original graph as a grid of qubits on the sphere, with on face node outside of the grid on the opposite side of the sphere, which is connected to all of the edge nodes around the outside of the grid of qubits we just drew above. To see exactly which nodes correspond to vertices, edges, and faces, let's build a new coloring of the nodes of the graph `G`.

```python
node_color = {(x[0], x[1]): 1-((x[0]+x[1])%2)/2 for x in G.nodes()}

for y in range(1,7,2):
    for z in range(1,7,2):
        node_color[(y,z)] = .2

node_color = [node_color[x] for x in sorted(node_color.keys())]
```

```python
nx.draw(G, pos = pos, node_color = node_color)
```

Here, the yellow nodes are vertices (cycle in $\sigma$), the blue nodes are edges (cycles in $\alpha$), and the purple nodes are faces (cycles in $\phi$).

---
## References
---
### Quantum Computing and Surface Codes

- [Quantum Computation with Topological Codes: from qubit to topological fault-tolerance](https://arxiv.org/pdf/1504.01444.pdf)

- [A graph-based formalism for surface codes and twists](https://www.youtube.com/watch?v=Ca85qdptceQ)

- [Constructions and Noise Threshold of Hyperbolic Surface Codes](https://arxiv.org/pdf/1506.04029.pdf)

- [Homological Quantum Codes Beyond the Toric Code](https://arxiv.org/pdf/1802.01520.pdf)

- [Hyperbolic Lattices in Circuit Quantum Electrodynamics](https://arxiv.org/pdf/1802.09549.pdf)

- [Quantum Simulation of Hyperbolic Space with Circuit Quantum Electrodynamics: From Graphs to Geometry](https://arxiv.org/pdf/1910.12318.pdf)

- [Quantum Error Correction for Quantum Memories](https://arxiv.org/pdf/1302.3428.pdf)

- [Ribbon Graphs on nLab](https://ncatlab.org/nlab/show/ribbon+graph)

### Quantum Gravity and String Theory

- [Maxim Kontsevich, Intersection Theory on the Moduli Space of Curves and the Matrix Airy Function](http://pagesperso.ihes.fr/~maxim/TEXTS/Intersection%20theory%20and%20Airy%20function.pdf)

- [Ed Witten, On the Kontsevich Model and other Models of Two-Dimensional Gravity](https://lib-extopc.kek.jp/preprints/PDF/1992/9203/9203552.pdf)

- [Lando & Zvonkin, Graphs on Surfaces and Their Applications](https://link.springer.com/book/10.1007/978-3-540-38361-1)

### The Langlands Program and the Generalized Riemann Hypothesis

- [SURFACE ALGEBRAS I: DESSINS D’ENFANTS, SURFACE ALGEBRAS, AND DESSIN
ORDERS](https://arxiv.org/pdf/1810.06750.pdf)

- [SURFACE ALGEBRAS AND SURFACE ORDERS II: AFFINE BUNDLES ON CURVES](https://arxiv.org/pdf/1812.00621.pdf)

### Hardware Related Papers

- [Hyperbolic Lattices in Circuit Quantum Electrodynamics](https://arxiv.org/pdf/1802.09549.pdf)

- [Hyperbolic Lattices in Circuit Quantum Electrodynamics](https://arxiv.org/pdf/1802.09549.pdf)


# Poroplastic Data Fitting

```python
import numpy as np
from numpy import *
from bokeh import *
from bokeh.plotting import *
output_notebook()
from matmodlab2 import *
from pandas import read_excel
from scipy.optimize import leastsq
diff = lambda x: np.ediff1d(x, to_begin=0.)
trace = lambda x, s='SIG': x[s+'11'] + x[s+'22'] + x[s+'33']
RTJ2 = lambda x: sqrt(((x['SIG11']-x['SIG22'])**2 +
                       (x['SIG22']-x['SIG33'])**2 +
                       (x['SIG33']-x['SIG22'])**2)/6.)
```

    Loading BokehJS ...

    Setting up the Matmodlab notebook environment

## Summary

In the cells to follow, the following material parameters were found

$$\begin{align}
B_0 &= 14617807286.8\\
B_1 &= 40384983097.2\\
B_2 &= 385649437.858\\
P_0 & = −164761936.257 \\
P_1 & = 3.20119273834e−10\\
P_2 & = 7.39166987894e−18\\
P_3 & = 0.0983914345654\\
G_1 & = 9647335534.93 \\
G_2 & = 2.3838775292e−09 \\
G_3 & = −7.40942609805e−07\\
\end{align}$$

## Read in the Data

Read in the hydrostatic data and compute derived values.

```python
df = read_excel('porodata.xlsx', sheetname='hydrostatic')
df['EV'] = trace(df, 'STRAIN')
df['I1'] = trace(df, 'SIG')
df['dEV'] = diff(df['EV'])
df['dI1'] = diff(df['I1'])
```

## Hydrostatic Response

### Elastic Unloading Curve

Plot the pressure vs. volume strain curve and determine the section in which elastic unloading occurs

```python
plot = figure(x_axis_label='Volume Strain', y_axis_label='Pressure')
plot.circle(-df['EV'], -df['I1']/3.)
plot.text(-df['EV'], -df['I1']/3.,
    text=range(len(df)),text_color="#333333",
    text_align="left", text_font_size="5pt")
show(plot)
```

It appears that the unloading occurs at data point 101 and continues until the end of the data.  This curve will be used to fit the bulk modulus parameters.  Below, `scipy` is used to optimize the parameters to the curve.

```python
kfun = lambda B0, B1, B2, I1: B0 + B1 * exp(-B2 / abs(I1))
def kmm_bulk(x, fac, I1, K):
    B0, B1, B2 = x * fac
    return K - kfun(B0, B1, B2, I1)
```

```python
imax = 101
df1 = df.iloc[imax:].copy()
K = np.array(df1['dI1'] / 3. / df1['dEV'])
b0 = np.array((K[-1], K[0] - K[-1], 1e9))
fac = 1e9
B, icov = leastsq(kmm_bulk, b0/fac, args=(fac, df1['I1'], K))
B0, B1, B2 = B * fac
B0, B1, B2
```

    (14617807276.85973, 40384983106.145226, 385649437.68283093)

```python
plot = figure(x_axis_label='Bulk Modulus', y_axis_label='Pressure')
plot.circle(-df1['I1']/3., K)
plot.line(-df['I1']/3., kfun(B0, B1, B2, df['I1']), color='red')
show(plot)
```

## Poro response

With the bulk response determined, find the porosity parameters

```python
df['EP'] = df['I1'] / 3. / kfun(B0, B1, B2, df['I1']) - df['EV']
p3 = max(df['EP'])
df['PORO'] = p3 - df['EP']
plot = figure(x_axis_label='Plastic Strain', y_axis_label='Pressure')
plot.circle(df['EP'], -df['I1']/3.)
show(plot)
```

```python
plot = figure(x_axis_label='Pressure', y_axis_label='PORO')
df2 = df.iloc[:imax].copy()
plot.circle(-df2['I1']/3., df2['PORO'])
show(plot)
```

```python
def pfun(P0, P1, P2, P3, I1):
    xi = -I1 / 3. + P0
    return P3 * exp(-(P1 + P2 * xi) * xi)

def kmm_poro(x, fac, I1, P):
    p0, p1, p2, p3 = asarray(x) * fac
    return P - pfun(p0, p1, p2, p3, I1)
```

```python
p0 = (1, 1, 1, p3)
fac = np.array([1e8, 1e-10, 1e-18, 1])
p, icov = leastsq(kmm_poro, p0, args=(fac, df2['I1'], df2['PORO']))
P0, P1, P2, P3 = p * fac
P0, P1, P2, P3
```

    (-28446120.517447289,
     -1.6950842119972055e-09,
     7.3916733442463017e-18,
     0.08958961426627815)

```python
plot = figure(x_axis_label='Pressure', y_axis_label='PORO')
plot.circle(-df2['I1']/3., df2['PORO'], legend='Data')
plot.line(-df2['I1']/3., pfun(P0, P1, P2, P3, df2['I1']), color='red', legend='Fit')
show(plot)
```

## Shear Response

```python
keys = (2.5, 5.0, 7.5, 10.0, 12.5, 15.0, 22.5, 30.0)
colors = ('red', 'blue', 'orange', 'purple',
          'green', 'black', 'magenta', 'teal', 'cyan')
df2 = {}
p = figure(x_axis_label='I1', y_axis_label='Sqrt[J2]')
p1 = figure(x_axis_label='Axial Strain', y_axis_label='Axial Stress')
for (i, key) in enumerate(keys):
    key = 'txc p={0:.01f}MPa'.format(key)
    x = read_excel('porodata.xlsx', sheetname=key)
    x['I1'] = trace(x, 'SIG')
    x['RTJ2'] = RTJ2(x)
    df2[key] = x
    p.circle(-df2[key]['I1'], df2[key]['RTJ2'], legend=key[4:], color=colors[i])

    # determine where hydrostatic preload ends
    j = nonzero(x['SIG11'] - x['SIG22'])[0]
    E0, S0 = df2[key]['STRAIN11'][j[0]], df2[key]['SIG11'][j[0]]
    p1.circle(-df2[key]['STRAIN11'][j]+E0, -df2[key]['SIG11'][j]+S0,
              legend=key[4:], color=colors[i])

p.legend.orientation = 'horizontal'
show(p1)
show(p)
```

The axial stress versus axial strain plot shows that the response is linear, meaning that the elastic modulus is constant.

```python
key = 'txc p=2.5MPa'
j = nonzero(df2[key]['SIG11'] - df2[key]['SIG22'])[0]
df3 = df2[key].iloc[j].copy()
E0, S0 = df3['STRAIN11'].iloc[0], df3['SIG11'].iloc[0]
EF, SF = df3['STRAIN11'].iloc[-1], df3['SIG11'].iloc[-1]
E = (SF - S0) / (EF - E0)
print('{0:E}'.format(E))
```

    2.372314E+10

The shear modulus can now be determined

```python
G = lambda I1: 3 * kfun(B0, B1, B2, I1) * E / (9 * kfun(B0, B1, B2, I1) - E)
gfun = lambda g0, g1, g2, rtj2: g0 * (1 - g1 * exp(-g2 * rtj2)) / (1 - g1)
def kmm_shear(x, fac, rtj2, G):
    g0, g1, g2 = asarray(x) * fac
    return G - gfun(g0, g1, g2, rtj2)
```

```python
g = asarray(G(df3['I1']))
g0 = (g[0], .0001, 0)
fac = 1.
g, icov = leastsq(kmm_shear, g0, args=(fac, RTJ2(df3), g))
G0, G1, G2 = g * fac
G0, G1, G2
```

    (9647335536.3045807, 2.3838678121801188e-09, -7.4094283435851223e-07)

```python
p2 = figure(x_axis_label='Sqrt[J2]', y_axis_label='Shear Modulus')
p2.circle(RTJ2(df3), G(df3['I1']))
p2.line(RTJ2(df3), gfun(G0, G1, G2, RTJ2(df3)), color='red')
show(p2)
```

```python

```


# Diagnostic of SED fitting

```python
import numpy as np
import math
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
import matplotlib.patches as mpatches
import matplotlib as mpl
import astropy
import scipy.stats as stats
from scipy.stats import linregress, lognorm,gaussian_kde
import itertools
import sys
from matplotlib.ticker import MultipleLocator,FormatStrFormatter
from astropy.table import Column, Table, join
from math import log10, sqrt

def plot_mock(name,estimated,exact,estimated_err, markersize, alpha_val1, alpha_val2, title_text,xlim_min, xlim_max,ylim_min, ylim_max,fit_line,oneTOone, color):

    # Calculate the point density
    xy = np.vstack([estimated,exact])
    z = gaussian_kde(xy)(xy)

    idx = z.argsort()
    estimated, exact, z = estimated[idx], exact[idx], z[idx]

    scatter_kwargs = {"zorder":100}
    error_kwargs = {"lw":.5, "zorder":0}

    name.scatter(estimated,exact, c=z, s=markersize, edgecolor='',alpha=1,**scatter_kwargs)
    name.errorbar(estimated,exact,0, estimated_err, '-*',markersize=0.5,c='gray',linestyle='', alpha=alpha_val1, **error_kwargs)

    name.set_ylim(ylim_min, ylim_max)
    name.set_xlim(xlim_min, xlim_max)
    name.title.set_text(title_text)
    xaxis = np.linspace(xlim_min, xlim_max,2)
    a,b,Pr,c,d=linregress(estimated,exact)

    if (fit_line==1):
        fit = np.polyfit(estimated,exact, deg=1)
        xaxis = np.linspace(xlim_min, xlim_max,2)
        yaxis = (xaxis*fit[0]+fit[1])
        name.plot(xaxis,yaxis,'-s',c='black',linewidth=2,linestyle=':',)

    if (oneTOone==1):
        yaxis = xaxis
        name.plot(xaxis,yaxis,'-s',c='red',linewidth=3,linestyle='-',label='1:1')
        fit_result = mlines.Line2D([], [], c='red',linewidth=3,linestyle='-',label='1:1')
        Pearson_result = mlines.Line2D([], [], c='black',linewidth=2,linestyle=':',label='r= %.4f \ny=%.2f*x %+.2f'%(Pr,fit[0],fit[1]))
        handles_MS = [Pearson_result,fit_result]
        labels = [h.get_label() for h in handles_MS]
        name.legend(handles=handles_MS, labels=labels,loc='best',frameon=False, fontsize =20,markerscale=0.8)

def plot_histo(name,data,bins_no, range_min, range_max, multiloc, label_text, label_size):

    name.hist(data,bins_no, range=(range_min,range_max),normed=True,color="steelblue", edgecolor="steelblue", linewidth=3)
    name.yaxis.set_major_locator(plt.MultipleLocator(multiloc))
    name.set_xlabel(label_text, fontsize=label_size)

    return name

def plot_lognorm_fit(name, data,sigma_multiply_by, label_size):
    shape, loc, scale = stats.lognorm.fit(data, floc=0)
    mu = np.log(scale)
    sigma = shape
    M = np.exp(mu)
    s=np.sqrt(sigma)

    x = np.linspace(min(data), max(data), num=400)
    name.plot(x, stats.lognorm.pdf(x, shape, loc=0, scale=scale), 'r', linewidth=3)
    name.annotate('M = %.2f\ns = %.2f' % (M, s), xy=(1, 0), xycoords='axes fraction', fontsize=label_size, xytext=(-5, 5), textcoords='offset points', ha='right', va='bottom')
    name.axvline(x=(M+(sigma_multiply_by*s)), color='black', linestyle='--',linewidth=2)

    return name, M, s

def plot_MS(name,Mstar,SFR,SFR_err, Mstar_err, markersize, alpha_val1, alpha_val2, title_text,xlim_min, xlim_max,ylim_min, ylim_max,fit_line,color,bin_list):

    name.errorbar(Mstar,SFR,SFR_err, Mstar_err, '-*',markersize=1,c='gray',linewidth=2,linestyle='', alpha=alpha_val1)
    name.plot(Mstar,SFR,'-*',c=color,markersize=25,linestyle='', alpha=alpha_val2)
    name.set_ylim(ylim_min, ylim_max)
    name.set_xlim(xlim_min, xlim_max)
    name.title.set_text(title_text)

    for k in range(0,len(bin_list)-1):
        del tmp_xbin[:]
        del tmp_ybin[:]
        for l in range(0,len(Mstar)):
            if Mstar[l]>=bin_list[k] and Mstar[l]<bin_list[k+1]:
                tmp_xbin.append(Mstar[l])
                tmp_ybin.append(SFR[l])
        if len(tmp_xbin)>20:
            name.errorbar(np.mean(tmp_xbin),np.mean(tmp_ybin), yerr=np.std(tmp_ybin), xerr=np.std(tmp_xbin), marker='o', color='black',markersize=10,linewidth=3,linestyle='-',ecolor='black',zorder=3)
    if (fit_line==1):
        fit = np.polyfit(Mstar, SFR, deg=1)
        xaxis = np.linspace(xlim_min, xlim_max,2)
        yaxis = (xaxis*fit[0]+fit[1])
        name.plot(xaxis,yaxis,'-s',c='red',linewidth=2,linestyle='-',label='y=%.2f*x %+.2f'%(fit[0],fit[1]))
        fit_result = mlines.Line2D([], [], c='red',linewidth=2,linestyle='-',label='y=%.2f*x %+.2f'%(fit[0],fit[1]))
        handles_MS = [fit_result]
        labels = [h.get_label() for h in handles_MS]
        name.legend(handles=handles_MS, labels=labels,loc='best',frameon=False, fontsize =20,markerscale=0.8)

    return name

plt.rc('text', usetex=True)
plt.rc('font', family='serif')

plt.rcParams['axes.linewidth'] = 2
plt.rcParams['xtick.major.size'] = 15
plt.rcParams['xtick.minor.size'] = 6
plt.rcParams['ytick.major.size'] = 15
plt.rcParams['ytick.minor.size'] = 6
plt.rcParams['ytick.minor.size'] = 6
plt.rc('legend',**{'fontsize':20})
plt.rc("font", size=20)
mpl.rc('lines', linewidth=8, color='gold', linestyle="-")

bbox_props = dict(boxstyle="round", fc="white", ec="blue", alpha=0.9, lw=1)
tmp_xbin, tmp_ybin=[],[]

```

## Mock analysis

The mock catalogue was generated to check the reliability of the computed parameters. To perform this test, we used an option included in CIGALE, which allows for a creation of mock objects for each galaxy. The final step of verification of estimated parameters is to run CIGALE on the simulated sample using the same set of input parameters as for the original catalogue and to compare the output physical parameters of the artificial catalogue with the real ones.

Figure below presents the comparison of the output parameters of the mock catalogue created from the input parameters versus values estimated by the code for our real galaxy sample. The determinant value, which can characterise the reliability of the obtained values, is the Pearson product-moment correlation coefficient (r). The comparison between the results from the mock and real catalogues shows that CIGALE gives an excellent estimation of main physical parameters which we use for the final analysis (Ldust, Mstar and SFR).

```python
#################################################################################################
#################################################################################################
# MOCK
#################################################################################################
#################################################################################################

final = Table.read("COSMOS/CF4/UV+OPT+IR/results_mock.fits")
final.meta = None

font_label=20

f, ((plt1), (plt2), (plt3)) = plt.subplots(3,1,figsize=(8,15))
f.text(0.01, 0.5, r'$\rm Exact\mbox{ }values$', va='center', rotation='vertical',fontsize=50)
f.text(0.3, 0.03, r'$\rm Estimated\mbox{ }values $', va='center', fontsize=50)

plt.subplots_adjust(left=0.3, bottom=0.14, right=0.95, top=0.95, wspace=0.5, hspace=0.4)
mock_final=final[(final['bayes.stellar.m_star']>0)]
plt1 = plot_mock(plt1,np.log10(mock_final['bayes.stellar.m_star']),np.log10(mock_final['best.stellar.m_star']), mock_final['bayes.stellar.m_star_err']/mock_final['bayes.stellar.m_star'],60, 0.7, 0.7, 'log(M_{star})\mbox{ }[M_{\odot}]', 8.9, 12.2,8.9, 12.2, 1, 1, 'blue')
plt2 = plot_mock(plt2,np.log10(mock_final['bayes.dust.luminosity']/(3.846*pow(10,26))),np.log10(mock_final['best.dust.luminosity']/(3.846*pow(10,26))), mock_final['bayes.dust.luminosity_err']/mock_final['bayes.dust.luminosity'],60, 0.7, 0.7, 'log(L_{dust})\mbox{ }[L_{\odot}]', 8.3, 13.7,8.3, 13.7, 1, 1, 'blue')
plt3 = plot_mock(plt3,np.log10(mock_final['bayes.sfh.sfr10Myrs']),np.log10(mock_final['best.sfh.sfr10Myrs']), mock_final['bayes.sfh.sfr10Myrs_err']/mock_final['bayes.sfh.sfr10Myrs'],60, 0.7, 0.7, 'log(SFR)\mbox{ }[M_{\odot}yr^{-1}]', -1.5, 4,-1.5, 4, 1, 1, 'blue')

plt.show()

```

## Quality of the SED fitting

The quality of the fitted SEDs is calculated making use of the $\chi^2$ value of the best model, marginalised over all parameters except the one assigned for the further physical analysis. The final output values of analysed parameters are calculated as the mean and standard deviation determined from the PDFs.

The distribution of the standard $\chi^2$ is shown in figure below (left bottom panel). To obtain the threshold value of the chi2 we fitted the lognormal distribution, and we found mean value and sigma, and we calculated the threshold as mean + 3sigma.

After visual inspection we have found galaxies with $\chi^2$ lower then threshold but with the wrong fit for dust or stellar part of the spectra. We decided to introduce two more $\chi^2$s values: one for optical part ($OPT\chi^2$), and one for infrared part of the spectra ($IR\chi^2$). We defined the wavelengh range for $OPT\chi^2$ and $IR\chi^2$ as < 8 um (in rest frame), and >=8 um (in rest frame),  respectively.
Distributions of both new $\chi^2s$ are shown in Figure below in the top panel. Again, we fitted lognormal distribution, and analogically we defined the thresholds ($OPT\chi^2_{threshold}$ and $IR\chi2_{threshold}$) as mean +4 sigma.

We select the final sample according to the equation:
\begin{equation}
\rm {\chi^2 \leq \chi2_{threshold} \wedge OPTchi^2 \leq OPT\chi2_{threshold} \wedge IR\chi^2 \leq IRchi^2_{threshold} }
\end{equation}
This equation allows us to select only good fits for further analysis (or to search for interesting cases).
Our final sample is shown in Figure below in right bottom panel.

```python

#################################################################################################
#################################################################################################
# RESULTS  - FULL SPECTRA
#################################################################################################
#################################################################################################

final = Table.read("COSMOS/CF4/UV+OPT+IR/cosmos_final_results.fits")
final.meta = None
sigma_multiply_by_IR_OPT_chi2=4
sigma_multyply_by_main_chi2=3
font_label=25

f, ((plt1, plt2), (plt3,plt4)) = plt.subplots(2,2,figsize=(20,12))
f.text(0.01, 0.5, r'$\rm Normalized\mbox{ }counts$', va='center', rotation='vertical',fontsize=60)

plt.subplots_adjust(left=0.1, bottom=0.14, right=0.95, top=0.95, wspace=0.5, hspace=0.4)

plt1 = plot_histo(plt1,final['UVoptIR_OPTchi2'],100,0,max(final['UVoptIR_OPTchi2'])-1, 0.5, r"$\rm \chi^2_{reduced}\mbox{ }OPT$", font_label)
plt1, M, s = plot_lognorm_fit(plt1, final['UVoptIR_OPTchi2'] ,sigma_multiply_by_IR_OPT_chi2,font_label)
treshold_chi2_OPT=M+(sigma_multiply_by_IR_OPT_chi2*s)
plt1.annotate(r"$\rm treshold_{mean+ %s\sigma}OPT = %.2f \mbox{ }(%s\mbox{ } gal.,\mbox{ } %.2f\mbox{ }perc.)$" % (sigma_multiply_by_IR_OPT_chi2,M+(sigma_multiply_by_IR_OPT_chi2*s), len(final[final['UVoptIR_OPTchi2']<treshold_chi2_OPT]), len(final[final['UVoptIR_OPTchi2']<treshold_chi2_OPT])*100/len(final)), xy=(1, 0.8), xycoords='axes fraction', fontsize=20, xytext=(-5, 5), textcoords='offset points', ha='right', va='bottom')

plt2 = plot_histo(plt2,final['UVoptIR_IRchi2'],100,0,max(final['UVoptIR_IRchi2'])-1, 0.5, r"$\rm \chi^2_{reduced}\mbox{ }IR $", font_label)
plt2, M, s = plot_lognorm_fit(plt2, final['UVoptIR_IRchi2'] ,sigma_multiply_by_IR_OPT_chi2,font_label)
treshold_chi2_IR=M+(sigma_multiply_by_IR_OPT_chi2*s)
plt2.annotate(r"$\rm treshold_{mean+ %s\sigma}IR = %.2f \mbox{ }(%s\mbox{ } gal.,\mbox{ } %.2f\mbox{ }perc.)$" % (sigma_multiply_by_IR_OPT_chi2,M+(sigma_multiply_by_IR_OPT_chi2*s), len(final[final['UVoptIR_IRchi2']<treshold_chi2_IR]), len(final[final['UVoptIR_IRchi2']<treshold_chi2_IR])*100/len(final)), xy=(1, 0.8), xycoords='axes fraction', fontsize=20, xytext=(-5, 5), textcoords='offset points', ha='right', va='bottom')

plt3 = plot_histo(plt3,final['UVoptIR_best.reduced_chi_square'],100,0,max(final['UVoptIR_best.reduced_chi_square'])-1, 0.5, r"$\rm \chi^2_{reduced}$", font_label)
plt3, M, s = plot_lognorm_fit(plt3, final['UVoptIR_best.reduced_chi_square'] ,sigma_multyply_by_main_chi2,font_label)
treshold_chi2_global=M+(sigma_multyply_by_main_chi2*s)
plt3.annotate(r"$\rm treshold_{mean+ %s\sigma} = %.2f \mbox{ }(%s\mbox{ } gal.,\mbox{ } %.2f\mbox{ }perc.)$" % (sigma_multyply_by_main_chi2,M+(sigma_multyply_by_main_chi2*s), len(final[final['UVoptIR_best.reduced_chi_square']<treshold_chi2_global]), len(final[final['UVoptIR_best.reduced_chi_square']<treshold_chi2_global])*100/len(final)), xy=(1, 0.8), xycoords='axes fraction', fontsize=20, xytext=(-5, 5), textcoords='offset points', ha='right', va='bottom')

ok_final = final[(final['UVoptIR_best.reduced_chi_square']<treshold_chi2_global) & (final['UVoptIR_IRchi2']<treshold_chi2_IR) &  (final['UVoptIR_OPTchi2']<treshold_chi2_OPT)]

plt4 = plot_histo(plt4,ok_final['UVoptIR_best.reduced_chi_square'],40,0,max(ok_final['UVoptIR_best.reduced_chi_square']), 0.5,r"$final\mbox{ }\chi^2_{reduced}$" , font_label)
plt4.annotate(r"$ FINAL\mbox{ }SAMPLE: %s\mbox{ } gal.,\mbox{ } (%.2f\mbox{ }perc.)$"%(len(ok_final), len(ok_final)*100/len(final)), xy=(1, 0.8), xycoords='axes fraction', fontsize=25, xytext=(-5, 5), textcoords='offset points', ha='right', va='bottom')

plt.show()

```

## Comparison with SED fitting based on the FIR data only

We check our Ldust results by comparion with SED fitting based on the FIR data only (the same parameters were used but as a dataset only FIR data were introcuded).

Figure below shows a very good comparison between both runs (the comparison was performed for all galaxies which fulfil $chi^2$s criteria:

```python
#################################################################################################
# Ldust full data set vs Ldust IRonly
#################################################################################################

fit_line=1
color='cyan'
bin_list=[7.75, 8.25, 8.75, 9.25, 9.75, 10.25, 10.75, 11.25, 11.75, 12.25, 12.75, 13.25]

fig, plt1 = plt.subplots(figsize=(10,8))
plt.subplots_adjust(left=0.1, bottom=0.16, right=0.95, top=0.90, wspace=None, hspace=None)

plt1 = plot_MS(plt1,ok_final['UVoptIR_logLdust'],ok_final['IR_logLdust'], ok_final['UVoptIR_logLdust_err'],ok_final['IR_logLdust_err'], 25, 0.3, 0.5,r"$\rm N=%s $"%len(ok_final),9.8,13.4,9.8, 13.4,fit_line,color,bin_list)
xline = np.linspace(9,14,2)
yline = (xline)
plt1.plot(xline,yline,'-s',c='black',linewidth=2, linestyle=':',label='1:1')
plt1.annotate(r"$\rm 1:1 $", xy=(0.1, 0.8), xycoords='axes fraction', fontsize=30, xytext=(-5, 5), textcoords='offset points', ha='right', va='bottom')

plt.xlabel(r'$\rm log(Ldust)_{all}\mbox{ }[M_{\odot}] $', fontsize=40)
plt.ylabel(r'$\rm log(Ldust)_{IRonly}\mbox{ }[M_{\odot}] $', fontsize=50)

plt.show()

```

```python

```

```python

```