```
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
```

#Linear Algebra

## 1.1 Graph vector $\vec{a}$

\begin{align}
\vec{a} = \begin{bmatrix} 3 \\ 2 \end{bmatrix}
\end{align}

```
# define a
a = [3,2]

# plot a
plt.arrow(0,0,a[0],a[1],head_width=.1, head_length=.1, color ='blue')

plt.xlim(0,5)
plt.ylim(0,5)
```

## 1.2 Find $||\vec{b}||$. What does the norm of a vector represent?
\begin{align}
\vec{b} = \begin{bmatrix} 17 & -4 & -2 & 1\end{bmatrix}
\end{align}

- The norm of a vector  (||vector||) is its length.

```
# Define b
b = [17, -4, -2, 1]

# Find the length of b
norm_b = sum([x**2 for x in b])**.5
norm_b
```

    17.60681686165901

## 1.3 Find $\vec{c} \cdot \vec{d}$

\begin{align}
\vec{c} = \begin{bmatrix}3 & 7 & -2 & 12\end{bmatrix}
\qquad
\vec{d} = \begin{bmatrix}9 & -7 & 4 & 6\end{bmatrix}
\end{align}

```
# Define c and d
c = [3, 7, -2, 12]
d = [9, -7, 4, 6]

# Do manual dot product of c and d
c_dot_d = [(c[0]*d[0] + c[1]*d[1] + c[2]*d[2] + c[3]*d[3])]

# Print results and check with builtin
print(c_dot_d, '\nCheck:\n', np.dot(c,d))
```

    [42]
    Check:
     42

## 1.4 Find $E^{-1}$ and $E^{T}$

\begin{align}
E =
\begin{bmatrix}
    7 & 4 & 2 \\
    1 & 3 & -1 \\
    2 & 6 & -4
\end{bmatrix}
\end{align}

```
# Define E
E = np.array([[7,4,2],[1,3,-1],[2,6,-4]])

# E transpose manually
E_Trans = np.array([[7,1,2],[4,3,6],[2,-1,-4]])

# Determinant manually
E_det = (7 *((3*-4) - (-1*6)) + 4*((-1*2) - (1*-4)) + 2*((1*6) - (3*2)))

E_inv = np.linalg.inv(E)

print(E_inv,'\n\n', E_Trans,'\n\n', E_det, '\n\nCheck Det:', np.linalg.det(E))
```

    [[ 0.17647059 -0.82352941  0.29411765]
     [-0.05882353  0.94117647 -0.26470588]
     [ 0.          1.         -0.5       ]]

     [[ 7  1  2]
     [ 4  3  6]
     [ 2 -1 -4]]

     -34

    Check Det: -34.00000000000001

# Intermediate Linear Algebra

## 2.1 Suppose that the number of customers at a ski resort as well as the number of inches of fresh powder (snow)  was recorded for 7 days.

### Customers: [820, 760, 1250, 990, 1080, 1450, 1600]

### Inches of new snow: [0, 1, 7, 1, 0, 6, 4 ]

## Find the mean, variance, and standard deviation for both the number of customers and inches of new snow for the week. You may use library functions, dataframes, .describe(), etc.

```
customers = [820, 760, 1250, 990, 1080, 1450, 1600]
snow = [0, 1, 7, 1, 0, 6, 4]

df = pd.DataFrame({'customers': customers, 'snow': snow})

df.head()
```

      customers
      snow

      0
      820
      0

      1
      760
      1

      2
      1250
      7

      3
      990
      1

      4
      1080
      0

```
df.describe()
```

      customers
      snow

      count
      7.000000
      7.000000

      mean
      1135.714286
      2.714286

      std
      314.264069
      2.927700

      min
      760.000000
      0.000000

      25%
      905.000000
      0.500000

      50%
      1080.000000
      1.000000

      75%
      1350.000000
      5.000000

      max
      1600.000000
      7.000000

## 2.2 Are the variances of the number of customers and inches of snow comparable?
## Why or why not?

Varaince between snow and customers are not comparable because they are measuring completely different things. We could as easily change snow units to milimeters and the variance would increase because the measurements are in larger numbers, but the number of customers would remain the same even if they are causally linked because the measurement does not effect the actual amount of snow.

```
df.var()
```

    customers    98761.904762
    snow             8.571429
    dtype: float64

## 2.3 Find the variance-covariance matrix for the number of customers and inches of snow at the ski resort.

```
#making sure this is behaving like I expected
df.T.values
```

    array([[ 820,  760, 1250,  990, 1080, 1450, 1600],
           [   0,    1,    7,    1,    0,    6,    4]])

```
# Use pandas variance covariance matrix function
df.cov()
```

      customers
      snow

      customers
      98761.904762
      670.238095

      snow
      670.238095
      8.571429

```
# Numpy's var/cov matrix function. has more controll, sometimes one of these
# does not behave like it should, aways good to check them against eachother
np.cov(df.T)
```

    array([[9.87619048e+04, 6.70238095e+02],
           [6.70238095e+02, 8.57142857e+00]])

```
# Show correlation
df.corr()
```

      customers
      snow

      customers
      1.000000
      0.728463

      snow
      0.728463
      1.000000

# PCA

## 3.1 Standardize the data so that it has a mean of 0 and a standard deviation of 1. (You may use library functions)

We have included some code to get you started so that you don't get stuck on something that isn't standardizing the data or PCA.

This might be helpful:

<https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html>

```
# Let me get you some data to start you off.
import pandas as pd

data = {"Country": ["England","Wales","Scotland","North Ireland"],
        "Cheese": [105,103,103,66],
        "Carcass_Meat": [245,227,242,267],
        "Other_Meat": [685, 803, 750, 586],
        "Fish": [147, 160, 122, 93],
        "Fats_and_Oils": [193, 235, 184, 209],
        "Sugars": [156, 175, 147, 139],
        "Fresh_Potatoes": [720, 874, 566, 1033],
        "Fresh_Veg": [253, 265, 171, 143],
        "Other_Veg": [488, 570, 418, 355],
        "Processed_Potatoes": [198, 203, 220, 187],
        "Processed_Veg": [360, 365, 337, 334],
        "Fresh_Fruit": [1102, 1137, 957, 674],
        "Cereals": [1472, 1582, 1462, 1494],
        "Beverages": [57,73,53,47],
        "Soft_Drinks": [1374, 1256, 1572, 1506],
        "Alcoholic Drinks": [375, 475, 458, 135],
        "Confectionery": [54, 64, 62, 41]}

df1 = pd.DataFrame(data)

# Look at the data
df1.head()
```

      Country
      Cheese
      Carcass_Meat
      Other_Meat
      Fish
      Fats_and_Oils
      Sugars
      Fresh_Potatoes
      Fresh_Veg
      Other_Veg
      Processed_Potatoes
      Processed_Veg
      Fresh_Fruit
      Cereals
      Beverages
      Soft_Drinks
      Alcoholic Drinks
      Confectionery

      0
      England
      105
      245
      685
      147
      193
      156
      720
      253
      488
      198
      360
      1102
      1472
      57
      1374
      375
      54

      1
      Wales
      103
      227
      803
      160
      235
      175
      874
      265
      570
      203
      365
      1137
      1582
      73
      1256
      475
      64

      2
      Scotland
      103
      242
      750
      122
      184
      147
      566
      171
      418
      220
      337
      957
      1462
      53
      1572
      458
      62

      3
      North Ireland
      66
      267
      586
      93
      209
      139
      1033
      143
      355
      187
      334
      674
      1494
      47
      1506
      135
      41

```
df2 = df1.drop('Country', axis=1)
```

```
df2means = df2.mean()
df2std = df2.std()
df2means
```

    Cheese                  94.25
    Carcass_Meat           245.25
    Other_Meat             706.00
    Fish                   130.50
    Fats_and_Oils          205.25
    Sugars                 154.25
    Fresh_Potatoes         798.25
    Fresh_Veg              208.00
    Other_Veg              457.75
    Processed_Potatoes     202.00
    Processed_Veg          349.00
    Fresh_Fruit            967.50
    Cereals               1502.50
    Beverages               57.50
    Soft_Drinks           1427.00
    Alcoholic Drinks       360.75
    Confectionery           55.25
    dtype: float64

```
cendf = (df2 - df2means.values) / df2std
covdf = cendf.T.cov()
```

```
covdf
```

      0
      1
      2
      3

      0
      0.199633
      0.058023
      -0.031143
      -0.226513

      1
      0.058023
      0.625020
      -0.273208
      -0.409836

      2
      -0.031143
      -0.273208
      0.520300
      -0.215948

      3
      -0.226513
      -0.409836
      -0.215948
      0.852297

```
cendf
```

      Cheese
      Carcass_Meat
      Other_Meat
      Fish
      Fats_and_Oils
      Sugars
      Fresh_Potatoes
      Fresh_Veg
      Other_Veg
      Processed_Potatoes
      Processed_Veg
      Fresh_Fruit
      Cereals
      Beverages
      Soft_Drinks
      Alcoholic Drinks
      Confectionery

      0
      0.570083
      -0.015152
      -0.224774
      0.558227
      -0.547700
      0.113060
      -0.389777
      0.747650
      0.327129
      -0.291214
      0.697564
      0.638613
      -0.557999
      -0.044962
      -0.376921
      0.090931
      -0.119774

      1
      0.464021
      -1.106061
      1.038240
      0.998043
      1.330128
      1.340571
      0.377324
      0.947023
      1.213892
      0.072804
      1.014638
      0.804795
      1.454458
      1.393816
      -1.216104
      0.729040
      0.838419

      2
      0.464021
      -0.196970
      0.470954
      -0.287572
      -0.950091
      -0.468392
      -1.156878
      -0.614734
      -0.429864
      1.310463
      -0.760979
      -0.049855
      -0.740950
      -0.404656
      1.031200
      0.620562
      0.646780

      3
      -1.498124
      1.318182
      -1.284421
      -1.268698
      0.167663
      -0.985239
      1.169331
      -1.079939
      -1.111157
      -1.092053
      -0.951223
      -1.393554
      -0.155508
      -0.944198
      0.561826
      -1.440533
      -1.365425

```
from sklearn.preprocessing import StandardScaler
```

```
scale = StandardScaler()
```

```
x = scale.fit(df2)
```

    /usr/local/lib/python3.6/dist-packages/sklearn/preprocessing/data.py:645: DataConversionWarning: Data with input dtype int64 were all converted to float64 by StandardScaler.
      return self.partial_fit(X, y)

    StandardScaler(copy=True, with_mean=True, with_std=True)

```
print(scale.fit_transform(df2))
```

    [[ 0.65827466 -0.01749546 -0.25954622  0.64458535 -0.63242919  0.13055067
      -0.45007561  0.86331183  0.37773603 -0.33626508  0.80547723  0.73740709
      -0.64432226 -0.05191741 -0.43523112  0.10499766 -0.13830319]
     [ 0.53580495 -1.27716878  1.19885635  1.15244047  1.53589947  1.54795798
       0.43569619  1.09352832  1.40168163  0.08406627  1.17160324  0.9292974
       1.67946294  1.60943981 -1.40423624  0.84182336  0.96812236]
     [ 0.53580495 -0.22744102  0.54381113 -0.33205912 -1.09707105 -0.54085279
      -1.33584741 -0.70983418 -0.49636387  1.51319287 -0.87870243 -0.0575671
      -0.85557546 -0.46725672  1.19072664  0.71656299  0.74683725]
     [-1.72988456  1.52210526 -1.48312126 -1.4649667   0.19360077 -1.13765587
       1.35022682 -1.24700598 -1.28305378 -1.26099406 -1.09837804 -1.60913739
      -0.17956522 -1.09026568  0.64874072 -1.66338402 -1.57665641]]

    /usr/local/lib/python3.6/dist-packages/sklearn/preprocessing/data.py:645: DataConversionWarning: Data with input dtype int64 were all converted to float64 by StandardScaler.
      return self.partial_fit(X, y)
    /usr/local/lib/python3.6/dist-packages/sklearn/base.py:464: DataConversionWarning: Data with input dtype int64 were all converted to float64 by StandardScaler.
      return self.fit(X, **fit_params).transform(X)

```
scale.mean_
```

    array([  94.25,  245.25,  706.  ,  130.5 ,  205.25,  154.25,  798.25,
            208.  ,  457.75,  202.  ,  349.  ,  967.5 , 1502.5 ,   57.5 ,
           1427.  ,  360.75,   55.25])

```

```

## 3.2 Perform PCA on the data and graph Principal Component 1 against Principal Component 2. (You may use library functions)

This might be helpful:

<https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html>

```
from sklearn.decomposition import PCA
```

```
pca = PCA(2)
pca.fit(df2.T)

df_evec = pd.DataFrame(pca.components_)
```

```
df_evec
```

      0
      1
      2
      3

      0
      0.490157
      0.498170
      0.504236
      0.507266

      1
      0.280842
      0.347597
      0.248220
      -0.859471

```
plt.scatter(df_evec.loc[0], df_evec.loc[1])
```

# Clustering

## 4.1 Use K-Means to cluster the following data and then graph your results. (You may use library functions)

We have included some code to get you started so that you don't get stuck on something that isn't standardizing clustering.

Prioritize calculating the clusters over graphing them.

Scikit-Learn K-Means Documentation:

<https://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html>

```
points = pd.read_csv('https://raw.githubusercontent.com/ryanleeallred/datasets/master/points.csv')
points.head()
```

      x
      y

      0
      -7.846803
      -3.421277

      1
      -3.554323
      -6.884729

      2
      -0.192822
      -9.671030

      3
      -6.401456
      -5.223972

      4
      -0.804026
      -9.704457

```
# Look at data to see if clusters number can be guessed.
# I am calling it 4, although if ther's time, I will try 5
# That bottom one could be either one or two

plt.scatter(points['x'], points['y'])
```

```
from sklearn.cluster import KMeans
```

```
#check using elbow method:

sum_of_squared_distances = []
K = range(1,8)
for k in K:
    km = KMeans(n_clusters=k)
    km = km.fit(points)
    sum_of_squared_distances.append(km.inertia_)
```

```
plt.plot(K, sum_of_squared_distances, 'bx-')
plt.xlabel('k')
plt.ylabel('Sum squared distances')
plt.title('Find good K')
plt.show()
```

```
# Use scikit to make cluster lables for point pairs
kmeans = KMeans(n_clusters=4)
kmeans.fit(points)
labels = kmeans.labels_
print(labels)
```

    [3 1 1 3 1 1 3 3 2 1 1 1 1 1 3 1 3 1 1 0 3 0 0 0 1 1 1 2 1 1 1 1 1 0 1 1 1
     1 2 1 1 0 2 2 2 1 1 1 3 1 0 3 3 2 0 1 1 2 2 3 0 1 3 2 0 2 0 2 3 2 1 1 1 1
     0 0 1 1 2 0 2 1 1 1 2 1 2 1 1 0 2 1 0 1 1 1 1 1 1 1 1 1 3 1 1 0 3 3 3 0 1
     0 1 3 1 1 2 2 0 2 0 1 1 3 1 3 1 0 2 3 1 3 1 3 2 2 1 0 3 1 2 1 1 0 1 2 1 2
     3 1 1 1 3 0 1 2 1 0 1 3 1 1 2 3 0 0 1 2 1 1 3 1 2 0 1 1 1 1 1 3 1 1 1 1 1
     1 3 0 2 1 1 3 0 3 1 2 0 0 1 1]

```
# Add cluster lables to our points dataframe
points['cluster_labels'] = pd.Series(labels)
```

```
# Make sure it worked
points.head()
```

      x
      y
      cluster_labels

      0
      -7.846803
      -3.421277
      3

      1
      -3.554323
      -6.884729
      1

      2
      -0.192822
      -9.671030
      1

      3
      -6.401456
      -5.223972
      3

      4
      -0.804026
      -9.704457
      1

```
#get_centr

centroid = points.groupby('cluster_labels').mean()

centroid
```

      x
      y

      cluster_labels

      0
      6.249793
      3.844572

      1
      -2.733593
      -8.204353

      2
      -3.597407
      4.133441

      3
      -8.366530
      -3.612381

```
colors = {0:'red', 1:'green', 2:'yellow', 3:'#84b3ff'}
fig, ax = plt.subplots()
ax.plot(centroid.iloc[0].x, centroid.iloc[0].y, "ok")
ax.plot(centroid.iloc[1].x, centroid.iloc[1].y, "ok")
ax.plot(centroid.iloc[2].x, centroid.iloc[2].y, "ok")
ax.plot(centroid.iloc[3].x, centroid.iloc[3].y, "ok")

groups = points.groupby('cluster_labels')

for key, group in groups:
  group.plot(ax=ax, kind='scatter', x='x', y='y', label=key, color=colors[key])
plt.show()

```

```

```


# `NUMERICAL POISSON GEOMETRY`

Este *notebook* fue utilizado en la charla **Un breve vistazo a la Geometría de Poisson Computacional** durante la realización del *Congreso Nacional Virtual de la Sociedad Matemática Mexicana en 2020*

Videos de la plática:
*   [Parte I](https://www.youtube.com/watch?v=27nBbz8BbL0&feature=emb_logo)
*   [Parte II](https://www.youtube.com/watch?v=PtTaK_Qef54&feature=emb_logo)

# Instalación y sintaxis
---

```
!pip install numericalpoissongeometry
```

### Bivector

El bivector asociado a $\mathfrak{so}(3)$ es

$$\Pi_{\mathfrak{so(3)}} = x_{3}\frac{\partial}{\partial x_{1}} \wedge \frac{\partial}{\partial x_{2}} - x_{2}\frac{\partial}{\partial x_{1}} \wedge \frac{\partial}{\partial x_{3}} + x_{1}\frac{\partial}{\partial x_{2}} \wedge \frac{\partial}{\partial x_{3}}$$

```
P_so3 = {(1, 2): 'x3', (1, 3): '-1*x2', (2, 3): 'x1'}
P_so3 = {(1, 2): 'x3', (1, 3): '-x2', (2, 3): 'x1'}
```

Recuerde lo siguiente para `Python`

*   `**` --> exponente
*   `*`  --> multiplicación
*   `+`  --> suma
*   `-`  --> resta
*   `/`  --> división

Más info https://docs.python.org/3/library/numeric.html

### Funciones Escalares

Sea $K_{\mathfrak{so}(3)}: \mathbb{R}^{3} \to \mathbb{R}$ definida como $$K_{\mathfrak{so}(3)}(x_{1}, x_{2}, x_{3}) = \frac{x^{2}_{1}}{2} + \frac{x^{2}_{2}}{2} + \frac{x^{2}_{3}}{2} $$

```
K_so3 = '(1/2)*(x1**2 + x2**2 + x3**2)'
K_so3 = '(x1**2)/2 + (x2**2)/2 + (x3**2)/2'
```

### Mallas

Las mallas que utiliza el módulo `NumericalPoissonGeometry` son las siguientes:
*   Lista de listas
*   Arreglos de `Numpy`

#### Lista de listas (`Python` nativo)

```
# Simple lista de listas en Python
R3_list = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
R3_list
```

#### Arreglos de `Numpy`

Para manipular una gran cantidad de puntos, es recomendable utilizar el módulo de `python` para análisis numérico `numpy`.

```
import numpy as np # Importamos el módulo numpy

 # Generamos un malla de 10**4 puntos con números aleatorios
R3_numpy_ramdon = np.random.rand(10**4, 3)
R3_numpy_ramdon
```

```
len(R3_numpy_ramdon)
```

### Primer uso del módulo

```
# Importamos el módulo NumericalPoissonGeometry
from numpoisson.numpoisson import NumPoissonGeometry as npg

# Instanciar el módulo NumericalPoissonGeometry con la dimensión y la variable a usar
npg3 = npg(3, 'x')
# Esta función muestra todos los métodos de una clase
dir(npg)
```

#### $\LaTeX$ bonito

```
import sympy
def custom_latex_printer(exp,**options):
    from google.colab.output._publish import javascript
    url = "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=default"
    javascript(url=url)
    return sympy.printing.latex(exp,**options)
sympy.init_printing(use_latex="mathjax",latex_printer=custom_latex_printer)
```

# Álgebra de Lie $\mathfrak{so}(3)$

---

Recordemos

$$\mathfrak{so}(3)\simeq\{ M \in Mat_{3\times 3}(\mathbb{R}) | M^{T} = -M \}$$

La estructura de álgebra de Lie en $\{ M \in Mat_{3\times 3}(\mathbb{R}) | M^{T} = -M \}$ es la inducida por el conmutador $[A, B]:= AB - BA$

El bivector asociado a $\mathfrak{so}(3)$ es

$$\Pi_{\mathfrak{so(3)}} = x_{3}\frac{\partial}{\partial x_{1}} \wedge \frac{\partial}{\partial x_{2}} - x_{2}\frac{\partial}{\partial x_{1}} \wedge \frac{\partial}{\partial x_{3}} + x_{1}\frac{\partial}{\partial x_{2}} \wedge \frac{\partial}{\partial x_{3}}$$

### Evaluación de la matriz asociada al bivector $\Pi_{\mathfrak{so(3)}}$

```
# Importamos el módulo NumericalPoissonGeometry
from numpoisson.numpoisson import NumPoissonGeometry as npg

# Instanciamos el módulo NumericalPoissonGeometry
npg3 = npg(3, 'x')
# Definimos el bivector asociado a so(3)
P_so3 = {(1, 2): 'x3', (1, 3): '-x2', (2, 3): 'x1'}
```

Para el caso de la malla `R3_list`

```
R3_list
```

```
# Importamos el módulo PoissonGeometry
from poisson.poisson import PoissonGeometry as pg

# Instanciamos el módulo PoissonGeometry
pg3 = pg(3, 'x')
# Calculamos la matriz asociada al bivector P_so3
pg3.bivector_to_matrix(P_so3)
```

Las funciones del método `NumericalPoissonGeometry` retornan 4 tipos de resultados:

*   `Numpy`,
*   tf_output  --->  `TensorFlow`
*   pt_output  --->  `Pytorch`
*   dict_output -->  `PoissonGeometry`

```
# Evaluamos la matriz asociada al bivector P_so3 en la malla R3_list
result_np = npg3.num_bivector_to_matrix(P_so3, R3_list)
result_np
```

```
type(result_np)
```

```
# Evaluamos la matriz asociado al bivector P_so3 en la malla R3_list
result_pt = npg3.num_bivector_to_matrix(P_so3, R3_list, pt_output=True)
result_pt
```

```
type(result_pt)
```

```
# Evaluamos la matriz asociada al bivector P_so3 en la malla R3_list
result_tf = npg3.num_bivector_to_matrix(P_so3, R3_list, tf_output=True)
result_tf
```

```
type(result_tf)
```

Para el caso de la malla `R3_numpy_ramdon`

```
# Evaluamos la matriz asociada al bivector P_so3 en la malla R3_numpy_ramdon
result_np = npg3.num_bivector_to_matrix(P_so3, R3_numpy_ramdon)
result_np
```

### Evaluación del bivector $\Pi_{\mathfrak{so(3)}}$

```
# Evaluamos el bivector P_so3 en la malla
npg3.num_bivector(P_so3, R3_list)
```

```
# Evaluamos el bivector asociado a P_so3 en la malla R3_list
result_dict = npg3.num_bivector(P_so3, R3_list, dict_output=True)
result_dict
```

```
type(result_dict)
```

#### Evaluación de $X_{h}$ relativo a $\Pi_{\mathfrak{so(3)}}$

Caso 1:

Sea $h = K_{\mathfrak{so}(3)}: \mathbb{R}^{3} \to \mathbb{R}$ definida como $$K_{\mathfrak{so}(3)}(x_{1}, x_{2}, x_{3}) = \frac{x^{2}_{1}}{2} + \frac{x^{2}_{2}}{2} + \frac{x^{2}_{3}}{2} $$

```
# Encontramos el campo vectorial asociado a K_so3 inducido por P_so3 con el módulo simbólico
pg3.hamiltonian_vf(P_so3, K_so3)
```

```
# Evaluamos el campo vectorial en la malla R3_numpy_ramdon
npg3.num_hamiltonian_vf(P_so3, K_so3, R3_numpy_ramdon, pt_output=True)
```

Caso 2:

Sea $h: \mathbb{R}^{3} \to \mathbb{R}$ definida como $$h(x_{1}, x_{2}, x_{3}) = \frac{x^{2}_{1}}{2} - \frac{x^{2}_{2}}{2} - \frac{x^{2}_{3}}{2} $$

```
# Definimos una función escalar h
h = '(x1**2)/2 - (x2**2)/2 - (x3**2)/2'
# Encontramos el campo vectorial asociado a K_so3 inducido por P_so3 con el módulo simbólico
pg3.hamiltonian_vf(P_so3, h)
```

```
# Evaluamos el campo vectorial en la malla R3_list
npg3.num_hamiltonian_vf(P_so3, h, R3_list)
```

```
# Evaluamos el campo vectorial en la malla R3_numpy_ramdon
npg3.num_hamiltonian_vf(P_so3, h, R3_numpy_ramdon)
```

# Álgebra de Lie $\mathfrak{sl}(2)$

---

Recordemos

$$\mathfrak{sl}(2)\simeq\{ M \in Mat_{3\times 3}(\mathbb{R}) | tr(M) = 0 \}$$

La estructura de álgebra de Lie en $\{ M \in Mat_{3\times 3}(\mathbb{R}) | tr(M) = 0 \}$ es la inducida por el conmutador $[A, B]:= AB - BA$

El bivector asociado a $\mathfrak{sl}(2)$ es

$$\Pi_{\mathfrak{sl(2)}} = - x_{3}\frac{\partial}{\partial x_{1}} \wedge \frac{\partial}{\partial x_{2}} - x_{2}\frac{\partial}{\partial x_{1}} \wedge \frac{\partial}{\partial x_{3}} + x_{1}\frac{\partial}{\partial x_{2}} \wedge \frac{\partial}{\partial x_{3}}$$

Evaluación del operador de cofrontera inducido por $\Pi_{\mathfrak{sl(2)}}$ en el campo vectorial

$$W = \frac{x_{1} x_{3} exp^{\frac{-1}{(x_1^2 + x_2^2 - x_3^2)^{2}}}}{x_1^2 + x_2^2}\frac{\partial}{\partial x_1} + \frac{x_{2} x_{3} exp^{\frac{-1}{(x_1^2 + x_2^2 - x_3^2)^{2}}}}{x_1^2 + x_2^2}\frac{\partial}{\partial x_2} + exp^{\frac{-1}{(x_1^2 + x_2^2 - x_3^2)^{2}}} \frac{\partial}{\partial x_3} $$

```
# Importamos el módulo NumericalPoissonGeometry
from numpoisson.numpoisson import NumPoissonGeometry as npg
# Importamos el módulo PoissonGeometry
from poisson.poisson import PoissonGeometry as pg

# Instanciar el módulo NumericalPoissonGeometry con la dimensión y la variable a usar
npg3 = npg(3, 'x')
# Instanciar el módulo PoissonGeometry con la dimensión y la variable a usar
pg3 = pg(3, 'x')

```

```
# Definimos el bivector asociado a so(3)
P_sl2 = {(1, 2): '-x3', (1, 3): '-x2', (2,3): 'x1'}
# Definimos el campo vectorial W
W = {(1,): 'x1 * x3* exp(-1/(x1**2 + x2**2 - x3**2)**2) / (x1**2 + x2**2)',
     (2,): 'x2 * x3* exp(-1/(x1**2 + x2**2 - x3**2)**2) / (x1**2 + x2**2)',
     (3,): 'exp(-1 / (x1**2 + x2**2 - x3**2)**2)'}
# Calculamos la imagen del operador de cofrontera inducido por P_sl2 en el campo vectorial W
pg3.lichnerowicz_poisson_operator(P_sl2, W)
```

Para tener un ejemplo distinto a cero redefinimos el campo vectorial

$$W = \frac{x_{1}exp^{\frac{-1}{(x_1^2 + x_2^2 - x_3^2)^{2}}}}{x_1^2 + x_2^2}\frac{\partial}{\partial x_1} + \frac{x_{2}exp^{\frac{-1}{(x_1^2 + x_2^2 - x_3^2)^{2}}}}{x_1^2 + x_2^2}\frac{\partial}{\partial x_2} + x_3 \frac{\partial}{\partial x_3} $$

```
# Redefinimos el campo vectorial W
W = {(1,): 'x1 * exp(-1/(x1**2 + x2**2 - x3**2)**2) / (x1**2 + x2**2)',
     (2,): 'x2 * exp(-1/(x1**2 + x2**2 - x3**2)**2) / (x1**2 + x2**2)',
     (3,): 'x3'}
# Calculamos la imagen del operador de cofrontera inducido por P_sl2 en el campo vectorial W
pg3.lichnerowicz_poisson_operator(P_sl2, W)
```

```
# Evaluamos la imagen del operador de cofrontera inducido por P_sl2 en el campo vectorial W en la malla R3_numpy_ramdon
npg3.num_coboundary_operator(P_sl2, W, R3_list)
```

```
npg3.num_coboundary_operator(P_sl2, W, R3_list, dict_output=True)
```

#### Evaluación de mapeo $\Pi_{\mathfrak{sl(2)}}^{\#}$

Sea $$\alpha = x_1 dx_1 + x_2 dx_2 + x_3 dx_3$$

Calculemos $\Pi_{\mathfrak{sl(2)}}^{\#}(\alpha)$ con ayuda del módulo simbólico.

```
# Definimos la 1-forma alpha
alpha = {(1,): 'x1', (2,): 'x2', (3,): '-x3'}
# Calculamos el morfismo sharp en alpha de manera simbólica
pg3.sharp_morphism(P_sl2, alpha)
```

```
# Evaluamos el morfismo sharp en alpha en la malla R3_numpy_ramdon
npg3.num_sharp_morphism(P_sl2, alpha, R3_numpy_ramdon)
```

Ahora consideremos $\alpha= (x_2-x_3)dx_1 + (x_3-x_1)dx_2 + (x_1-x_2)dx_3$

```
# Redefinimos la 1-forma alpha
alpha = {(1,): 'x2-x3', (2,): 'x3-x1', (3,): 'x1-x2'}
# Calculamos el morfismo sharp en alpha de manera simbólica
pg3.sharp_morphism(P_sl2, alpha)
```

```
# Evaluamos el morfismo sharp en alpha en la malla R3_list
npg3.num_sharp_morphism(P_sl2, alpha, R3_list)
```

#### Evaluación del corchete de Poisson asociado a $\Pi_{\mathfrak{sl(2)}}$

Calculamos $$\{x_1, x_3\}_{\Pi_{\mathfrak{sl(2)}}} = -x_2$$

```
# Definimmos las funciones f y g
f = 'x1'
g = 'x3'
# Calculamos el corchete de Poisson de forma simbólica
pg3.poisson_bracket(P_sl2, f, g)
```

```
# Evaluamos el corchete de Poisson en la malla R3_list
npg3.num_poisson_bracket(P_sl2, f, g, R3_list)
```

De igual manera para

*   $\{x_1, x_2\}_{\Pi_{\mathfrak{sl(2)}}} = -x_3$
*   $\{x_2, x_3\}_{\Pi_{\mathfrak{sl(2)}}} = x_1$

```
# Definimmos las funciones f y g
f = 'x1'
g = 'x2'
# Calculamos el corchete de Poisson de forma simbólica
pg3.poisson_bracket(P_sl2, f, g)
```

```
# Evaluamos el corchete de Poisson en la malla R3_list
npg3.num_poisson_bracket(P_sl2, f, g, R3_list)
```

```
# Definimmos las funciones f y g
f = 'x2'
g = 'x3'
# Calculamos el corchete de Poisson de forma simbólica
pg3.poisson_bracket(P_sl2, f, g)
```

```
# Evaluamos el corchete de Poisson en la malla R3_list
npg3.num_poisson_bracket(P_sl2, f, g, R3_list)
```

#### Los métodos

| **num_bivector_field**        | **num_bivector_to_matrix**    | **num_poisson_bracket**           |
| :---------------------------: | :---------------------------: | :------------------------------:  |
| **num_hamiltonian_vf**        | **num_sharp_morphism**        | **num_coboundary_operator**       |
| **num_modular_vf**            | **num_curl_operator**         | **num_one_forms_bracket**         |
| **num_gauge_transformation**  | **num_linear_normal_form_R3** | **num_flaschka_ratiu_bivector**   |

### Referencias

* Miguel Evangelista-Alvarado, José C. Ruíz Pantaleón & P. Suárez-Serrato,

 [On Computational Poisson Geometry I: Symbolic Foundations](https://arxiv.org/pdf/1912.01746.pdf),

   arXiv:1912.01746 [math.DG] (2019)

* https://github.com/mevangelista-alvarado/NumericalPoissonGeometry/blob/master/README.md


# 2-46 Compresibilidad
**Fecha:** 28/09/2021, **Alumno:** Nombre y Apellido

La densidad del agua de mar en una superficie libre donde la presión es de $98$ kPa es aproximadamente 1030 $kg/m^3$. Tome el **modulo de elasticidad de volumen del agua de mar** como $2.34 \times 10^9 $ N/m y expresando la variación de la presión con la profunidad $z$ como $dP = \rho g dz $. Determine la densidad a una produndidad de 2500 m.

# Datos

- Fluido: agua de mar
- $P_o = 98$ [kPa]
- $\rho_o = 1030 \: [kg/m^3]$
- $k = 2.34 \times 10^9 \: [N/m]$
- $z_o = 0 $ [m]
- $z =  2500$ [m]
- $\rho = $ (Variable a calcular )

```python

```

## Ecuación relacionada (Temperatura constante)

$$ k = \rho \frac{\partial P}{\partial \rho} $$

## Sustitutir

$$ \partial P = \rho g \partial z $$

##  k en función de z

$$ k = \rho \frac{\rho g}{\partial \rho} dz  = \rho^2 g \frac{\partial z}{\partial \rho}$$

## Reacomodamos

$$ \frac{1}{\rho^2} \partial \rho = \frac{g}{k} \partial z $$

## Integral y evaluar (Corregir)

$$ - (\frac{1}{\rho_o} - \frac{1}{\rho}) = \frac{g}{k} (z)$$

$$ \frac{1}{\rho} - \frac{1}{\rho_o} = \frac{g}{k} (z)$$

$$ \frac{1}{\rho} = \frac{1}{\rho_o}  + \frac{g}{k} (z)$$

$$ \rho = \frac{1}{\frac{1}{\rho_o}  + \frac{g z}{k}} $$

## Integrar (Corrección)
$$ \int_{\rho_o}^{\rho_f} \frac{1}{\rho^2} d\rho  = \int_{z_o}^{z_f} \frac{g}{k} dz$$

$$ \frac{-1}{\rho_f} - \frac{-1}{\rho_o} = \frac{gz_f}{k} - \frac{gz_o}{k}$$

$$ \frac{1}{\rho_o} - \frac{1}{\rho_f}  = \frac{gz_f}{k}$$
$$ \frac{1}{\rho_o} -  \frac{gz_f}{k}  = \frac{1}{\rho_f} $$

$$  \rho_f = \frac{1}{\frac{1}{\rho_o} -  \frac{gz_f}{k}}$$

```python
#Importr modulo para realizar integración
# URL: https://www.youtube.com/watch?v=yTDmlAaYx10

from sympy import *
init_printing()

#Declara variables a integrar
rho = symbols("rho")

# Define las funciones a evaluar (integrar)
f1 = 1/rho**2

# Ejemplo integral symbolica
integrate(f1,rho)

# Evaluar
```

```python
#Declarar constantes y variables
rho_o = 1030   #[kg/m^3]
g     = 9.8107 #[m/s^2]
k     = 2.34e9 #[N/m]
z_f   = 2500   #[m]
```

```python
# Función que calcula densidad a distancia z (rho_f)

```

```python
# Realizar cálculos

rho_f = 1/((1/rho_o)-(g*z_f/k))
round(rho_f,1)
```

    1041.2

```python
# Presentar resultados [kg/m3]
print(f'La densidad del agua a 2500 m de profunidad es: {round(rho_f,1)} kg/m^3')
```

    La densidad del agua a 2500 m de profunidad es: 1041.2 kg/m^3

```python

```


```python
import numpy as np
import scipy as sp
import sklearn as sk

import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pylab as plt
from mpl_toolkits.mplot3d import Axes3D

%matplotlib inline
```

    /home/kang/anaconda3/lib/python3.6/site-packages/matplotlib/font_manager.py:279: UserWarning: Matplotlib is building the font cache using fc-list. This may take a moment.
      'Matplotlib is building the font cache using fc-list. '

```python
def f(x) :
    return x**3 - 3*x**2 + x
```

```python
import numpy as np
x = np.linspace(-1, 3, 9)
```

    array([-1. , -0.5,  0. ,  0.5,  1. ,  1.5,  2. ,  2.5,  3. ])

```python
y = f(x)
```

```python
plt.plot(x, y, 'ro')

plt.xlim(-2, 4)
plt.xticks(np.arange(-1, 4))
plt.yticks(np.arange(-5, 4))

plt.show()
```

```python
x = np.linspace(-1, 3, 400, 'ro-')
y = f(x)
plt.plot(x, y)

plt.xlim(-2, 4)
plt.xticks(np.arange(-1, 4))
plt.yticks(np.arange(-5, 4))

plt.show()
```

```python
def get_slope(x, dx) :
    function = x**3 - 3*x**2 + x
    new_function = (x + dx)**3 - 3*(x + dx)**2 + (x +dx)

    slope = (new_function - function)/ dx
    return slope
```

```python
get_slope(2, 0.000000000005)
```

    1.000000082740371

```python
get_slope(1.5, 0.000000000005)
```

    -1.2501111257279263

```python
get_slope(-0.5, 0.00000000000005)
```

    4.75175454539567

```python
get_slope(2.5, 0.0000000000005)
```

    4.75175454539567

```python
np.e
```

    2.718281828459045

```python
def f(x, y) :
    return 2* x**2 + 6 * x * y + 7 * y ** 2 - 26 * x - 54 * y + 107
```

```python
xx = np.linspace(-5, 5, 100)
yy = np.linspace(-4, 4, 100)
X , Y = np.meshgrid(xx, yy)
Z = f(X, Y)
```

```python
from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure()
fig.gca(projection='3d').plot_surface(X, Y, Z)
plt.xlabel('x')
plt.ylabel('y')
plt.title("Surface Plot")
plt.show()

```

```python
CS = plt.contour(X, Y, Z, levels = np.arange(0, 300, 20))
plt.clabel(CS, fmt = "%d")
plt.title("Contour Plot")
plt.show()
```

$\frac{\partial f}{\partial x} = 2\,\sqrt{a}\,x$

```python
import sympy
sympy.init_printing(use_latex = 'mathjax')
```

```python
x = sympy.symbols('x')
x
```

$$x$$

```python
type(x)
```

    sympy.core.symbol.Symbol

```python
f = x * sympy.exp(x)
f
```

$$x e^{x}$$

```python
sympy.diff(f)
```

$$x e^{x} + e^{x}$$

```python
sympy.simplify(sympy.diff(f))
```

$$\left(x + 1\right) e^{x}$$

```python
x, y = sympy.symbols('x y')
f = x ** 2 + x * y + y ** 2
f
```

$$x^{2} + x y + y^{2}$$

```python
sympy.diff(f, x)
```

$$2 x + y$$

```python
sympy.diff(f, y)
```

$$x + 2 y$$

```python
x, mu , sigma = sympy.symbols('x mu sigma')
f = sympy.exp((x - mu) ** 2 / sigma ** 2)
f
```

$$e^{\frac{1}{\sigma^{2}} \left(- \mu + x\right)^{2}}$$

```python
sympy.diff(f, x)
```

$$\frac{1}{\sigma^{2}} \left(- 2 \mu + 2 x\right) e^{\frac{1}{\sigma^{2}} \left(- \mu + x\right)^{2}}$$

```python
sympy.simplify(sympy.diff(f, x))
```

$$\frac{2}{\sigma^{2}} \left(- \mu + x\right) e^{\frac{1}{\sigma^{2}} \left(\mu - x\right)^{2}}$$

```python
!pip install tangent
```

    Collecting tangent
      Downloading tangent-0.1.9.tar.gz (80kB)
        88% |████████████████████████████▎   | 71kB 677kB/s eta 0:00:01    100% |████████████████████████████████| 81kB 628kB/s
    Collecting astor>=0.6 (from tangent)
      Downloading astor-0.6.2-py2.py3-none-any.whl
    Collecting autograd>=1.2 (from tangent)
      Downloading autograd-1.2.tar.gz
    Collecting enum34 (from tangent)
      Downloading enum34-1.1.6-py3-none-any.whl
    Collecting future (from tangent)
      Downloading future-0.16.0.tar.gz (824kB)
        100% |████████████████████████████████| 829kB 577kB/s ta 0:00:01    57% |██████████████████▎             | 471kB 1.7MB/s eta 0:00:01
    Collecting gast (from tangent)
      Downloading gast-0.2.0.tar.gz
    Requirement already satisfied: nose in /home/kang/anaconda3/lib/python3.6/site-packages (from tangent)
    Requirement already satisfied: numpy in /home/kang/anaconda3/lib/python3.6/site-packages (from tangent)
    Requirement already satisfied: six in /home/kang/anaconda3/lib/python3.6/site-packages (from tangent)
    Collecting tf-nightly>=1.5.0.dev20171026 (from tangent)
      Downloading tf_nightly-1.6.0.dev20180126-cp36-cp36m-manylinux1_x86_64.whl (44.6MB)
        100% |████████████████████████████████| 44.6MB 14kB/s eta 0:00:017  10% |███▍                            | 4.8MB 3.5MB/s eta 0:00:12    11% |███▌                            | 4.9MB 9.1MB/s eta 0:00:05    15% |████▉                           | 6.8MB 2.6MB/s eta 0:00:15    29% |█████████▎                      | 13.0MB 3.0MB/s eta 0:00:11    43% |██████████████                  | 19.4MB 3.4MB/s eta 0:00:08    57% |██████████████████▌             | 25.7MB 3.5MB/s eta 0:00:06    63% |████████████████████▍           | 28.3MB 2.5MB/s eta 0:00:07    80% |█████████████████████████▉      | 36.0MB 2.8MB/s eta 0:00:04    99% |███████████████████████████████▊| 44.2MB 466kB/s eta 0:00:01
    Collecting protobuf>=3.4.0 (from tf-nightly>=1.5.0.dev20171026->tangent)
      Downloading protobuf-3.5.1-cp36-cp36m-manylinux1_x86_64.whl (6.4MB)
        100% |████████████████████████████████| 6.4MB 100kB/s ta 0:00:011    44% |██████████████▎                 | 2.9MB 219kB/s eta 0:00:17
    Requirement already satisfied: wheel>=0.26 in /home/kang/anaconda3/lib/python3.6/site-packages (from tf-nightly>=1.5.0.dev20171026->tangent)
    Collecting absl-py>=0.1.6 (from tf-nightly>=1.5.0.dev20171026->tangent)
      Downloading absl-py-0.1.9.tar.gz (79kB)
        100% |████████████████████████████████| 81kB 2.2MB/s ta 0:00:01
    Collecting tb-nightly<1.6.0a0,>=1.5.0a0 (from tf-nightly>=1.5.0.dev20171026->tangent)
      Downloading tb_nightly-1.5.0a20180126-py3-none-any.whl (3.0MB)
        100% |████████████████████████████████| 3.0MB 211kB/s ta 0:00:011
    Collecting termcolor>=1.1.0 (from tf-nightly>=1.5.0.dev20171026->tangent)
      Downloading termcolor-1.1.0.tar.gz
    Requirement already satisfied: setuptools in /home/kang/anaconda3/lib/python3.6/site-packages (from protobuf>=3.4.0->tf-nightly>=1.5.0.dev20171026->tangent)
    Collecting bleach==1.5.0 (from tb-nightly<1.6.0a0,>=1.5.0a0->tf-nightly>=1.5.0.dev20171026->tangent)
      Downloading bleach-1.5.0-py2.py3-none-any.whl
    Collecting html5lib==0.9999999 (from tb-nightly<1.6.0a0,>=1.5.0a0->tf-nightly>=1.5.0.dev20171026->tangent)
      Downloading html5lib-0.9999999.tar.gz (889kB)
        100% |████████████████████████████████| 890kB 585kB/s ta 0:00:01
    Collecting markdown>=2.6.8 (from tb-nightly<1.6.0a0,>=1.5.0a0->tf-nightly>=1.5.0.dev20171026->tangent)
      Downloading Markdown-2.6.11-py2.py3-none-any.whl (78kB)
        100% |████████████████████████████████| 81kB 2.5MB/s ta 0:00:01
    Requirement already satisfied: werkzeug>=0.11.10 in /home/kang/anaconda3/lib/python3.6/site-packages (from tb-nightly<1.6.0a0,>=1.5.0a0->tf-nightly>=1.5.0.dev20171026->tangent)
    Building wheels for collected packages: tangent, autograd, future, gast, absl-py, termcolor, html5lib
      Running setup.py bdist_wheel for tangent ... done
      Stored in directory: /home/kang/.cache/pip/wheels/f0/ae/c2/5b094a427f1a1b31c32b94df26678b3efcfe7fedddd2ab4181
      Running setup.py bdist_wheel for autograd ... done
      Stored in directory: /home/kang/.cache/pip/wheels/ea/d4/2d/e7d74fb5d727853026c54f23fa96f0b8defefc7d57dcaca0aa
      Running setup.py bdist_wheel for future ... done
      Stored in directory: /home/kang/.cache/pip/wheels/c2/50/7c/0d83b4baac4f63ff7a765bd16390d2ab43c93587fac9d6017a
      Running setup.py bdist_wheel for gast ... done
      Stored in directory: /home/kang/.cache/pip/wheels/8e/fa/d6/77dd17d18ea23fd7b860e02623d27c1be451521af40dd4a13e
      Running setup.py bdist_wheel for absl-py ... done
      Stored in directory: /home/kang/.cache/pip/wheels/04/f5/7c/5d4eab10ddf87dec875016e74ba289d87270a90fb2662a76fc
      Running setup.py bdist_wheel for termcolor ... done
      Stored in directory: /home/kang/.cache/pip/wheels/de/f7/bf/1bcac7bf30549e6a4957382e2ecab04c88e513117207067b03
      Running setup.py bdist_wheel for html5lib ... done
      Stored in directory: /home/kang/.cache/pip/wheels/6f/85/6c/56b8e1292c6214c4eb73b9dda50f53e8e977bf65989373c962
    Successfully built tangent autograd future gast absl-py termcolor html5lib
    Installing collected packages: astor, future, autograd, enum34, gast, protobuf, absl-py, html5lib, bleach, markdown, tb-nightly, termcolor, tf-nightly, tangent
      Found existing installation: html5lib 0.999999999
        Uninstalling html5lib-0.999999999:
          Successfully uninstalled html5lib-0.999999999
      Found existing installation: bleach 2.0.0
        Uninstalling bleach-2.0.0:
          Successfully uninstalled bleach-2.0.0
    Successfully installed absl-py-0.1.9 astor-0.6.2 autograd-1.2 bleach-1.5.0 enum34-1.1.6 future-0.16.0 gast-0.2.0 html5lib-0.9999999 markdown-2.6.11 protobuf-3.5.1 tangent-0.1.9 tb-nightly-1.5.0a20180126 termcolor-1.1.0 tf-nightly-1.6.0.dev20180126

```python
import tangent

def f(x) :
    return x ** 3 - 3 * x ** 2 + x

import numpy as np
x = np.linspace(-1, 3, 10)

f(x)

fprime0 = tangent.grad(f)

fprime0(1)

fprime = np.vectorize(fprime0)

fprime(x)
```

$$  \int 3x^2 dx $$

$$ \int (3x^2 - 6x + 1)dx $$

$$ \int \left( 2 + 6x + 4\exp(x) + \dfrac{5}{x} \right) dx $$

$$ \int \frac{2x}{x^2 - 1} dx $$

$$\int \left( 1 + xy \right) dx  = x + \frac{1}{2}x^2 y  + C(y)$$
$$\int \$$

```python
import numpy as np
import scipy as sp
import sklearn as sk

import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pylab as plt
from mpl_toolkits.mplot3d import Axes3D

%matplotlib inline
```

    /home/kang/anaconda3/lib/python3.6/site-packages/ipykernel_launcher.py:6: UserWarning:
    This call to matplotlib.use() has no effect because the backend has already
    been chosen; matplotlib.use() must be called *before* pylab, matplotlib.pyplot,
    or matplotlib.backends is imported for the first time.

    The backend was *originally* set to 'module://ipykernel.pylab.backend_inline' by the following code:
      File "/home/kang/anaconda3/lib/python3.6/runpy.py", line 193, in _run_module_as_main
        "__main__", mod_spec)
      File "/home/kang/anaconda3/lib/python3.6/runpy.py", line 85, in _run_code
        exec(code, run_globals)
      File "/home/kang/anaconda3/lib/python3.6/site-packages/ipykernel_launcher.py", line 16, in <module>
        app.launch_new_instance()
      File "/home/kang/anaconda3/lib/python3.6/site-packages/traitlets/config/application.py", line 658, in launch_instance
        app.start()
      File "/home/kang/anaconda3/lib/python3.6/site-packages/ipykernel/kernelapp.py", line 477, in start
        ioloop.IOLoop.instance().start()
      File "/home/kang/anaconda3/lib/python3.6/site-packages/zmq/eventloop/ioloop.py", line 177, in start
        super(ZMQIOLoop, self).start()
      File "/home/kang/anaconda3/lib/python3.6/site-packages/tornado/ioloop.py", line 888, in start
        handler_func(fd_obj, events)
      File "/home/kang/anaconda3/lib/python3.6/site-packages/tornado/stack_context.py", line 277, in null_wrapper
        return fn(*args, **kwargs)
      File "/home/kang/anaconda3/lib/python3.6/site-packages/zmq/eventloop/zmqstream.py", line 440, in _handle_events
        self._handle_recv()
      File "/home/kang/anaconda3/lib/python3.6/site-packages/zmq/eventloop/zmqstream.py", line 472, in _handle_recv
        self._run_callback(callback, msg)
      File "/home/kang/anaconda3/lib/python3.6/site-packages/zmq/eventloop/zmqstream.py", line 414, in _run_callback
        callback(*args, **kwargs)
      File "/home/kang/anaconda3/lib/python3.6/site-packages/tornado/stack_context.py", line 277, in null_wrapper
        return fn(*args, **kwargs)
      File "/home/kang/anaconda3/lib/python3.6/site-packages/ipykernel/kernelbase.py", line 283, in dispatcher
        return self.dispatch_shell(stream, msg)
      File "/home/kang/anaconda3/lib/python3.6/site-packages/ipykernel/kernelbase.py", line 235, in dispatch_shell
        handler(stream, idents, msg)
      File "/home/kang/anaconda3/lib/python3.6/site-packages/ipykernel/kernelbase.py", line 399, in execute_request
        user_expressions, allow_stdin)
      File "/home/kang/anaconda3/lib/python3.6/site-packages/ipykernel/ipkernel.py", line 196, in do_execute
        res = shell.run_cell(code, store_history=store_history, silent=silent)
      File "/home/kang/anaconda3/lib/python3.6/site-packages/ipykernel/zmqshell.py", line 533, in run_cell
        return super(ZMQInteractiveShell, self).run_cell(*args, **kwargs)
      File "/home/kang/anaconda3/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 2698, in run_cell
        interactivity=interactivity, compiler=compiler, result=result)
      File "/home/kang/anaconda3/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 2808, in run_ast_nodes
        if self.run_code(code, result):
      File "/home/kang/anaconda3/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 2862, in run_code
        exec(code_obj, self.user_global_ns, self.user_ns)
      File "<ipython-input-75-5fa60e113fc7>", line 10, in <module>
        get_ipython().magic('matplotlib inline')
      File "/home/kang/anaconda3/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 2146, in magic
        return self.run_line_magic(magic_name, magic_arg_s)
      File "/home/kang/anaconda3/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 2067, in run_line_magic
        result = fn(*args,**kwargs)
      File "<decorator-gen-107>", line 2, in matplotlib
      File "/home/kang/anaconda3/lib/python3.6/site-packages/IPython/core/magic.py", line 187, in <lambda>
        call = lambda f, *a, **k: f(*a, **k)
      File "/home/kang/anaconda3/lib/python3.6/site-packages/IPython/core/magics/pylab.py", line 99, in matplotlib
        gui, backend = self.shell.enable_matplotlib(args.gui)
      File "/home/kang/anaconda3/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 2930, in enable_matplotlib
        pt.activate_matplotlib(backend)
      File "/home/kang/anaconda3/lib/python3.6/site-packages/IPython/core/pylabtools.py", line 307, in activate_matplotlib
        matplotlib.pyplot.switch_backend(backend)
      File "/home/kang/anaconda3/lib/python3.6/site-packages/matplotlib/pyplot.py", line 229, in switch_backend
        matplotlib.use(newbackend, warn=False, force=True)
      File "/home/kang/anaconda3/lib/python3.6/site-packages/matplotlib/__init__.py", line 1305, in use
        reload(sys.modules['matplotlib.backends'])
      File "/home/kang/anaconda3/lib/python3.6/importlib/__init__.py", line 166, in reload
        _bootstrap._exec(spec, module)
      File "/home/kang/anaconda3/lib/python3.6/site-packages/matplotlib/backends/__init__.py", line 14, in <module>
        line for line in traceback.format_stack()

```python
import sympy
sympy.init_printing(use_latex = 'mathjax')

def f(x) :
    return 3 * x**2 - 6 * x + 1

def f2(x) :
    return 2 + 6 * x + 4 * sympy.exp(x) + (5/x)
```

```python
sp.integrate.quad(f, 0, 1)
```

$$\left ( -1.0, \quad 1.3085085171449517e-14\right )$$

```python
sp.integrate.quad(f2, 1, 10)
```

$$\left ( 88421.50297737827, \quad 1.5276890734473408e-06\right )$$

```python
def f3(x, y) :
    return 1 + x* y
```

```python
sp.integrate.quad(f3, -1, 1)
```

```python
import sympy
x, y = sympy.symbols('x y')
f = 1 + x * y
f

```

$$x y + 1$$

```python
#부정 적분
F = sympy.integrate(f, x)
F
```

$$\frac{x^{2} y}{2} + x$$

```python
F2 = sympy.integrate(F, y)
F2
```

$$\frac{x^{2} y^{2}}{4} + x y$$

```python
#성환님 답
x, y = sympy.symbols('x y')
f = 1+x*y
F = sympy.integrate(f, x)
F1 = sympy.integrate(F, y)
(F1.subs(x, 1) - F1.subs(x, -1)).evalf()

```

$$2.0 y$$

```python

```


# Assignment 4

### Math 502 - Lamoureux

### Due April 4, 2019

## Exercise 0

Fix a constant $\lambda >0$. Find a solution to the delay differential equation
$$y'(x) = -\lambda y(x-1), \qquad y(0) = 1,$$
where we are looking for a function $y=y(x)$ that satisfies this equation.

Does your solution decay over time? At what rate? Do you think the decay rate is somehow related to the delay in the differential equation? (Here the delay is 1, since we have a $x-1$ in the DE.) Test your idea by solving a related equation $y'(x) = -\lambda y(x-x_0)$.

## Solution

I presented this exercise because we saw a delay differential equation (DDE) in the pantograph problem. Howison was very quick to solve it, without discussing many interesting aspects of DDEs.

For full marks, I was happy to see people look for decaying solutions explicitly, such as
$$y(x) = e^{-cx}.$$
The DDE above results in the formula
$$-c e^{-cx} = -\lambda e^{-c(x-1)} = -\lambda e^c e^{-cx}.$$
Canceling the $e^{-cx}$ we are left with a simple equation to solve,
$$ \frac{c}{e^c} = \lambda.$$

We can plot the curve on the left to look for positive values for $\lambda$ that we can solve for.

```python
from numpy import *
from matplotlib.pyplot import *
c = linspace(-.1,5)
plot(c,c/exp(c), c,.1+0*c);
```

This function has a maximum at $c = 1$ (check with calculus) and max value of $1/e \approx 0.35$. For any $\lambda $ between 0 and $1/e$, there will be two positive values of $c$ where the equation
$$ \frac{c}{e^c} = \lambda$$
is satisfied. So our general solution is the linear combination
$$y(x) = A e^{-c_1 x} + B e^{-c_2 x},$$
where $A,B$ are arbitrary constants.

Of course we need A+B=1 to satisfy the initial condition $y(0) = 1$.

These decay constants $c_1,c_2$ will change is you replace the delay parameter in $$y'(x) = -\lambda y(x-x_0).$$
You should try this out yourself, now that you see how this is done. Note which directions the delay constants move.

The numbers $c_1,c_2$ are the decay constants. From the graph above, looking at the intersection points, you should notice one is close to zero, and the other is big. As $\lambda$ gets close to zero, the smaller $c_i$ goes to zero, and the other goes to infinity. So that is interesting, the decay constants have different behaviour.

(Most students did not notice the two solutions, they just picked one.)

In the case $\lambda = 1/e$, the critical value, there is a double root at $c = 1$, so the general (exponential form) solution is
$$y(x) = e^{-x} + xe^{-x}.$$

(Nobody saw this.)

A few people noticed that oscillatory solutions are possible. Yes, this is true. I gave full marks for their work numerically, or qualitatively.

If you want more details on oscillations, you could try to solve the equation
$$ \frac{c}{e^c} = \lambda$$
using complex values of $c$. Or you can try a direct solution of the form
$$y(x) = e^{-ax} \cos(bx).$$
In this case, the DDE $y'(x) = -\lambda y(x-1)$ gives us
$$e^{-ax}(-a\cos(bx) - b \sin(bx) ) = -\lambda e^{-a(x-1)} \cos(b(x-1)).$$
Cancelling the exponential and expanding the RHS using trig identities, we get
$$a\cos(bx) +  b \sin(bx) = \lambda e^a \{\cos(bx)\cos(b) + \sin(bx)\sin(b) \}.$$
Equating the coefficients of the $\cos(bx), \sin(bx)$ functions, we are left with two equations:
$$ a = \lambda e^a \cos(b), \qquad b = \lambda e^a \sin(b).$$
This systems can be solved numerically. The interesting point is that there are infinitely many solutions. (Given $\lambda>0$ you can find infinitely many pairs $a,b$ that solve these two equations.) To see this, divide one equation by the other to get
$$\frac{a}{b} = \frac{\cos(b)}{\sin(b)}.$$
In other words, $a = b\cot(b).$ Plugging into the first equation, we see that
$$\lambda = \frac{a}{e^a \cos(b)} = \frac{b \cot(b)}{e^{b\cot(b)}\cos(b)},$$
which simplifies to
$$\lambda  = \frac{b}{e^{b\cot(b)}\sin(b)}.$$
So as before, we have $\lambda$ expressed in the form of one paramater $b$, and we see from the plot below that there are many solutions for $b$ given any $\lambda >0$.

So yes, lots and lots of oscillatory solutions.

```python
b = linspace(-20,20,1000)
plot(b,b/(sin(b)*exp(b/tan(b))), b,2.5+0*b);
ylim([-10,10]);
```

Anyhow, the interesting point it, there are many, many solutions. Turns out the DDE problem is infinite dimensional, and very challenging. If you would like to learn more, here is an interesting reference.

https://www.scribd.com/document/334488045/Delay

## Exercise 1

A sequence $a= (\ldots, a_{-2},a_{-1}, a_0, a_1, a_2, \ldots)$ is called **anti-symmetric** if $$a_{-n} = -a_n \mbox{ for all $n$. }$$
For instance, the sequence $a= (\ldots, 0,0, 1,-3,5,0,-5,3,-1,0,0,\ldots)$ is anti-symmetric, when we label the middle 0 as $a_0.$

### Part 1
- Show that the Fourier transform $\hat{a}(\theta)$ of an anti-symmetric sequence is purely imaginary (its real part is zero.)

### Solution, part 1

Note $a_{-0} = -a_0$ tells us that $2a_0 = 0$ so the zero-th coefficient is alway zero in an antisymmtrixc sequence. (Several students thought they needed to add this as an assumption.)

The proof that the FT is purely imaginary is done by adding terms in pairs. Recall
$$\hat{a}(\theta) = \sum_{n=-\infty}^\infty a_n e^{2\pi i n \theta}.$$
Add the negative and postive n-indices in pairs, $a_n, a_{-n}$, so
$$\hat{a}(\theta) = \sum_{n= 1}^\infty ( a_n e^{2\pi i n \theta} + a_{-n} e^{-2\pi i n \theta}).$$

Simplify, using the fact that $a_{-n} = -a_n,$
$$\hat{a}(\theta) = \sum_{n= 1}^\infty a_n(  e^{2\pi i n \theta} -  e^{-2\pi i n \theta}).$$
The difference of those two exponetials is a sine, times i. So
$$\hat{a}(\theta) = \sum_{n= 1}^\infty a_n(  2i\sin(2\pi n \theta) )
= 2i \sum_{n= 1}^\infty a_n\sin(2\pi n \theta),$$
which is pure imaginary.

### Part 2

- Show that $\hat{a}(1-\theta) = -\hat{a}(\theta)$ for all $\theta$ if $a$ is anti-symmetric

### Solution part 2

We use the fact that $\sin$ is 2pi periodic, and odd. So $$\sin(2\pi n - 2\pi n\theta) = \sin(- 2\pi n\theta) = - \sin(2\pi n\theta).$$
Using the formula in the last part for FT as a sum of sines, we have
$$\hat{a}(1-\theta) = 2i \sum a_n \sin(2\pi n - 2\pi n\theta) = - 2i \sum a_n \sin(2\pi n\theta) = - \hat{a}(\theta),$$
since we can take the minus sign outside the sum.

### Part 3
- Show that the convolution $a*a$ is symmetric whenever $a$ is anti-symmetric.

### Part 3, solution

I liked the solution one student gave in words. If you flip both sequences $a$, the result of the convolution will be the flip of the regular result. But flipping $a$ introduces a minus sign. The two minus signs cancel, so the flip of the result equals the original result, hence it is symmetric.

Or, if you prefer algebra, define $$c_n = \sum_k a_{n-k}a_k$$ as the usual convolution. Then we have by definition
$$c_{-n} = \sum_k a_{-n-k}a_k,$$
where I just replaced $n$ with $-n$ under the summation. Now $a_{-n-k} = -a{n+k}$ since a is antisymmetric, so I can replace that under the previous summation to get
$$c_{-n} = -\sum_k a_{n+k}a_k.$$
Now, the summation index goes from $-N$ to $+N$ so it doesn't hurt to sum in reverse order, so I will replace the summation index $k$ with $-k$ to get
$$c_{-n} = -\sum_k a_{n-k}a_{-k}.$$
Now use the fact that $a_{-k} = -a_k$ to change the last factor in the summation to get
$$c_{-n} = -\sum_k a_{n-k}(-a_{k}) = + \sum_k a_{n-k}a_{k} = c_n,
$$
where we recognize this last sum as our original convolution.

So $c$ is symmetric.

By the way, the same proof works for $c=a*b$, where a and b are both antisymmetric.

What do you think happens if $a$ is symmetric and $b$ is antisymmetric? Is $c=a*b$ symmetric, antisymmetric, or possibly neither?

Hint: If you are finding this hard for general sequences $a$, try to solve it for that one example $$a= (\ldots, 0,0, 1,-3,5,0,-5,3,-1,0,0,\ldots).$$

## Exercise 2

Here's a little trick to keep convolution from making our sequences "too long."

Recall if you convolve two sequence $a,b$ of length N, the result is of length 2N-1.

To "fix" this, we can define **circular convolution**  as $c = a\otimes b$, using the formula
$$c_k = \sum_{j=0}^{N-1} a_{k-j}b_j, \mbox{ with $k-j$ computed modulo $N$.}$$

So, for instance, the convolution of length-3 sequences is given as
$$(a_0,a_1,a_2)\otimes (b_0,b_1,b_2) =
(a_0b_0 + a_1b_2 + a_2b_1, a_0b_1 + a_1b_0 + a_2b_2, a_0b_2 + a_1b_1 + a_2 b_0).$$

(Notice each component on the RHS has 3 terms, and the indices in each component add up to the same number, mod 3.)

1. Compute the circular convolution: $(1,1,0,0)\otimes(1,2,3,4)$. The result should have length 4.
2. Compute the regular convolution: $(1,1,0,0) * (1,2,3,4)$. The result should have length 7.
3. Compare your two answers. What parts are the same, what are different?
4. Compute the DFT of the three sequences $a = (1,1,0,0)$ , $b = (1,2,3,4)$, $c = (1,1,0,0)\otimes(1,2,3,4)$. Either by hand, or using Python. (Each result will be a length-4 sequence, with complex values.)
5. Verify your results in \#4, by showing that the DFT of c is equal to the product of the DFT of a times the DFT of b.

DFT = Discrete Fourier transform. It takes vectors of length N and produces vectors of length N, using that formula we saw in class.

### Solution.

This was supported to be super easy.

1. The circular convolution is $(5,3,5,7).$
2. The regular convolution is $(1,3,5,7,4,0,0).$
3. You should notice the 3,5,7, are the same, and the first number 5 in the circular is the sum of the 1,4 in the regular.
4. You can do the DFT using Python, or just note that the 4x4 matrix for the DFT in this case is the matrix
$$\left[
\begin{array}{rrrr}
1 & 1 & 1 & 1\\
1 & i & -1 & -i \\
1 & -1 & 1 & -1 \\
1 & -i & -1 & i
\end{array}
\right].
$$
So just apply this matrix to the (column) vectors to get
$$DFT(1,1,0,0) = (2,1+i,0,1-i).$$
$$DFT(1,2,3,4) = (10,-2-2i,-2,-2+2i).$$
$$DFT(5,3,5,7) = (20,-4i,0,4i).$$
Then it is easy to check with complex multiplication that
$$(2,1+i,0,1-i)\times (10,-2-2i,-2,-2+2i)= (20,-4i,0,4i).$$

## Exercise 3

In the notes to lecture 18, we took the 2D Fourier transform of the identity matrix. As an image, this took a line pointing at 45 degrees down, and transformed it into a line at 45 degrees up.

Write some code to explore this idea further. Take a big array of zeros, and then fill in a line at some angle. Take the 2D FFT and display it -- it should look like a line, going at a different direction.

Once you get the lines working, see if you can describe the relation between the angle (or slope) of the input and output lines.

Can you prove this relationship between the angles?

## Solution

I've tempted to use a widget here. Why not?

```python
from ipywidgets import interact
```

```python
def display1(slope=0):
    image0 = zeros((256,256))
    for i in range(-128,128):
        image0[128+round(-slope*i),128+i] = 1
#    image00 =
    subplot(1,2,1)
    imshow(image0);
    subplot(1,2,2)
    imshow(abs(fftshift(fft2(image0))));

interact(display1,slope=(-.99,.99,.01));
```

    interactive(children=(FloatSlider(value=0.0, description='slope', max=0.99, min=-0.99, step=0.01), Output()), …

To prove this, you could go with some intuition.

When the line is horizontal, the sum
$$\hat{a}(\theta,\phi) = \sum_{j,k} a_{jk} e^{2\pi i j\theta + k\phi}$$
will only be non-zero for indicies like $a_{j0}$. So the sum reduces to $$\hat{a}(\theta,\phi) = \sum_{j} a_{j0} e^{2\pi i j\theta},$$
which means the FT is independent of the $\phi$ variable. So it looks the same as you go "up and down." In fact, the FT is s straight vertical line, which you can see when all the $a_{j0} = 1$ since in the discrete case, this complex exponentials sum up to zero (cancellation) , except at $\theta = 0$. So that's a vertical line.

What happens if you rotate the line? Well, as we saw with the widget above, the Fourier transform also rotates. The point is, it rotates in the same direction, so it keeps the perpendicular character of the line and its FT.

To see this, think of the matrix $a_{jk}$ as samples from a 2D function $f(x,y)$ that represents an image. So we might have
$$a_{jk} = f(j,k).$$
We apply a rotation matrix
$$R =
\left[
\begin{array}{cc}
\cos(r) & \sin(r) \\
-\sin(r) & \cos(r)
\end{array}
\right],$$
to get the rotated image
$$b_{jk} = f(R(j,k)'),$$
where here I mean the matrix $R$ is applied to the column vector $(j,k)$.

Fourier transform of $b$ is given by
$$\hat{b}(\theta,\phi) = \sum_{j,k} f(R(j,k))e^{2\pi i \langle (j,k),(\theta,\phi) \rangle},$$
where this funny thing in the exponent, $\langle (j,k),(\theta,\phi) \rangle$ is just the usual inner product in 2D.

Now, lets change summation variables, to indices $(m,n) = R(j,k)$. This changes the the exponent term to have indices $(j,k)$ replaced with $(j,k) = R^{-1}(m,n)$, where here $R^{-1}$ is the inverse rotation matrix. So now the Fourier transform of $b$ becomes
$$\hat{b}(\theta,\phi) = \sum_{m,n} f(m,n)e^{2\pi i \langle R^{-1}(m,n),(\theta,\phi) \rangle}.$$
But now we pull the rotation matrix to the other side of the inner product, getting its transpose, which cancels the inverse, so
$$\hat{b}(\theta,\phi) = \sum_{m,n} f(m,n)e^{2\pi i \langle (m,n),R(\theta,\phi) \rangle}.$$

Stare at the sum for a while and you will recognize it is just FT of array a, evaluated at the point $R(\theta,\phi).$

That is,
$$\hat{b}(\theta,\phi) = \hat{a}(R(\theta,\phi)).$$

So, the point is, we rotate the image, and its Fourier transform rotates the same way.

So when we rotate a line, its FT (which is also a line, perpendicular to the first), also rotates, keeping the same perpendicular relation.

### Student solution.

A couple of studnet mentioned a method using the FT on the 2D plane. I copy it here for your reference.

Notice that a line $y = mx$ can be described by the equation $y-mx = 0$, then a nice way to approach the problem is to consider the Fourier transform of $\delta(y-mx)$. While the $\delta$ function is infact infinite when $y = mx$ instead of $1$, it imparts the geometric intuition we might expect.

Then, taking the continuous two-dimensional Fourier transform of $\delta(y-mx)$ we have
\begin{align}
\hat{f}(u,v) &= \int_{-\infty}^\infty \int_{-\infty}^\infty \delta(y-mx) e^{-2\pi i ux}e^{-2\pi i vy} dydx\\
&= \int_{-\infty}^\infty e^{-2\pi i ux} \int_{-\infty}^\infty \delta(y-mx) e^{2\pi i vy}dydx\\
&= \int_{-\infty}^\infty e^{-2\pi i ux} \int_{-\infty}^\infty \delta(y-mx) \frac{e^{2\pi i vmx}}{e^{2\pi i vmx}}e^{2\pi i vy}dydx\\
&= \int_{-\infty}^\infty e^{-2\pi i ux - 2\pi i vmx} \int_{-\infty}^\infty \delta(y-mx) e^{-2\pi i v(y-mx)}dydx\\
&= \int_{-\infty}^\infty e^{-2\pi i x(u+vm)}dx\\
&= \delta(u+vm)
\end{align}
it follows that $\hat{f}(x,y) = \delta(x + my)$, which is nonzero if and only if $y = -(1/m)x$.

## Exercise 4

This is an exploration exercise.

- Take a geometric figure, place into a NxN array to create an image.
- Its a good idea to take N to be a power of 2, like 128 or 256.
- Take the 2D FFT of the image, and save the absolute values as a NxN array.
- Plot the original image and absolute value of the FFT of the image.
- You might also try fftshift on the image (see Lecture 18 again)
- Can you see any connection between the geometric figure and its FFT?

For instance, we saw in Exercise 3 that the FFT of a line was a line.

What is the FFT of a square? A circle? A triangle? A polygon?

Try the outline of a circle, and a solid (filled in) circle. Also for the other shapes.

Try creating a pattern in the image.
- Parallel lines
- Grid lines in x and y direction
- Rectangular array of dots
- Hexagonal array of dots.
What does the FFT of these things look like?

**Note:** There is no right answer here, I just want you to explore some shapes and patterns.

**Historical note:** In the early 1900s when scientists were trying to discover the atomic structure of crystals, they could essentially "see" the FFT of the arrangement of atoms in the crystal, but not see the arrangement itself. So it was an important challenge to be able to guess the arrangement only by looking at the FFT of the image. (Actually, they could only look at the absolute value of the FFT.)

The structure of DNA, a double helix, was discovered by Watson and Crick in 1953. Their work depended heavily upon  the X-ray images produced by Rosalind Franklin, which is essentially the FFT images as above. Watson and Crick received the Nobel prize in 1962; Franklin died of cancer in 1958.

## Solutions

The idea is to play around here.

The only real challenge is to figure out how to plot things in Python, that are interesting shapes. You can write your own code, or use a package like CV2.

I'll go old school and just draw using little loops.

The idea is to set up an array as an image, and put in ones according to some pattern.

Then we use fft2 and fftshift to make the fourier transform image.

```python
from numpy import *
from matplotlib.pyplot import *
from numpy.fft import fft2, fftshift
from matplotlib import cm

```

```python
## A line
image1 = zeros((256,256))
for i in range(256):
    image1[i,i] = 1
imshow(image1);
```

```python
imshow(abs(fftshift(fft2(image1))));
```

```python
## A line with a different slope

slope = 2
image2 = zeros((256,256))
for i in range(256):
    image2[i,round(i/slope)] = 1
imshow(image2);
```

```python
imshow(abs(fftshift(fft2(image2))));
```

```python
## A circle
image3 = zeros((256,256))
for i in range(256):
    for j in range(256):
        if (i-128)**2 + (j-128)**2 < 10**2:
            image3[i,j] = 1
imshow(image3);
```

```python
imshow(abs(fftshift(fft2(image3))));
```

```python
## A bigger circle
image4 = zeros((256,256))
for i in range(256):
    for j in range(256):
        if (i-128)**2 + (j-128)**2 < 30**2:
            image4[i,j] = 1
imshow(image4);
```

```python
imshow(abs(fftshift(fft2(image4))));
```

Notice how the bigger circle has a smaller FFT

```python
## A bulls eye
image5 = zeros((256,256))
for i in range(256):
    for j in range(256):
        r = sqrt((i-128)**2 + (j-128)**2)
        if abs(sin(r/10))<.5:
            image5[i,j] = 1
imshow(image5);
```

```python
## Let's use a log to see more detail
imshow(log(abs(fftshift(fft2(image5)))));
```

```python
## Let's do a grid

image6 = zeros((256,256))
for i in range(256):
    for j in range(256):
        if (mod(i,64)==0) or (mod(j,32)==0):
            image6[i,j] = 1
imshow(image6);
```

```python
imshow(abs(fftshift(fft2(image6))),cmap='jet');
```

```python
## Let's do a grid of points

image7 = zeros((256,256))
for i in range(256):
    for j in range(256):
        if (mod(i,64)==0) and (mod(j,32)==0):
            image7[i,j] = 1
imshow(image7);
```

```python
imshow(abs(fftshift(fft2(image7))),cmap='gray');
```

Notice a grid of lines maps to a pattern of points, and vice versas.

If the grid of lines is tightly spaced in the horizontal direction, then the resulting points are tightly spaced in the vertical direction.


# Pairs in a trace

Progress done between 2021-04-17 and 2021-04-22 by jgil@eso.org

## Some definitions

An *alphabet* $\Sigma$ is any finite set, and its elements are called *symbols*. A *trace* $T$ is any concatenation of symbols in $\Sigma$, written as $T \in \Sigma^{*}$. The symbols in $\Sigma$ that are also in a trace $T$ are written as $\Sigma(T)$

## Notion of a pair

In any trace we can precisely determine which of them appears in pairs by rewritting the trace as repetitions. For example in $T=ababab$, $T$ can be written as a repetition of the subtrace $ab$: $T= (ab)(ab)(ab) = 3 \times (ab)$. Note that if any other symbols exists in the trace, the pairing condition of $ab$ remains, for example $(ab)$ is paired in both $T_1=ababab$ and $T_2=abXabYab$.

Let be the set $\Sigma$ an alphabet, $T \in \Sigma^{*}$ a trace, and $a,b \in \Sigma; a \neq b$ a disjoint ordered pair. We say that $(a,b)$ is **paired in $T$** if exists $n \ge 0$ such that $T \cap \{a,b\} = n \times (ab)$, i.e. the symbols in $T$ restricted to $a$ and $b$ can be written as a repetition of the trace $(ab)$, possibly with $n=0$.

## Pair-cardinality

The pair-cardinality captures the repetitions of $ab$ in $T$.

**Definition**: Given $T \in \Sigma^{*}$ and $a,b \in \Sigma$ we define the function $n_T(ab)$ as:

$$
\begin{align}
n_T: & \Sigma^2 \rightarrow \mathbb{N} \cup \{- \infty \} \\
n_T(ab) & = \left\{
  \begin{array}{rcl} n & \text{ if } a \neq b \text{ and } T \cap \{a,b\} = n \times (ab)   \\
  -\infty & \text{ otherwise } \end{array}\right.
\end{align}
$$

Now we can define more precisely the notion of pairs.

**Definition**: A pair $(a,b)$ is **paired in T** if $n_T(ab) \ge 0$, and $(a,b)$ is **unpaired in T** if $n_T(ab) \lt 0$.

**Definition**: The set of all pairs in $T$ is defined as $\mathcal{P}_T = \{ (ab) | (a,b) \text{ is paired in } T \}$

### Examples

```python
def pair_cardinality(x, y, T):
    '''
    Returns the cardinality if (x,y) is pair in T, otherwise it returns -1
    '''
    if x==y:
        return -1
    intersection=[a for a in T if a==x or a==y ]
    cardinality=int(len(intersection) / 2)
    return cardinality if [x,y]*cardinality==intersection else -1
```

Let be $\Sigma = \{a,b,x,y,m,n\}$ and $T=axbabyabyx$.

```python
T='axbabyabyx'
```

$(x,y)$ is unpaired in T because $T \cap \{x,y\} = xyyx$ cannot be written as repetitions of $(xy)$. Its cardinality $n_T(xy) = - \infty $

```python
[i for i in T if i in ('x', 'y')]
```

    ['x', 'y', 'y', 'x']

```python
pair_cardinality('x', 'y', T)
```

    -1

The pair $(a,b)$ is paired in $T$ and $n_T(ab)=3$, because $T \cap \{a,b\} = ababab = 3 \times (ab)$.

```python
[i for i in T if i in ('a', 'b')]
```

    ['a', 'b', 'a', 'b', 'a', 'b']

```python
pair_cardinality('a', 'b', T)
```

    3

## Properties of pairs

**Property**: If $(a,b)$ is paired in $T$ then it is easy to prove that $(b,a)$ is unpaired in $T$ because there not exists any $n$ that verifies $T \cap \{b,a\} = n \times (ba)$

```python
pair_cardinality('b', 'a', T)
```

    -1

**Property**: If both $m$, $n$ are not in $\Sigma(T)$, then $T \cap \{m,n\} = 0 \times (mn)$ and its cardinality is equal to 0 which means that there is no evidence that $(m,n)$ be unpaired in T.

```python
pair_cardinality('m', 'n', T)
```

    0

**Property**: If $a \in \Sigma(T); x \in \Sigma \setminus \Sigma(T)$ i.e. the first symbol is in $T$ and the second not in $T$, then both $(a,x)$ and $(x,a)$ are unpaired in $T$.

```python
pair_cardinality('a', 'm', T)
```

    -1

```python

pair_cardinality('m', 'a', T)
```

    -1

## All pairs in T

Strategy: $\forall a,b \in \Sigma(T); a \neq b$ test if $n_T(ab) \ge 0$

```python
T='axbabyabyx'
```

```python
list( set( [x for x in T] ) )
```

    ['a', 'b', 'y', 'x']

```python
import pandas as pd
```

```python
def pairs_in_trace(T):
    Pairs_in_T = {}

    # The alphabet in T
    Sigma_T = list( set( [x for x in T] ) )
    for i in range( len(Sigma_T) -1 ):
        a = Sigma_T[i]
        # By def (a,a) is not paired in T
        Pairs_in_T[ (a,a) ] = -1
        for b in Sigma_T[i+1:]:
            Pairs_in_T[ (a,b) ] = pair_cardinality(a, b, T)
            # asymmetric paired property: if (a,b) is paired in T the (b,a) is not
            if Pairs_in_T[ (a,b) ]>=0:
                Pairs_in_T[ (b,a) ] = -1
            else:
                Pairs_in_T[ (b,a) ] = pair_cardinality(b, a, T)
    return Pairs_in_T
```

```python
# Note that the sequence 1234 can be destroyed if a 4321 is added.
T='123abcabc44321'
```

```python
pairs = pairs_in_trace(T)
P=pd.DataFrame.from_dict( { a[0]+a[1]:b for a,b in pairs.items() }, orient='index', columns=['pairs'] )

# Show only pairs with n_T >= 0
P[ P['pairs'] >= 0 ].T
```

      bc
      ac
      ab

      pairs
      2
      2
      2

```python
P=pairs_in_trace(T)

# See how just (a,b) is pair and the rest has n_T=-1

pd.DataFrame.from_dict( { a[0]+a[1]:b for a,b in P.items() }, orient='index', columns=['pairs'] ).T
```

      33
      32
      23
      31
      13
      34
      43
      3c
      c3
      3b
      ...
      4a
      a4
      cc
      cb
      bc
      ca
      ac
      bb
      ba
      ab

      pairs
      -1
      -1
      -1
      -1
      -1
      -1
      -1
      -1
      -1
      -1
      ...
      -1
      -1
      -1
      -1
      2
      -1
      2
      -1
      -1
      2

1 rows × 48 columns

```python
T='123abcabc4'
pairs = pairs_in_trace(T)
P=pd.DataFrame.from_dict( { a[0]+a[1]:b for a,b in pairs.items() }, orient='index', columns=['pairs'] )

# Show only pairs with n_T >= 0
P[ P['pairs'] >= 0 ].T
```

      23
      13
      34
      12
      24
      14
      bc
      ac
      ab

      pairs
      1
      1
      1
      1
      1
      1
      2
      2
      2

## Sequences

(Write here a discussion about notation, path, simple paths, sequences in literature, and be clear what a "sequence" is for us)

**Definition**: A sequence $S \in \Sigma^*$  is a trace whose symbols are different, $ s_i \neq s_j \forall i \neq j$. Similar to pairs, $S$ is a **sequence in $T$** if exists $n \ge 0$ such that $T \cap \Sigma(S) = n \times S$, i.e., the trace restricted to symbols of $S$ is a n-repetition of $S$.

The cardinality of $S$ in $T$ is defined in the same way than for pairs, and extended to any trace:

**Definition**: Let be $S$ any trace in $\Sigma^*$, then  $n_T(S) = n$ for some suitable $n$ if $S$ is a sequence in $T$, or $- \infty$ if $S$ is not a sequence in T. For completeness we also define $n_T( S=s_1 ) = n_T( \emptyset ) = -\infty$, the cases for one symbol and empty sequence.

**Definition**: The set of all sequences in $T$ is denoted as $\mathcal{S}_T = \{ S | S \text{ is sequence in } T \}$

Note that if $abcd$ is a sequence in $T$, then also $ab$, $abc$, $bcd$, $ad$ and all other subtraces are sequences in $T$.

**Definition**: A sequence $S$ is called a **maximal sequence in $T$** if is not a combination of other sequences in $T$: $\nexists R, S' \in \mathcal{S}_T; S \neq R $ such that $\Sigma(S') = \Sigma(S) \cup \Sigma(R)$.

**Definition**: The set of all maximal sequences in T is denoted as $\overline{ \mathcal{S}_T } = \{ S | S \text{ is maximal sequence in } T \}$

If $S=ab$ we recover the definitions of pairs: $n_T(S) = n_T(ab)$. Also, any sequence in T can be written in terms of its pairs. Therefore, all pairs properties inherits to sequences.

```python
def sequence_cardinality(S, T):
    '''
    Returns the cardinality if S=abc...z is a sequence in T, otherwise it returns -1
    '''
    # Force list
    if type(S)==type(''):
        S = list(S.strip())

    # two or more symbols only
    if len(S) < 2:
        return -1

    # Check all symbols are different
    if len(S) != len(set(S)):
        return -1

    intersection=[a for a in T if a in S ]
    cardinality=int(len(intersection) / len(S)  )
    return cardinality if S*cardinality==intersection else -1
```

```python
T='123abcabc4'
```

```python
sequence_cardinality('abc', T)
```

    2

```python
sequence_cardinality('ab', T)
```

    2

```python
sequence_cardinality('a', T)
```

    -1

```python
sequence_cardinality('ba', T)
```

    -1

```python
# This sequence has cardinality 0
sequence_cardinality('MNO', T)
```

    0

This sequence has $n_T(S) < 0$ because one of its symbols is not in T

```python
sequence_cardinality('abcM', T)
```

    -1

```python
sequence_cardinality('1234', T)
```

    1

```python
sequence_cardinality('abc', T)
```

    2

```python
sequence_cardinality('1234', T)
```

    1

```python
sequence_cardinality('abc', T)
```

    2

## Properties of sequences

If $S = s_1 ... s_m$ , $m \ge  2$ is a sequence in $T$, then the following properties can be verified.

**Property**: $(s_i, s_j)$ are paired in $T$ for $i \lt j$ and unpaired for $i \ge j$.

**Property**: the cardinality is equal for the sequence and its pairs. $n_T(S) = n_T(s_i,  s_j)$ for all $i \lt j$

```python
T='a1b2ca3bc4a5bc'
S='abc'
```

```python
print( "Cardinality of S in T: {}".format( sequence_cardinality(S,T) ) )
print( "Cardinality of ab in T: {}".format( pair_cardinality('a', 'b', T) ) )
print( "Cardinality of bc in T: {}".format( pair_cardinality('b', 'c', T) ) )
print( "Cardinality of ac in T: {}".format( pair_cardinality('a', 'c', T) ) )
print( "Cardinality of ba in T: {} (should be negative)".format( pair_cardinality('b', 'a', T) ) )
```

    Cardinality of S in T: 3
    Cardinality of ab in T: 3
    Cardinality of bc in T: 3
    Cardinality of ac in T: 3
    Cardinality of ba in T: -1 (should be negative)

## Lemma: Consecutive sequence order

When describing a sequence based on the pairs which lies in it, an interesting property emerges. Clearly there are exactly $m$ pairs in $S=s_1 ... s_m$ of the form $(s_i, s_m)$ because all its symbols are different bby definition. And this is applicable to any subtrace of $S$. A stronger claim can be proved, that there are no such pairs ending in $s_m$ in $T$ outside $S$ with the same cardinality.

**Lemma**: $s_j$ is the $j$-esim element of a sequence $S$ in $T$ $\iff$ there are exactly $j-1$ pairs $(x, s_j)$ paired in $T$ where $n_T(S) = n_T(x, s_j)$. All such $x$ lies inside $S$.

(proof easy but pending) **however, please study $aXYbaYXb$ before sing and dance**

Below are some examples of the lemma.

```python
T='a1b2ca3bc4a5bc'
S='abc'

pairs = pairs_in_trace(T)
```

```python
# Show all pairs in T

P=pd.DataFrame.from_dict( { a[0]+a[1]:b for a,b in pairs.items() }, orient='index', columns=['pairs'] )
P[ P['pairs'] >= 0 ].T
```

      23
      35
      13
      34
      25
      12
      24
      15
      45
      14
      bc
      ac
      ab

      pairs
      1
      1
      1
      1
      1
      1
      1
      1
      1
      1
      3
      3
      3

```python
print( "Cardinality of S in T: {}".format( sequence_cardinality(S,T) ) )
```

    Cardinality of S in T: 3

```python
# All pairs ending in 'a' for S=abc
[ (x,sj,n) for (x,sj), n in pairs.items()
 if sj == 'a'
 and sequence_cardinality(S, T) == pair_cardinality(x, sj, T) ]
```

    []

```python
# All pairs ending in 'b' for S=abc
[ (x,sj,n) for (x,sj), n in pairs.items()
 if sj == 'b'
 and sequence_cardinality(S, T) == pair_cardinality(x, sj, T) ]
```

    [('a', 'b', 3)]

```python
# All pairs ending in 'c' for S=abc
[ (x,sj,n) for (x,sj), n in pairs.items()
 if sj == 'c'
 and sequence_cardinality(S, T) == pair_cardinality(x, sj, T) ]
```

    [('b', 'c', 3), ('a', 'c', 3)]

## Complexity of getting all pairs in T

The time seems to be bounded by $O( |T|  2^{\Sigma(T)} )$ , it depends on the size of alphabet instead of the length of the traces.

```python
print('Extract all pairs: fixed length\n-------')
N=500
for i in range(1,6):
    T = list(range(100*i)) * int(N/(10*i) )
    print( "\nlength={}, symbols={}".format(len(T), 100*i))
    %time pairs_in_trace( T  )
```

    Extract all pairs: fixed length
    -------

    length=5000, symbols=100
    CPU times: user 1.32 s, sys: 7.27 ms, total: 1.33 s
    Wall time: 1.33 s

    length=5000, symbols=200
    CPU times: user 5.2 s, sys: 20.7 ms, total: 5.22 s
    Wall time: 5.24 s

    length=4800, symbols=300
    CPU times: user 11 s, sys: 17.8 ms, total: 11 s
    Wall time: 11 s

    length=4800, symbols=400
    CPU times: user 19.1 s, sys: 22.6 ms, total: 19.1 s
    Wall time: 19.1 s

    length=5000, symbols=500
    CPU times: user 30.8 s, sys: 22.2 ms, total: 30.8 s
    Wall time: 30.9 s

```python
print('\nExtract all pairs: fixed symbols\n-------')
S=100
for N in range(1,11):
    T = list(range(S)) * (N*10)
    print( "\nlength={}, symbols={}".format(len(T), S))
    %time pairs_in_trace( T  )
```

    Extract all pairs: fixed symbols
    -------

    length=1000, symbols=100
    CPU times: user 268 ms, sys: 3.57 ms, total: 271 ms
    Wall time: 269 ms

    length=2000, symbols=100
    CPU times: user 503 ms, sys: 1.99 ms, total: 504 ms
    Wall time: 504 ms

    length=3000, symbols=100
    CPU times: user 752 ms, sys: 1.54 ms, total: 754 ms
    Wall time: 753 ms

    length=4000, symbols=100
    CPU times: user 1.02 s, sys: 2.78 ms, total: 1.03 s
    Wall time: 1.03 s

    length=5000, symbols=100
    CPU times: user 1.29 s, sys: 4.45 ms, total: 1.3 s
    Wall time: 1.3 s

    length=6000, symbols=100
    CPU times: user 1.54 s, sys: 4.27 ms, total: 1.54 s
    Wall time: 1.54 s

    length=7000, symbols=100
    CPU times: user 1.87 s, sys: 10.2 ms, total: 1.88 s
    Wall time: 1.89 s

    length=8000, symbols=100
    CPU times: user 2.07 s, sys: 6.73 ms, total: 2.08 s
    Wall time: 2.08 s

    length=9000, symbols=100
    CPU times: user 2.29 s, sys: 5.83 ms, total: 2.29 s
    Wall time: 2.3 s

    length=10000, symbols=100
    CPU times: user 2.51 s, sys: 3.94 ms, total: 2.52 s
    Wall time: 2.52 s

```python

```


```python
import numpy as np
import sympy
import itertools
from sympy import *

init_printing(use_latex="mathjax")
```

Let $d:=\dim (H)$

```python
d = 3
```

Insert a frame $f$

Cube

```python
f_1 = Matrix([[1], [1], [1]])
f_2 = Matrix([[-1], [1], [1]])
f_3 = Matrix([[1], [-1], [1]])
f_4 = Matrix([[1], [1], [-1]])
f_5 = Matrix([[-1], [-1], [1]])
f_6 = Matrix([[1], [-1], [-1]])
f_7 = Matrix([[-1], [1], [-1]])
f_8 = Matrix([[-1], [-1], [-1]])

f = [f_1, f_2, f_3, f_4, f_5, f_6,f_7,f_8]
f
```

$$\left [ \left[\begin{matrix}1\\1\\1\end{matrix}\right], \quad \left[\begin{matrix}-1\\1\\1\end{matrix}\right], \quad \left[\begin{matrix}1\\-1\\1\end{matrix}\right], \quad \left[\begin{matrix}1\\1\\-1\end{matrix}\right], \quad \left[\begin{matrix}-1\\-1\\1\end{matrix}\right], \quad \left[\begin{matrix}1\\-1\\-1\end{matrix}\right], \quad \left[\begin{matrix}-1\\1\\-1\end{matrix}\right], \quad \left[\begin{matrix}-1\\-1\\-1\end{matrix}\right]\right ]$$

Calculating the frame bound $A$ (Proposition 2.1. p 12 in Waldron): Let $S =VV^∗ = AI_H ,\ V := [f_j]_j$

```python
V = Matrix([v.T for v in f]).T
V
```

$$\left[\begin{matrix}1 & -1 & 1 & 1 & -1 & 1 & -1 & -1\\1 & 1 & -1 & 1 & -1 & -1 & 1 & -1\\1 & 1 & 1 & -1 & 1 & -1 & -1 & -1\end{matrix}\right]$$

```python
S = V * V.T.conjugate()
S
```

$$\left[\begin{matrix}8 & 0 & 0\\0 & 8 & 0\\0 & 0 & 8\end{matrix}\right]$$

Insert  the frame bound $A$

```python
A = S[0,0]
A
```

$$8$$

```python
def prj(u):
    return u * u.T.conjugate()
```

POVM $W_i = \frac{1}{A}f_i f_i^*$

```python
W = [sympy.Rational(1/A) * prj(u) for u in f]
W
```

$$\left [ \left[\begin{matrix}\frac{1}{8} & \frac{1}{8} & \frac{1}{8}\\\frac{1}{8} & \frac{1}{8} & \frac{1}{8}\\\frac{1}{8} & \frac{1}{8} & \frac{1}{8}\end{matrix}\right], \quad \left[\begin{matrix}\frac{1}{8} & - \frac{1}{8} & - \frac{1}{8}\\- \frac{1}{8} & \frac{1}{8} & \frac{1}{8}\\- \frac{1}{8} & \frac{1}{8} & \frac{1}{8}\end{matrix}\right], \quad \left[\begin{matrix}\frac{1}{8} & - \frac{1}{8} & \frac{1}{8}\\- \frac{1}{8} & \frac{1}{8} & - \frac{1}{8}\\\frac{1}{8} & - \frac{1}{8} & \frac{1}{8}\end{matrix}\right], \quad \left[\begin{matrix}\frac{1}{8} & \frac{1}{8} & - \frac{1}{8}\\\frac{1}{8} & \frac{1}{8} & - \frac{1}{8}\\- \frac{1}{8} & - \frac{1}{8} & \frac{1}{8}\end{matrix}\right], \quad \left[\begin{matrix}\frac{1}{8} & \frac{1}{8} & - \frac{1}{8}\\\frac{1}{8} & \frac{1}{8} & - \frac{1}{8}\\- \frac{1}{8} & - \frac{1}{8} & \frac{1}{8}\end{matrix}\right], \quad \left[\begin{matrix}\frac{1}{8} & - \frac{1}{8} & - \frac{1}{8}\\- \frac{1}{8} & \frac{1}{8} & \frac{1}{8}\\- \frac{1}{8} & \frac{1}{8} & \frac{1}{8}\end{matrix}\right], \quad \left[\begin{matrix}\frac{1}{8} & - \frac{1}{8} & \frac{1}{8}\\- \frac{1}{8} & \frac{1}{8} & - \frac{1}{8}\\\frac{1}{8} & - \frac{1}{8} & \frac{1}{8}\end{matrix}\right], \quad \left[\begin{matrix}\frac{1}{8} & \frac{1}{8} & \frac{1}{8}\\\frac{1}{8} & \frac{1}{8} & \frac{1}{8}\\\frac{1}{8} & \frac{1}{8} & \frac{1}{8}\end{matrix}\right]\right ]$$

calculate the traces $tr W_i$

```python
trW = [sympy.trace(w) for w in W ]
trW
```

$$\left [ \frac{3}{8}, \quad \frac{3}{8}, \quad \frac{3}{8}, \quad \frac{3}{8}, \quad \frac{3}{8}, \quad \frac{3}{8}, \quad \frac{3}{8}, \quad \frac{3}{8}\right ]$$

chеcking that $\sum W_i = Id$

```python
sumPOVM = W[0]
for w in W[1:]:
    sumPOVM += w
sumPOVM
```

$$\left[\begin{matrix}1 & 0 & 0\\0 & 1 & 0\\0 & 0 & 1\end{matrix}\right]$$

Let $F:=(\dots, F_i,\dots)$, $F_i = \frac{W_i}{tr(W_i)}$

```python
F = [w/(sympy.trace(w)) for w in W]
F
```

$$\left [ \left[\begin{matrix}\frac{1}{3} & \frac{1}{3} & \frac{1}{3}\\\frac{1}{3} & \frac{1}{3} & \frac{1}{3}\\\frac{1}{3} & \frac{1}{3} & \frac{1}{3}\end{matrix}\right], \quad \left[\begin{matrix}\frac{1}{3} & - \frac{1}{3} & - \frac{1}{3}\\- \frac{1}{3} & \frac{1}{3} & \frac{1}{3}\\- \frac{1}{3} & \frac{1}{3} & \frac{1}{3}\end{matrix}\right], \quad \left[\begin{matrix}\frac{1}{3} & - \frac{1}{3} & \frac{1}{3}\\- \frac{1}{3} & \frac{1}{3} & - \frac{1}{3}\\\frac{1}{3} & - \frac{1}{3} & \frac{1}{3}\end{matrix}\right], \quad \left[\begin{matrix}\frac{1}{3} & \frac{1}{3} & - \frac{1}{3}\\\frac{1}{3} & \frac{1}{3} & - \frac{1}{3}\\- \frac{1}{3} & - \frac{1}{3} & \frac{1}{3}\end{matrix}\right], \quad \left[\begin{matrix}\frac{1}{3} & \frac{1}{3} & - \frac{1}{3}\\\frac{1}{3} & \frac{1}{3} & - \frac{1}{3}\\- \frac{1}{3} & - \frac{1}{3} & \frac{1}{3}\end{matrix}\right], \quad \left[\begin{matrix}\frac{1}{3} & - \frac{1}{3} & - \frac{1}{3}\\- \frac{1}{3} & \frac{1}{3} & \frac{1}{3}\\- \frac{1}{3} & \frac{1}{3} & \frac{1}{3}\end{matrix}\right], \quad \left[\begin{matrix}\frac{1}{3} & - \frac{1}{3} & \frac{1}{3}\\- \frac{1}{3} & \frac{1}{3} & - \frac{1}{3}\\\frac{1}{3} & - \frac{1}{3} & \frac{1}{3}\end{matrix}\right], \quad \left[\begin{matrix}\frac{1}{3} & \frac{1}{3} & \frac{1}{3}\\\frac{1}{3} & \frac{1}{3} & \frac{1}{3}\\\frac{1}{3} & \frac{1}{3} & \frac{1}{3}\end{matrix}\right]\right ]$$

$\alpha = \{\alpha_i\}_i = \{\frac{1}{d} trW_i\}_i$

```python
alpha = [Rational(1,d) * u for u in trW]
alpha
```

$$\left [ \frac{1}{8}, \quad \frac{1}{8}, \quad \frac{1}{8}, \quad \frac{1}{8}, \quad \frac{1}{8}, \quad \frac{1}{8}, \quad \frac{1}{8}, \quad \frac{1}{8}\right ]$$

$\mathcal{E}(A):=d\int_{\Omega} ((F(s),A))F(s)d\alpha(s)=d\sum_{\Omega} ((F_i,A))F_i\alpha_i$

for matrix $d \times d$  this is the basis matrix $E_{kl}$:

```python
def basismatrix_kl(d,k,l):
    return Matrix(d,d,lambda i,j: Integer(i == k and j == l))
```

$G_{kl}$ basis matricies for $\mathcal{L}(H)$

```python
def G_kl(d,k,l):
    if k==l :
        return  basismatrix_kl(d,k,l)
    if k<l :
        return basismatrix_kl(d,k,l) + basismatrix_kl(d,l,k)
    else :
        return 1j * basismatrix_kl(d,k,l) - 1j * basismatrix_kl(d,l,k)
```

$tr(F_i G_{kl})$

```python
def trWiEkl(F_i,G_kl):
    return (F_i * G_kl(d,k,l)).trace()
```

$tr(F_i A)F_i$

```python
def inthesum(F_i,A):
    return (F_i * A).trace() * F_i
```

$\mathcal{E}(G_{kl})=d\sum_{\Omega} tr(F_i G_{kl})F_i\alpha_i$

$\sum_{\Omega} tr(F_i G_{kl})F_i\alpha_i$:

```python
def cE_of_Gkl(F,A,alpha):
    B = inthesum(F[0],A)*alpha[0]
    for i in range(1, len(F)):
            B +=  inthesum(F[i],A)*alpha[i]
    return B
```

CHECK THE SUM ABOVE: range(1,len[F]) should it be range(1,len[F] + 1)?????????????????????????????

$\frac{1}{d}\mathcal{E}(G_{kl})=\sum_{m,n}\beta_{mn}G_{mn}$ , if $k\leq l$  then  $ \beta_{mn} = (\frac{A+A^t}{2})_{mn}$ else $\beta_{mn} = (\frac{A-A^t}{2i})_{mn}$

```python

def beta_mn(d,k,l,m,n) :
    A = G_kl(d,k,l)
    B = cE_of_Gkl(F,A,alpha)
    if m<=n :
        return  (B+B.T)[m,n]/2
    else :
        return (B-B.T)[m,n]/(2j)

```

```python

def cE(d,k,l):
    return Matrix(d,d, lambda m,n: beta_mn(d, k, l, m, n)).reshape(1,(d)**2)
cE(3,0,0)
```

$$\left[\begin{matrix}\frac{1}{9} & 0 & 0 & 0 & \frac{1}{9} & 0 & 0 & 0 & \frac{1}{9}\end{matrix}\right]$$

$\text{matrix with rows }cE(d,k,l)^T$ i.e. $\frac{1}{d}\mathcal{E}(A)$ in the basis $G_{kl}$ of $\mathcal{L}(H)$

```python
M = Integer (d) * Matrix([cE(d,k,l) for k,l in itertools.product(range(0,d), range(0,d))]).T
M
```

$$\left[\begin{matrix}\frac{1}{3} & 0 & 0 & 0 & \frac{1}{3} & 0 & 0 & 0 & \frac{1}{3}\\0 & \frac{2}{3} & 0 & 0 & 0 & 0 & 0 & 0 & 0\\0 & 0 & \frac{2}{3} & 0 & 0 & 0 & 0 & 0 & 0\\0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\frac{1}{3} & 0 & 0 & 0 & \frac{1}{3} & 0 & 0 & 0 & \frac{1}{3}\\0 & 0 & 0 & 0 & 0 & \frac{2}{3} & 0 & 0 & 0\\0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\frac{1}{3} & 0 & 0 & 0 & \frac{1}{3} & 0 & 0 & 0 & \frac{1}{3}\end{matrix}\right]$$

eigenvalues of $\mathcal{E}(A)$

```python
M.eigenvals()
```

$$\left \{ 0 : 5, \quad \frac{2}{3} : 3, \quad 1 : 1\right \}$$

Gap of a frame - for example a cube.ipynb


$$
\newcommand{\cond}{{\mkern+2mu} \vert {\mkern+2mu}}
\newcommand{\SetDiff}{\mathrel{\backslash}}
\DeclareMathOperator{\BetaFunc}{Β}
\DeclareMathOperator{\GammaFunc}{Γ}
\DeclareMathOperator{\prob}{p}
\DeclareMathOperator{\dcategorical}{Categorical}
\DeclareMathOperator{\dcategorical}{Categorical}
\DeclareMathOperator{\ddirichlet}{Dirichlet}
$$

# Collapsed Gibbs Sampling for LDA

## Notation

* $\ddirichlet_N(\alpha)$ is the symmetric $N$–dimensional Dirichlet distribution, all of whose $N$ parameters are $\alpha$

* $\dcategorical_N(\alpha)$ is the (asymmetric) $N$–dimensional categorical distribution whose $N$ parameters are given by the vector $\alpha$

* $\BetaFunc_N(\alpha)$, with a vector argument, is the (asymmetric) $N$–dimensional beta function defined by

$$\BetaFunc_N(\alpha) = \frac{\prod_n \GammaFunc(\alpha_n)}{\GammaFunc(\sum_n \alpha_n)}$$

* $\BetaFunc_N(\alpha)$, with a scalar argument, is the symmetric $N$–dimensional beta function defined by

$$\BetaFunc_N(\alpha) = \frac{\prod_n \GammaFunc(\alpha)}{\GammaFunc(\sum_n \alpha)} = \frac{\GammaFunc^N(\alpha)}{\GammaFunc(N\alpha)}$$

## Glossary

* $D$ documents

* $K$ topics

* $W$ words in the vocabulary

* $\theta_d$ is a per document $K$–vector of probabilities over the topics

* $\phi_k$ is a per topic $W$–vector of probabilities over the words in the vocabulary

* $z_{id}$ are per document word unobserved latent variables giving the topic assignments

* $x_{id}$ are per document word observed variables giving the vocabulary choices

* $Z$ is the collection of all the latent topic assignment variables

* $X$ is the collection of all the observed vocabulary assignment variables

* $\Theta$ is the collection of per document topic distributions

* $\Phi$ is the collection of per topic word distributions

* $Z^{\SetDiff id}$ is the collection of all the latent topic assignment variables excepting that for topic assignment $z_{id}$

* $\Theta^{\SetDiff d}$ is the collection of per document topic distributions excepting that for document $d$

* $\Phi^{\SetDiff k}$ is the collection of per topic word distributions excepting that for topic $k$

## Statistics

* $N_d$, the number of instances of words in document $d$

* $A_{dk}$, the number of $z_{id}$ variables assigned to topic $k$ in document $d$

$$
A_{dk} = \sum_{i=1}^{N_d} \delta(z_{id} = k)
$$

* $B_{kw}$, the number of times an instance of word $w$ is assigned to topic $k$

$$
B_{kw} = \sum_{d=1}^D \sum_{i=1}^{N_d} \delta(x_{id} = w) \delta(z_{id} = k)
$$

* $A_{dk}^{\SetDiff id}$, the $A_{dk}$ statistic taken over all variables except $z_{id}$

$$
\begin{align}
A_{dk}^{\SetDiff id}
    &= \sum_{i\prime \ne i} \delta(z_{i\prime d} = k) \\
    &= A_{dk} - \delta(z_{id} = k)
\end{align}
$$

* $B_{kw}^{\SetDiff id}$, the $B_{kw}$ statistic taken over all variables except $z_{id}$

$$
\begin{align}
B_{kw}^{\SetDiff id}
    &= B_{kw} - \delta(x_{id} = w) \delta(z_{id} = k)
\end{align}
$$

* $M_k$, the total number of instances of words assigned to topic k, and the matching statistic, $M_k^{\SetDiff id}$

$$
\begin{align}
M_k &= \sum_{w=1}^W B_{kw} \\
M_k^{\SetDiff id} &= \sum_{w=1}^W B_{kw}^{\SetDiff id}
\end{align}
$$

## Probabilities

The LDA model is then given by

$$
\begin{align}
\theta_d \cond \alpha &\sim \ddirichlet_K(\alpha) \\
\phi_k \cond \beta &\sim \ddirichlet_W(\beta) \\
z_{id} \cond \theta_d &\sim \dcategorical_K(\theta_d) \\
x_{id} \cond z_{id}, \phi_{z_{id}} &\sim \dcategorical_W(\phi_{z_{id}})
\end{align}
$$

Assuming that the only dependencies are those given above, the joint probability of
the latent and observed variables along with the parameters is given by

$$
\prob(Z, X, \Theta, \Phi) = \frac{1}{\BetaFunc_K^D(\alpha)} \prod_{d=1}^D \prod_{k=1}^K \theta_{dk}^{A_{dk} + \alpha - 1} \cdot \frac{1}{\BetaFunc_W^K(\beta)} \prod_{k=1}^K \prod_{w=1}^W \phi_{kw}^{B_{kw}+ \beta - 1}.
$$

### Probabilities required for Gibbs sampling

The required conditional probabilites for this model are

$$
\begin{align}
\prob(z_{id} \cond Z^{\SetDiff id}, X, \Theta, \Phi) &= \frac{\theta_{d,z_{id}} \phi_{z_{id}, x_{id}}}{\sum_{k=1}^K \theta_{dk} \phi_{k,x_{id}}} \\
\theta_d \cond Z, X, \Theta^{\SetDiff d}, \Phi &\sim \ddirichlet_K(A_d + \alpha) \\
\phi_k \cond Z, X, \Theta, \Phi^{\SetDiff k} &\sim \ddirichlet_W(B_k + \beta)
\end{align}
$$

where $A_d = (A_{d1}, \dotsc, A_{dK})$ and $B_k = (B_{k1}, \dotsc, B_{kW})$.

## Probabilities required for collapsed Gibbs sampling

We need to integrate out the $\theta_d$'s and the $\phi_k$'s from the model's joint probability.
This gives us

$$
\prob(Z, X) = \prod_{d=1}^D \frac{\BetaFunc_K(A_d + \alpha)}{\BetaFunc_K(\alpha)} \cdot \prod_{k=1}^K \frac{\BetaFunc_W(B_k + \beta)}{\BetaFunc_W(\beta)}
$$

Then the conditional probability from which we need to sample is

$$
\prob(z_{id} \cond Z^{\SetDiff id}, X)
    \propto \frac{
        \left( A_{d,z_{id}}^{\SetDiff id} + \alpha \right)
        \left( B_{z_{id},x_{id}}^{\SetDiff id} + \beta \right)
    }{
        M_{z_{id}}^{\SetDiff id} + W\beta
    }
$$

Since this is a discrete distribution over $k = 1, \dotsc, K$ it is easy to normalise as required, either explicitly or implicitly.