Project Demo

CSCI 4850-5850 - Neural Networks

Due: May 2 @ 9:30am

Inverse Nutrition Mapping - Bread Recipes

```python
import tensorflow as tf
import tensorflow.keras as keras
import numpy as np
import matplotlib.pyplot as plt
import csv
import sympy as sp
sp.init_printing(use_latex=True)

np.set_printoptions(suppress = True)

from src import *

from IPython.display import Image
```

#### Load Model & Load Normalizer Function

```python
def load(folderpath, modelpath):

    recipe_max_path = f"{folderpath}/norm_recipe_max"
    recipe_min_path = f"{folderpath}/norm_recipe_min"

    quant_max_path = f"{folderpath}/norm_quant_max"
    quant_min_path = f"{folderpath}/norm_quant_min"

    model = keras.models.load_model(f'{folderpath}/{modelpath}.h5')

    recipe_normalizer = Normalizer()
    recipe_normalizer.load(recipe_max_path, recipe_min_path)

    quant_normalizer = Normalizer()
    quant_normalizer.load(quant_max_path, quant_min_path)

    return model, recipe_normalizer, quant_normalizer
```

All Parameters

### Model to Load

```python
# For Generator
rank = True

train_examples = 10000
test_examples = 100

# For Model
embed_dim = 1024
stack = 6
optimizer = keras.optimizers.Nadam() # Optimizer
loss = keras.losses.MSE # Regression Loss Function
metrics = keras.metrics.MeanSquaredError() # Metrics

# For Model Training
batch_size = 48
epochs = 1000
validation_split = 0.2
verbose = 0
```

    2022-05-02 00:56:43.004508: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:925] could not open file to read NUMA node: /sys/bus/pci/devices/0000:01:00.0/numa_node
    Your kernel may have been built without NUMA support.
    2022-05-02 00:56:43.115942: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:925] could not open file to read NUMA node: /sys/bus/pci/devices/0000:01:00.0/numa_node
    Your kernel may have been built without NUMA support.
    2022-05-02 00:56:43.116456: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:925] could not open file to read NUMA node: /sys/bus/pci/devices/0000:01:00.0/numa_node
    Your kernel may have been built without NUMA support.
    2022-05-02 00:56:43.117859: I tensorflow/core/platform/cpu_feature_guard.cc:151] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX2 FMA
    To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
    2022-05-02 00:56:43.135062: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:925] could not open file to read NUMA node: /sys/bus/pci/devices/0000:01:00.0/numa_node
    Your kernel may have been built without NUMA support.
    2022-05-02 00:56:43.135438: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:925] could not open file to read NUMA node: /sys/bus/pci/devices/0000:01:00.0/numa_node
    Your kernel may have been built without NUMA support.
    2022-05-02 00:56:43.135768: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:925] could not open file to read NUMA node: /sys/bus/pci/devices/0000:01:00.0/numa_node
    Your kernel may have been built without NUMA support.
    2022-05-02 00:56:45.061914: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:925] could not open file to read NUMA node: /sys/bus/pci/devices/0000:01:00.0/numa_node
    Your kernel may have been built without NUMA support.
    2022-05-02 00:56:45.062440: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:925] could not open file to read NUMA node: /sys/bus/pci/devices/0000:01:00.0/numa_node
    Your kernel may have been built without NUMA support.
    2022-05-02 00:56:45.062456: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1609] Could not identify NUMA node of platform GPU id 0, defaulting to 0.  Your kernel may not have been built with NUMA support.
    2022-05-02 00:56:45.062861: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:925] could not open file to read NUMA node: /sys/bus/pci/devices/0000:01:00.0/numa_node
    Your kernel may have been built without NUMA support.
    2022-05-02 00:56:45.062913: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1525] Created device /job:localhost/replica:0/task:0/device:GPU:0 with 1615 MB memory:  -> device: 0, name: NVIDIA GeForce RTX 3050 Ti Laptop GPU, pci bus id: 0000:01:00.0, compute capability: 8.6

```python
run = 9
model_name = f"{run}_rank_{rank}"
model_folder = f"./models/{model_name}"
```

General Explanation

The goal of this network is to be able to pass in information that is found on nutritional facts labels, such as nutrients and relative ingredient quantities, and have the network estimate the main ingredients of that recipe with their masses in grams. To accomplish this, we have provided our network with the accurate values relating those ingredients to the nutritional items. These values are shown below. They are all in grams for convenience and comparability.

Generator

We took the list above and ran it through a generator we built to create thousands of random recipes to use as training data. These recipes could then be ranked or normalized to alter how the network would interpret them.

```python
generator = Generator('./data/nutrients.csv') # filename with data
generator.generate(train_examples) # generates arg number of examples
```

The shape is (n,13) where n is the number of training samples and 13 is the number of nutrients

```python
Shape_s = sp.symbols("Shape")
sp.Eq(Shape_s,sp.Matrix(generator.recipes.shape),evaluate=False)
```

$\displaystyle Shape = \left[\begin{matrix}10000\\13\end{matrix}\right]$

```python
index = 0
Nutr_s = sp.symbols('Nutrients')
sp.Eq(Nutr_s,sp.Matrix(generator.recipes[index]),evaluate=False)
```

$\displaystyle Nutrients = \left[\begin{matrix}1931.47993213582\\9.92678469354512\\1.19577698947603\\8.0 \cdot 10^{-5}\\10.8954214126394\\383.944532201888\\18.5197480629447\\12.3719140270568\\71.7786900667674\\1.0 \cdot 10^{-8}\\0.112728337947848\\0.0252387369595324\\1.12955050403903\end{matrix}\right]$

```python
Quant_s = sp.symbols('Quantities')
sp.Eq(Quant_s,sp.Matrix(generator.quant[index]),evaluate=False)
```

$\displaystyle Quantities = \left[\begin{matrix}31.2639434002339\\336.35210724647\\28.0181303071137\\498.096664537315\\0\\10.8326240334449\\0\\0\\0\\0\\0\\0\\0\\0\\1.0\end{matrix}\right]$

When the 'rank' flag is set, we append numbers on to the end of the nutrients list that represent the ingredients highest in quantity. The highest will receive a 14 and lowest gets 0

```python
if rank:
    generator.rank()
```

```python
sp.Eq(Shape_s,sp.Matrix(generator.recipes.shape),evaluate=False)
```

$\displaystyle Shape = \left[\begin{matrix}10000\\28\end{matrix}\right]$

```python
index = 0
sp.Eq(Nutr_s,sp.Matrix(generator.recipes[index]),evaluate=False)
```

$\displaystyle Nutrients = \left[\begin{matrix}1931.47993213582\\9.92678469354512\\1.19577698947603\\8.0 \cdot 10^{-5}\\10.8954214126394\\383.944532201888\\18.5197480629447\\12.3719140270568\\71.7786900667674\\1.0 \cdot 10^{-8}\\0.112728337947848\\0.0252387369595324\\1.12955050403903\\12.0\\13.0\\11.0\\14.0\\0\\10.0\\1.0\\2.0\\3.0\\4.0\\5.0\\6.0\\7.0\\8.0\\9.0\end{matrix}\right]$

We are solving a continuous problem. Instead of forcing the network to interpret nutrients in the hundreds or thousands, we normalize the data, scaling all nutrients between 0-2 and quantities between 0-1

```python
recipes = generator.recipes
quant = generator.quant

#normalize
recipe_norm_example = Normalizer()
recipe_norm_example.fit(recipes)
recipes = recipe_norm_example.normalize(recipes)

quant_norm_example = Normalizer()
quant_norm_example.fit(quant)
quant = quant_norm_example.normalize(quant)
```

```python
sp.Eq(Nutr_s,sp.Matrix(recipes[index]),evaluate=False)
```

$\displaystyle Nutrients = \left[\begin{matrix}0.516961360429602\\0.1768147562356\\0.0568429373476058\\0.000270099278529921\\0.77638481604165\\0.540869024323339\\0.260745337405612\\0.147152446901128\\0.407212410861272\\0.000151951975216973\\0.308061848277596\\0.328212568467101\\0.193386340626001\\1.5\\0.928571428571429\\1.57142857142857\\1.0\\0\\0.769230769230769\\0.0714285714285714\\0.153846153846154\\0.214285714285714\\0.307692307692308\\0.454545454545455\\0.666666666666667\\0.7\\0.888888888888889\\1.5\end{matrix}\right]$

```python
sp.Eq(Quant_s,sp.Matrix(quant[index]),evaluate=False)
```

$\displaystyle Quantities = \left[\begin{matrix}0.119983439083752\\0.570095875048776\\0.786617329832926\\0.732537583268827\\0\\0.343964968313721\\0\\0\\0\\0\\0\\0\\0\\0\\1.0\end{matrix}\right]$

Model

The model uses stacked residual blocks to create a deep Network with skip connections.

```python
i1 = keras.layers.Input(generator.recipes.shape[1:])
f1 = keras.layers.Flatten()(i1)
y1 = keras.layers.Dense(embed_dim)(f1)

# Stack residual blocks (skip connections)
for i in range(stack):
    y2 = y1
    y1 = keras.layers.Dense(embed_dim,
        activation=keras.activations.relu)(y1)
    y1 = keras.layers.Add()([y1,y2])
    y1 = keras.layers.LayerNormalization()(y1)

o1 = keras.layers.Dense(quant.shape[1],activation=keras.activations.softplus)(y1)

# Uncomment these if you wish to make your own model
model = keras.Model(i1,o1)
model.compile(optimizer=optimizer,loss=loss,metrics=metrics)
model.summary()
keras.utils.plot_model(model,show_shapes=True,expand_nested=True)
```

Model Training

If you uncomment the fit function below and comment the model load function and the plot_model function instead, you can run the model above in real time and graph its progress. You can also then see the plot of how the model trained if you run this live by uncommenting the plt section. However, this process could take quite a long time, as such, a pre-trained model has been prepared.

```python
# history = model.fit(generator.recipes, generator.quant,batch_size=batch_size,epochs=epochs,
#                    validation_split = validation_split,verbose=verbose)

# plt.figure(1)
#summarize history for accuracy
# plt.subplot(211)
# plt.plot(history.history['mean_squared_error'])
# plt.plot(history.history['val_mean_squared_error'])
# plt.plot(history.history['loss'])
# plt.ylabel('Progress')
# plt.xlabel('Epoch')
# plt.legend(['Training','Validation'],loc='upper right')
```

```python
# Load in pre-made model
model, recipe_normalizer, quant_normalizer = load(model_folder, model_name)

#keras.utils.plot_model(model,to_file='recipe_model.png',show_shapes=True,expand_nested=True)
```

```python
# Comment this out as well when testing your own models
Image("./images/training_graph.png")
```

Testing Fake Data

Here we create new data to run tests on.

```python
#prediction values
test_gen = Generator('./data/nutrients.csv')
test_gen.generate(test_examples) # generates arg number of examples
if rank:
    test_gen.rank()

test_recipes = test_gen.recipes
test_quant = test_gen.quant

#normalize
test_recipes = recipe_normalizer.normalize(test_recipes)
test_quant = quant_normalizer.normalize(test_quant)

pred = model.predict(test_recipes)
pred = quant_normalizer.inv_normalize(pred) #inverse normalize

actual = test_gen.quant
```

```python
# Change this value to anything within the amount of
# test_examples you specified in the parameter block above
testing_index = 0
# Then rerun this block

ingredientsList =['Active Dry Yeast','Water','Salt','Bread Flour','Butter','Sugar','Egg','Dry Milk',
                  'Whole Wheat Flour','Vegetable Oil','Olive Oil','Honey','Brown Sugar','All-Purpose Flour','Milk']

print(f"Fake Recipe #{testing_index+1}\n")
for i in range(len(actual[0])):
    print(f"{ingredientsList[i].ljust(18)} - predicted: {str(round(pred[testing_index,i],3)).ljust(7)} grams | actual: {str(round(actual[testing_index,i],3)).ljust(6)} grams ")
```

    Fake Recipe #1

    Active Dry Yeast   - predicted: 28.957  grams | actual: 32.54  grams
    Water              - predicted: 365.289 grams | actual: 353.536 grams
    Salt               - predicted: 20.506  grams | actual: 21.153 grams
    Bread Flour        - predicted: 129.725 grams | actual: 145.614 grams
    Butter             - predicted: 0.0     grams | actual: 0.0    grams
    Sugar              - predicted: 26.942  grams | actual: 25.714 grams
    Egg                - predicted: 0.001   grams | actual: 0.0    grams
    Dry Milk           - predicted: 0.001   grams | actual: 0.0    grams
    Whole Wheat Flour  - predicted: 0.016   grams | actual: 0.0    grams
    Vegetable Oil      - predicted: 0.002   grams | actual: 0.0    grams
    Olive Oil          - predicted: 12.396  grams | actual: 12.894 grams
    Honey              - predicted: 0.0     grams | actual: 0.0    grams
    Brown Sugar        - predicted: 0.001   grams | actual: 0.0    grams
    All-Purpose Flour  - predicted: 0.013   grams | actual: 0.0    grams
    Milk               - predicted: 0.022   grams | actual: 1.0    grams

Testing Real Data

```python
data = list(csv.reader(open(f"{model_folder}/accuracyPer.csv")))
data = np.array(data)
recipe_quant = "./data/Recipe Data.csv"
ingr_nutr = "./data/nutrients.csv"

realdata = RealData(recipe_quant,ingr_nutr)
if rank:
    realdata.rank()

real_recipes = realdata.recipes
real_quant = realdata.quant

#normalize
real_recipes = recipe_normalizer.normalize(real_recipes)

pred = model.predict(real_recipes)
pred = quant_normalizer.inv_normalize(pred) #inverse normalize

actual = realdata.quant
```

For testing purposes, we have gathered a small list of recipes with the proper amounts of ingredients so we can accurately test our network.

```python
# Change this value to an index of a recipe above
testing_index = 8
# Then rerun this block

recipeNames = ['invalid','Traditional White Bread','Country White Bread','Sourdough Starter','French Countryside Bread',
               'Italian Herb Bread','White Bread','100% Whole Wheat Bread','Dough','Bagel dough',
               'Bread Pretzel','Butter Rolls','Pizza Crust']

print(f"Recipe - {recipeNames[testing_index]} - {str(data[testing_index-1,0])}% Accurate\n")

for i in range(len(actual[0])):
    print(f"{ingredientsList[i].ljust(18)} - predicted: {str(round(pred[testing_index-1,i],3)).ljust(7)} grams | actual: {str(round(actual[testing_index-1,i],3)).ljust(6)} grams")
```

    Recipe - Dough - 96.3% Accurate

    Active Dry Yeast   - predicted: 2.638   grams | actual: 2.8    grams
    Water              - predicted: 379.23  grams | actual: 413.0  grams
    Salt               - predicted: 10.215  grams | actual: 11.4   grams
    Bread Flour        - predicted: 0.012   grams | actual: 0.0    grams
    Butter             - predicted: 0.0     grams | actual: 0.0    grams
    Sugar              - predicted: 3.483   grams | actual: 4.2    grams
    Egg                - predicted: 0.005   grams | actual: 0.0    grams
    Dry Milk           - predicted: 2.638   grams | actual: 2.8    grams
    Whole Wheat Flour  - predicted: 0.004   grams | actual: 0.0    grams
    Vegetable Oil      - predicted: 29.728  grams | actual: 28.6   grams
    Olive Oil          - predicted: 0.0     grams | actual: 0.0    grams
    Honey              - predicted: 0.0     grams | actual: 0.0    grams
    Brown Sugar        - predicted: 0.0     grams | actual: 0.0    grams
    All-Purpose Flour  - predicted: 565.609 grams | actual: 540.0  grams
    Milk               - predicted: 0.0     grams | actual: 0.0    grams

Accuracy

```python
datafile = open(f"{model_folder}/accuracy.csv")
data = list(csv.reader(datafile))
data = np.array(data)
```

```python
parsed = [""]
count = 0
for arr in data:
    for row in arr:
        for c in row:
            if c.isdigit() or c == '.':
                parsed[count] += c
            else:
                count += 1
                parsed.append("")

parsed = list(filter(('').__ne__, parsed))
data = np.array(parsed,dtype=float)
data = data.reshape(-1,17)
```

### Mean Absolute Percentage Error (MAPE)

## ${\sum^{n}_{t=1} \frac{ | A_t - F_t | }{A_t}} $

```python
index = 6

sp.symbols('Nutrients')

sym_1 = sp.symbols(f"{index}_mape")
sym_2 = sp.symbols(f"{index}_ingr-mape")
```

```python
sp.Eq(sym_1,sp.Matrix(np.array(data[index,1:2],dtype=float)),evaluate=False)
```

$\displaystyle 6_{mape} = \left[\begin{matrix}97.826\end{matrix}\right]$

```python
sp.Eq(sym_2,sp.Matrix(np.array(data[index,2:],dtype=float)),evaluate=False)
```

$\displaystyle 6_{ingr-mape} = \left[\begin{matrix}82.35\\96.03\\93.9\\100.0\\100.0\\100.0\\100.0\\100.0\\98.59\\100.0\\100.0\\100.0\\96.48\\100.0\\100.0\end{matrix}\right]$

______________________


 ILI286 - Computación Científica II

 Ecuaciones Diferenciales Parciales: Elípticas

 [[S]cientific [C]omputing [T]eam](#acknowledgements)

 Version: 1.12

# Tabla de Contenidos
* [Introducción](#intro)
* [Marco Teórico](#teo)
* [Ecuación de Poisson](#poisson)
* [Ecuación de Helmotz](#helmotz)
* [Acknowledgements](#acknowledgements)

```python
import numpy as np
from numpy.linalg import solve
from mpl_toolkits.mplot3d import axes3d
%matplotlib inline
from matplotlib import pyplot as plt

def plot(x,y,w):
    # Plot the solution
    X,Y = np.meshgrid(y,x)
    W = w.reshape(X.shape)
    fig = plt.figure(figsize=(10,10))
    ax = fig.add_subplot(111, projection='3d')
    ax.plot_wireframe(X, Y, W)
    #ax.plot_surface(X, Y, W, alpha=0.25)
    plt.xlabel("y")
    plt.ylabel("x")
    #ax.set_zlim(0.,1.)
    #plt.savefig("sol%dx%d.png"%(Nx+1,Ny+1))
    plt.show()
```

# Introducción

En el siguiente notebook se estudia la resolución numérica de **ecuaciones diferenciales parciales elípticas**. La resolución de estas tiene gran importancia, ya que aparecen repetidas veces en diferentes modelos físicos relacionados con los potenciales de energía. Por ejemplo, el potencial de carga eléctrica de acuerdo a las ecuaciones de _Maxwell_ puede ser escrito como:
$$
\Delta u = -\frac{\rho}{\epsilon},
$$
con $u$ el potencial eléctrico, $\rho$ la densidad de carga, y $\epsilon$ el la permitividad eléctrica.

El método numérico que estudiaremos se basa en **diferencias finitas**, del mismo modo en que se hizo para resolver EDOs anteriormente, esto es, las derivadas son aproximadas por gradientes calculados numéricamente en base a los valores vecinos de cada punto. Sin embargo ahora *las derivadas son parciales!*, pero como veremos esto no es gran problema.

# Marco Teórico

Si consideramos una función $u(x,y)$ dos veces diferenciable, entonces se define el operador *Laplaciano* como:

$$
\Delta u(x,y) = u_{xx}(x,y) + u_{yy}(x,y),
$$

si se considera además una función $f(x,y)$, entonces es posible definir:

$$
\Delta u(x,y) = u_{xx}(x,y) + u_{yy}(x,y) = f(x,y), \ \ \  \text{con } \ x,y \in \Omega \ \text{ y condiciones de borde en } \ \partial \Omega
$$

como la **ecuación de Poisson**, la cual es una de las más conocidas dentro de la clase _ Elípticas _. El caso particular donde $f(x,y) = 0$ se conoce como **ecuación de Laplace**.

### Formulación numérica

Sea la ecuación de Laplace $\Delta u(x,y) = 0$ sobre un dominio rectangular $[x_a, x_b] \times [y_a, y_b]$, con condiciones de borde de Dirichlet:

\begin{align}
    u(x,y_a) &= g_1(x) \\
    u(x,y_b) &= g_2(x) \\
    u(x_a,y) &= g_3(y) \\
    u(x_b,y) &= g_4(y) .
\end{align}

Para resolver este problema por diferencias finitas, es necesario discretizar el dominio $\Omega$ sobre el que se define la función. Tal discretización se puede ver gráficamente como se muestra a continuación:

```python
x = np.linspace(0., 1., 10)
y = np.linspace(0., 1., 10)
xgrid, ygrid = np.meshgrid(x, y, sparse=False)
plt.scatter(xgrid.ravel(), ygrid.ravel())
plt.plot((0,1),(0,0), 'g--' ,lw=2)
plt.plot((1,1),(0,1), 'g--' ,lw=2)
plt.plot((1,0),(1,1), 'g--' ,lw=2)
plt.plot((0,0),(1,0), 'g--' ,lw=2)
plt.title('Esquema de discretizacion uniforme')
plt.xlim(-0.1,1.1)
plt.ylim(-0.1,1.1)
plt.axis('equal')
plt.show()
```

en donde cada punto interior (*azul*), representa un punto donde queremos conocer el valor de la función $u(x,y)$. Consideraremos además que  $h_x$ y $h_y$ el *space step* de la malla.

Dado que las derivadas del Laplaciano son *parciales*, se puede utilizar diferencias finitas sin ninguna modificación. Para aproximar cada segunda derivada se utiliza *diferencias centradas* (**centered difference formula**), que posee un error de orden cuadrático en $h$.

$$
\underbrace{ \frac{u(x-h_x,y) - 2 u(x,y) + u(x+h_x, y)}{h_x^2} + O(h_x^2) }_{= u_{xx}(x,y)} + \underbrace{ \frac{u(x,y-h_y) - 2 u(x,y) + u(x, y+h_y)}{h_y^2} + O(h_y^2) }_{= u_{yy}(x,y)} = f(x,y)
$$

Ocupando la notación $u(x_i,y_j) = w_{ij}$ para el punto $(x_i, y_j)$ de la malla, la ecuación de discretización es finalmente:

$$
\frac{w_{i-1,j} - 2 w_{i,j} + w_{i+1,j}}{h_x^2} + \frac{w_{i,j-1} - 2 w_{i,j} + w_{i,j+1}}{h_y^2} \approx f(x_i, y_j),
$$

válida para todos los puntos $(x_i, y_j) \in \Omega - \partial \Omega$ (puntos interiores). Hay una aspecto muy importante a notar: **Esta ecuación es lineal en los $\mathbf{w_{i,j}}$!**. El procedimiento es entonces como sigue:

* Evaluar la ecuación discretizada para cada punto interior $(x_i, y_j)$, utilizando la información de los valores en la frontera.
* Construir un sistema lineal $A \mathbf{w} = b$, en donde $\mathbf{w}$ es el vector con todos los $w_{i,j}$. (Para su construcción revisar el libro guía: *Numerical Analysis, Timothy Sauer*).
* Resolver el sistema anterior para $\mathbf{w}$, utilizando cualquier `solver` de sistemas lineales (LU, PALU, Jacobi, Gauss-Seidel, etc).

# Ecuación de Poisson

Buscaremos resolver la ecuación de Poisson con condición de frontera de Dirichlet:

\begin{align*}
u_{xx}(x,y) + u_{yy}(x,y) = f(x,y) \ , \ x, y \in [0,1]\times[0,1] \\
\end{align*}

\begin{align*}
u(x,0) = b(x)
\end{align*}

\begin{align*}
u(x,1) = t(x)
\end{align*}

\begin{align*}
u(0, y) = l(y)
\end{align*}

\begin{align*}
u(1,y) = r(y)
\end{align*}

A continuación se definen tres problemas de Poisson a resolver.

```python
# Problema 1
xmin, xmax = -1, 1.
ymin, ymax = -1, 1.
f  = lambda x,y : x*y
bottom = lambda x : 0# np.sin(np.pi*x)
top = lambda x : 0 #np.sin(np.pi*x)
left = lambda y : 0
right = lambda y: 0
P1 = {"f":f, "b":bottom, "t":top, "l":left, "r":right,
      "xmin":xmin, "xmax":xmax, "ymin":ymin, "ymax":ymax}

# Problema 2
xmin, xmax = 0., 1.
ymin, ymax = 0., 1.
f  = lambda x,y : x
bottom = lambda x : np.sin(np.pi*x)
top = lambda x : np.sin(np.pi*x)
left = lambda y : 0
right = lambda y: 0
P2 = {"f":f, "b":bottom, "t":top, "l":left, "r":right,
      "xmin":xmin, "xmax":xmax, "ymin":ymin, "ymax":ymax}

# Problema 3
xmin, xmax = -1, 1.
ymin, ymax = -1, 1.
f  = lambda x,y : 0
bottom = lambda x : 0
top = lambda x : 0
left = lambda y : np.sin(np.pi*y)
right = lambda y: -np.sin(np.pi*y)
P3 = {"f":f, "b":bottom, "t":top, "l":left, "r":right,
      "xmin":xmin, "xmax":xmax, "ymin":ymin, "ymax":ymax}

# Problema 4
xmin, xmax = 0, 1.
ymin, ymax = 0, 1.
f  = lambda x,y : x*np.exp(y)
bottom = lambda x : x
top = lambda x : x*np.exp(1)
left = lambda y : 0*y
right = lambda y: np.exp(y)
P4 = {"f":f, "b":bottom, "t":top, "l":left, "r":right,
      "xmin":xmin, "xmax":xmax, "ymin":ymin, "ymax":ymax}
```

La función `solve_laplace()` es la encargada de construir el sistema lineal correspondiente para al problema `P` a resolver.

**Q:** _¿Podría usted explicar esta construcción en base a lo visto en la formulación teórica?_.

```python
def solve_laplace(P, Nx, Ny):
    # Discretize x and y
    x = np.linspace(P["xmin"], P["xmax"], Nx+1)
    y = np.linspace(P["ymin"], P["ymax"], Ny+1)

    # Define the discretization parameters
    dx = x[1]-x[0]
    dy = y[1]-y[0]

    # Create the matrix and the right hand size vector
    A = np.zeros([(Nx+1)*(Ny+1), (Nx+1)*(Ny+1)])
    b = np.zeros([(Nx+1)*(Ny+1), 1])

    # Define global indexing
    def index(i, j, nCols=(Ny+1)):
        return j + i*nCols

    # Fill up the matrix and right hand side vector
    for i in range(Nx+1):
        for j in range(Ny+1):
            k = index(i,j)
            if j==0: # y=ymin, bottom
                A[k,k] =  1.
                b[k] = P["b"](x[i])
            elif i==Nx: # x=xmax, right
                A[k,k] =  1.
                b[k] = P["r"](y[j])
            elif j==Ny: # y=ymax, top
                A[k,k] =  1.
                b[k] = P["t"](x[i])
            elif i==0:  # x=xmin, left
                A[k,k] =  1.
                b[k] = P["l"](y[j])
            else:
                A[k, k] = -2./dx**2 - 2./dy**2
                A[k,index(i+1,j)] =  1./dx**2
                A[k,index(i-1,j)] =  1./dx**2
                A[k,index(i,j-1)] =  1./dy**2
                A[k,index(i,j+1)] =  1./dy**2
                b[k] = P["f"](x[i], y[j])

    # Solve the linear system
    w = solve(A, b)
    return x, y, w
```

```python
P = P4
x, y, u = solve_laplace(P, Nx=20, Ny=20)
plot(x,y,u)
```

# Ecuación de Helmoltz

```python
# Problem 1
Lambda = 0.0
xmin, xmax = 0., 1.
ymin, ymax = 0., 1.
f  = lambda x,y : 0
bottom = lambda x : 0
top = lambda x : 0
left = lambda y : 1
right = lambda y: 1
P1 = {"Lambda":Lambda, "f":f, "b":bottom, "t":top, "l":left, "r":right,
      "xmin":xmin, "xmax":xmax, "ymin":ymin, "ymax":ymax}

# Problem 2
Lambda = 2.0
xmin, xmax = 0., 1.
ymin, ymax = 0., 1.
f  = lambda x,y : 0
bottom = lambda x : 0
top = lambda x : 0
left = lambda y : 1
right = lambda y: 1
P2 = {"Lambda":Lambda, "f":f, "b":bottom, "t":top, "l":left, "r":right,
      "xmin":xmin, "xmax":xmax, "ymin":ymin, "ymax":ymax}

# Problem 3
Lambda = 0.0
xmin, xmax = -1, 1.
ymin, ymax = -1, 1.
f  = lambda x,y : 0
bottom = lambda x : 0
top = lambda x : 0
left = lambda y : np.sin(np.pi*y)
right = lambda y: -np.sin(np.pi*y)
P3 = {"Lambda":Lambda, "f":f, "b":bottom, "t":top, "l":left, "r":right,
      "xmin":xmin, "xmax":xmax, "ymin":ymin, "ymax":ymax}
```

La función `solve_herlmotz()` es la encargada de construir el sistema lineal correspondiente para al problema `P` a resolver.

**Q:** _¿Podría usted explicar esta construcción en base a lo visto en la formulación teórica?_.

```python
def solve_helmholtz(P, Nx, Ny):
    # Discretize x and y
    x = np.linspace(P["xmin"], P["xmax"], Nx+1)
    y = np.linspace(P["ymin"], P["ymax"], Ny+1)
    L = P["Lambda"]

    # Define the discretization parameters
    dx = x[1]-x[0]
    dy = y[1]-y[0]

    # Create the matrix and the right hand size vector
    A = np.zeros([(Nx+1)*(Ny+1), (Nx+1)*(Ny+1)])
    b = np.zeros([(Nx+1)*(Ny+1), 1])

    # Define global indexing
    def index(i, j, nCols=(Ny+1)):
        return j + i*nCols

    # Fill up the matrix and right hand side vector
    for i in range(Nx+1):
        for j in range(Ny+1):
            k = index(i,j)
            if j==0: # y=ymin, bottom
                A[k,k] =  -1.5/dy
                A[k,index(i,j+1)] = 2.0/dy
                A[k,index(i,j+2)] =-0.5/dy
                b[k] = P["b"](x[i])
            elif i==Nx: # x=xmax, right
                A[k,k] =  1.
                b[k] = P["r"](y[j])
            elif j==Ny: # y=ymax, top
                A[k,k] =  1.5/dy
                A[k,index(i,j-1)] =  -2.0/dy
                A[k,index(i,j-2)] =  +0.5/dy
                b[k] = P["t"](x[i])
            elif i==0:  # x=xmin, left
                A[k,k] =  1.
                b[k] = P["l"](y[j])
            else:
                A[k, k] = -2./dx**2 - 2./dy**2 - L
                A[k,index(i+1,j)] =  1./dx**2
                A[k,index(i-1,j)] =  1./dx**2
                A[k,index(i,j-1)] =  1./dy**2
                A[k,index(i,j+1)] =  1./dy**2
                b[k] = P["f"](x[i], y[j])

    # Solve the linear system
    w = solve(A, b)
    return x, y, w
```

```python
P = P3
x, y, u = solve_helmholtz(P, Nx=40, Ny=40)
plot(x,y,u)
```

# Acknowledgements
* _Material creado por profesor Claudio Torres_ (`ctorres@inf.utfsm.cl`) _y ayudantes: Alvaro Salinas y Martín Villanueva. DI UTFSM. Abril 2016._

***
### DISCLAIMER ###

El presente notebook ha sido creado para el curso **ILI286 - Computación Científica 2**, del [Departamento de Informática](http://www.inf.utfsm.cl/), [Universidad Técnica Federico Santa María](http://www.utfsm.cl/).

El material ha sido creado por Claudio Torres <ctorres@inf.utfsm.cl> y ayudantes, y es distribuido sin restricciones. En caso de encontrar un error, por favor no dude en contactarnos.

[Update 2016] V1.1 (Martín) Integrados los notebook de Poisson y Helmotz en un sólo notebook. Se agregó contexto y marco teórico.
***

```python

```


(c) Juan Gomez 2019. Thanks to Universidad EAFIT for support. This material is part of the course Introduction to Finite Element Analysis

# Stress analysis with SolidsPy

## Introduction

This notebook briefly describes the steps required to conduct stress analysis over two-dimensional elastic domains using **SolidsPy**. The process involves meshing the domain with **gmsh**, a free three-dimensional finite element mesh generetor, and the generation of the required **SolidsPy** input files using available Python subroutines.

**After completing this notebook you should be able to:**

* Identify the representation of a finite element model in the open code **SolidsPy** in terms of text files.

* Create a finite element model of a two-dimensional domain using **gmsh** and ready to be processed with **SolidsPy**.

* Create Python scripts required to convert a **gmsh** finte element model into **SolidsPy**.

**Note: To cover this notebook the user must have installed SolidsPy and the required modules as defined in the code's REPO. Similarly, files to conduct the example are available in the files folder of this REPO**

## SolidsPy

**SolidsPy** is a simple finite element code to conduct stress analysis of arbitrary two-dimensional elastic domains. The code has been created for teaching purposses by Juan Gomez and Nicolas Guarin-Zapata from the Applied Mechanics Lab at [Universidad EAFIT](http://www.eafit.edu.co/) . It has a modular structure so it can be used by simply typing an execution command from within a Python terminal or through the execution of problem dependent scripts written with **SolidsPy** own subroutines.

Currently **SolidsPy** supports an element library with the following elements:

In **SolidsPy** a finite element model is represented in terms of text files defining the nodes, elements, loads and material properties. The complete description of the code, together with detailed tutorials and installation instructions is avaialable in the following [REPO.](https://github.com/AppliedMechanics-EAFIT/SolidsPy)

## Steps in the creation of a finite element model for SolidsPy

The Python code **SolidsPy** conducts stress analysis over two-dimensional (plane strain) elastic domains discretized with finite elements. The code uses, as input data, a finite element representation of the 2D solid described in terms of text files defining:

* Nodal data: Space coordinates and displacement boundary conditions for every nodal point in the model.

* Element data: Fundamental interpolation function, material properties and nodal points for each element in the mesh.

* Loads data: Load vector (defined in a cartesian reference system) and nodal data for each loaded loaded point in the model.

* Materials data: Young's modulus and Poisson's ratio for each new material appearing in the model.

The creation of a model in **SolidsPy** involves two basic steps, namely:

* (i) meshing the 2D domain

* (ii) writing the input (text) files.

In the case of complex domains resulting in a large number of elements step 1 is conducted with third party software. Here we will use [gmsh](http://gmsh.info/) to create finite element meshes. On the other hand, step 2 involving the process of writing the input files for **SolidsPy** is performed using subroutines available in the Python module [meshio](https://github.com/nschloe/meshio). The complete process is schematically described in the figure below.

The first step, involving the creation of a model in **gmsh** is not covered in this notebook, however the following video presents a summary of these steps for a simple model.

```python
from IPython.display import YouTubeVideo
YouTubeVideo('ibeIKaSYG2U')
```

## Problem definition.

Once the model is created with **gmsh** it must be converted into **SolidsPy** input files. This second step is conducted with Python subroutines coded in the module **meshio** which can interpret all the parts in a **gmsh** model and convert them into Python dictionaries. These dictionaries can then be processed in standrd Python based scripts. This process is described next with reference to a simple elasticty problem.

The problem to be solved consists in finding the stress tensor over a bi-material plate (under plane strain conditions) loaded at the top surface by a uniform load of intensity W. Each material is characterized by its Poisson’s ratio and Young modulus. The bottom surface is not allowed to displace along the vertical direction while the lateral surfaces are not allowed to experience horizontal displacements as indicated by the roller supports.

## Model definition.

In **gmsh** a model is defined through the creation and combination of **geometric objects** of increasing hierarchy. In the case of two-dimensional domains these are summarized like:

* Points.
* Lines: resulting from the combination of points.
* Plane surafces (or line loops): resulting from the combination of several lines.

This process is ilustrated in the following figure.

To facilite the subsequent interpretation of the different parts of the model in a finte element analysis **gmsh** allows the creation of groups of these **geometric objects** into **physical objects**. For instance, these **physical objects** are useful in the definition of loads over specific lines, on assignaning material properties to the different parts of the region and in the defintion of displacement boundary conditions along specific groups of nodes. The followong figure shows the **physical objects** for the current example.

Note that each such physical object is named by an integer number. In this case physical line **500** defines the top surface of the model where the uniform load is to be applied; physical line **300** corresponds to the lateral surfaces where the horizontal displacement component are resrained; physical line **400** is the bottom surface with restrained vertical displacements and finally physical surfaces **100** and **200** define respectively regions of the model whose material properties are either 1 or 2.

## Converting the gmsh model into SolidsPy text files.

Once the mesh is created the information of nodes, lines, elements and other geometric entities would be associated to the different physical objects facilitating its interpretation by external codes.

In **SolidsPy** a finite element model is described in terms of 4 text files, namely: **nodes.txt**, **eles.txt**, **loads.txt** and **mater.txt**. The creation of these files out of the **gmsh** model is schematized in the following figure.

**Note: The structure of the input files used by SolidsPy is described for a 4-elements simple [square](https://github.com/AppliedMechanics-EAFIT/SolidsPy/blob/master/docs/tutorials/square_example.rst) problem.**

To make this conversion from **gmsh** to **SolidsPy** the user needs to create a problem dependent script based on 4 subroutines available on the pre-processing module from **SolidsPy**. These subroutines interpret the **gmsh** model using the identifiers for the different **phyiscal objects**. The subroutines are summarized like:

* **node_writer()**: The function returns the numpy array **nodes_array[]** ready to be written into the file **nodes.txt** of **SolidsPy**.

* **boundary_conditions()**: Handles each physical line where displacement boundary conditions are imposed.

* **loading()**: The subroutine uses the load magnitudes  along the x and y direction defined by the parameters P_x and P_y and divides the resultant force by the number of nodes pertaining to the **physical line**.

* **ele_writer()**: It is used to write the text file with the element data. The subroutine must be used as many times as physical surfaces there are in the model in order to assign different material properties or different element types.

```python
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import sympy as sym
import solidspy.preprocesor as msh
import solidspy.assemutil as ass
import solidspy.postprocesor as pos
import solidspy.solutil as sol
import meshio
```

**readin()** is also avaialble within **SolidsPy** but here we have added a modified version to upload the input files from the **files** folder in this REPO. The specific files to be read are **Mnodes.txt**, **Meles.txt**, **Mloads.txt** and **Mmater.txt**

```python
def readin():
    nodes    = np.loadtxt('files/' + 'Mnodes.txt', ndmin=2)
    mats     = np.loadtxt('files/' + 'Mmater.txt', ndmin=2)
    elements = np.loadtxt('files/' + 'Meles.txt', ndmin=2, dtype=np.int)
    loads    = np.loadtxt('files/' + 'Mloads.txt', ndmin=2)

    return nodes, mats, elements, loads
```

### Meshing script

In the following block of code we write the meshing script for the bi-material plate example. A graphical description of the script is also given in the figure. The script uses the **gmsh** model given by the file **template.msh** available in the **files** folder of the REPO and writes the text files **Mnodes.txt**, **Meles.txt** and **Mloads.txt** to that same folder.

**Question:**

**Using the figure below, which shows a graphical description of the meshing script used to generate the text files corresponding to the bi-material plate model add comments to each one of the relevant commands in the script.**

```python
#points, cells, point_data, cell_data, field_data = meshio.read("files/template.msh")
mesh = meshio.read("files/template.msh")
points = mesh.points
cells = mesh.cells
point_data = mesh.point_data
cell_data = mesh.cell_data
nodes_array     = msh.node_writer(points , point_data)
nf , els1_array = msh.ele_writer(cells , cell_data , "triangle" , 100 , 3 , 0 , 0)
nini = nf
nf , els2_array = msh.ele_writer(cells , cell_data , "triangle" , 200 , 3 , 1 , nini)
els_array =np.append(els1_array , els2_array , axis = 0)
#
nodes_array = msh.boundary_conditions(cells , cell_data , 300 , nodes_array , -1 , 0)
nodes_array = msh.boundary_conditions(cells , cell_data , 400 , nodes_array , 0 , -1)
cargas      = msh.loading(cells , cell_data , 500 , 0.0 , -2.0)
np.savetxt("files/Meles.txt" , els_array   , fmt="%d")
np.savetxt("files/Mloads.txt", cargas, fmt=("%d", "%.6f", "%.6f"))
np.savetxt("files/Mnodes.txt", nodes_array , fmt=("%d", "%.4f", "%.4f", "%d", "%d"))
```

## Executing the analysis

Once the input files are written in the **files** folder the analaysis can be executed. Here we do this following a step-by-step process where we compute the assembly operator, use it to assemble the global system of equations and solve the system to complete the solution. Alternatively the analysis can also be performed using a single call to the execution subroutine as descripbed in the [REPO.](https://github.com/AppliedMechanics-EAFIT/SolidsPy)

```python
nodes, mats, elements, loads = readin()
DME , IBC , neq = ass.DME(nodes, elements)
KG = ass.assembler(elements, mats, nodes, neq, DME)
RHSG = ass.loadasem(loads, IBC, neq)
UG = sol.static_sol(KG, RHSG)
UC = pos.complete_disp(IBC, nodes, UG)
E_nodes, S_nodes = pos.strain_nodes(nodes , elements, mats, UC)
pos.fields_plot(elements, nodes, UC, E_nodes=E_nodes, S_nodes=S_nodes)
```

### Glossary of terms

**gmsh:** An external and free finite element mesher for two and three-dimensional computational domains.

**Geometric objects:** Hierarchichal geometric entities used in the construction of computational domains within **gmsh**.

**Physical objects:** Specif groups of geometric objects defined within **gmsh** and used to facilitate subsequent finite element operations.

**meshio:** A set of Python subroutines to perform reading/writing operations between several mesh formats including **gmesh.**

**Meshing script:** Problem dependent Python script used to translate a **gmsh** model into a **SolidsPy** model and built in terms of **messhio** functions.

## Class activity.

Repeat the stress analysis for the bi-material plate but now add a third material layer. Start from the file **template.geo** available from the files folder in this Repo and containing the initial geometry.

### References

* Juan Gómez, Nicolás Guarín-Zapata (2018). SolidsPy: 2D-Finite Element Analysis with Python, <https://github.com/AppliedMechanics-EAFIT/SolidsPy>.

```python
from IPython.core.display import HTML
def css_styling():
    styles = open('./nb_style.css', 'r').read()
    return HTML(styles)
css_styling()
```

<link href='http://fonts.googleapis.com/css?family=Fenix' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Source+Code+Pro:300,400' rel='stylesheet' type='text/css'>

```python

```


```python
!pip install discopy
!pip install sympy
!pip install jax
!pip install noisyopt
```

    Requirement already satisfied: discopy in /usr/local/lib/python3.7/dist-packages (0.3.6)
    Requirement already satisfied: matplotlib>=3.1.2 in /usr/local/lib/python3.7/dist-packages (from discopy) (3.2.2)
    Requirement already satisfied: numpy>=1.18.1 in /usr/local/lib/python3.7/dist-packages (from discopy) (1.19.5)
    Requirement already satisfied: networkx>=2.4 in /usr/local/lib/python3.7/dist-packages (from discopy) (2.5.1)
    Requirement already satisfied: pillow>=6.2.1 in /usr/local/lib/python3.7/dist-packages (from discopy) (7.1.2)
    Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=3.1.2->discopy) (1.3.1)
    Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=3.1.2->discopy) (0.10.0)
    Requirement already satisfied: python-dateutil>=2.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=3.1.2->discopy) (2.8.1)
    Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=3.1.2->discopy) (2.4.7)
    Requirement already satisfied: six in /usr/local/lib/python3.7/dist-packages (from cycler>=0.10->matplotlib>=3.1.2->discopy) (1.15.0)
    Requirement already satisfied: decorator<5,>=4.3 in /usr/local/lib/python3.7/dist-packages (from networkx>=2.4->discopy) (4.4.2)
    Requirement already satisfied: sympy in /usr/local/lib/python3.7/dist-packages (1.7.1)
    Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.7/dist-packages (from sympy) (1.2.1)
    Requirement already satisfied: jax in /usr/local/lib/python3.7/dist-packages (0.2.17)
    Requirement already satisfied: numpy>=1.17 in /usr/local/lib/python3.7/dist-packages (from jax) (1.19.5)
    Requirement already satisfied: opt-einsum in /usr/local/lib/python3.7/dist-packages (from jax) (3.3.0)
    Requirement already satisfied: absl-py in /usr/local/lib/python3.7/dist-packages (from jax) (0.12.0)
    Requirement already satisfied: six in /usr/local/lib/python3.7/dist-packages (from absl-py->jax) (1.15.0)
    Requirement already satisfied: noisyopt in /usr/local/lib/python3.7/dist-packages (0.2.2)
    Requirement already satisfied: scipy in /usr/local/lib/python3.7/dist-packages (from noisyopt) (1.4.1)
    Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from noisyopt) (1.19.5)

```python
import jax.numpy as np
from jax import jit
from discopy import Tensor
Tensor.np = np

from sympy import symbols
from discopy import *
from discopy.quantum import *
import requests
```

```python
#******************************************
# Generate your corpus
#******************************************
# sentences can be of the `word1 word2 word3 ...`
# if you are generating the diagrams using templates
# then you may want to consider adding POS tags (N, V, Adj) for each word
# labels should be of the form [0,1] or [1,0] for binary classification

# Words
adjectives  = ['cruel', 'strong', 'weak'] #'smart', 'happy', 'cowardly', 'brave', 'stupid', 'heroic', 'loving']
nouns =['professor', 'doctor', 'politician'] #'fireman', 'policeman', 'scientist', 'lawyer', 'physicist', 'nurse', 'mathematician']
verbs = ['wins', 'loses', 'lies','saves'] #'flees', 'kills', 'saves', 'cries', 'innovates', 'creates', 'believes']

# Verbal connotation
positive  = ['wins', 'saves','innovates', 'creates', 'believes']
negatives = ['loses', 'lies', 'flees', 'kills','cries']

# Sentences generator
sentences = []
labels = []
for adj in adjectives:
  for noun in nouns:
    for verb in verbs:
        sentences.append(adj + ' ' + noun + ' '+ verb)
        if verb in positive:
          labels.append(1)
        else:
          labels.append(0)

train_sentences = sentences[0:12]
dev_sentences   = sentences[12:24]
test_sentences  = sentences[24:]

train_labels = labels[0:12]
dev_labels   = labels[12:24]
test_labels  = labels[24:]

train_labels = np.array(train_labels)
dev_labels   = np.array(dev_labels)
test_labels  = np.array(test_labels)
```

```python
#******************************************
# Generate your diagrams
#******************************************
# TODO generate the DisCoCat diagrams for your corpus. There are 2 options:
# 1. Follow a few fixed templates (N V N, N V Adj N, ...)
# 2. Use the online tool to generate diagrams.
# If you are using the online tool, consider writing a script.

# Suggestion: print out some of your DisCoCat diagrams using
# `grammar.draw` to check that they make sense.

# TODO implement sentence2diagram

def sentence2diagram(sent):
  byte_string = requests.get("https://cqc.pythonanywhere.com/discocat/code?sentence=" + sent)
  processesed_string = ''
  for i in byte_string:
    processesed_string += i.decode('utf-8').replace('\n', '')
  return processesed_string

train_diagrams = [eval(sentence2diagram(s)) for s in train_sentences]
dev_diagrams   = [eval(sentence2diagram(s)) for s in dev_sentences]
test_diagrams  = [eval(sentence2diagram(s)) for s in test_sentences]
```

```python
len(dev_diagrams)
```

    12

```python
###idea 1: use IQP ansantz to create bolier plate ansantz for 3 word sentences
#### idea 2: create custumized ansantz based on sentence diagram specifications
###idea 3: circuit functor
```

```python
#******************************************
# Generate your circuits
#******************************************
# TODO: Implement your own functorial ansatz conversion!
# The `qnlp-tutorial` has some code on this.
# https://discopy.readthedocs.io/en/main/notebooks/qnlp-tutorial.html
# You may also find discopy's `IQPansatz` function useful.

# Suggestion: Draw some DisCoCat diagrams alongside their circuit diagrams
# and compare them

train_circuits = []
test_circuits  = []
val_circuits   = []

alpha = symbols("alpha")
beta  = symbols("beta")
sym_list = [alpha, beta]

for k in range(len(train_sentences)):
    train_circuits.append(IQPansatz(3,[[alpha, beta], [alpha, beta]]))
    test_circuits.append(IQPansatz(3,[[alpha, beta], [alpha, beta]]))
    val_circuits.append(IQPansatz(3,[[alpha, beta], [alpha, beta]]))
```

```python
len(test_circuits)
```

    12

```python
# These imports will come in handy when you want to run
# backend simulations.

# from qiskit import IBMQ
# from pytket.extensions.qiskit import AerBackend, IBMQBackend, IBMQEmulatorBackend
```

```python
#******************************************
# Counting parameters in your model
#******************************************
from sympy import default_sort_key
circuits = train_circuits + val_circuits + test_circuits

# Make sure you sort your symbols as they are returned as a set.

parameters = sorted({
    s for circ in circuits for s in circ.free_symbols
}, key=default_sort_key)
```

```python
def normalise(predicts):
    # apply smoothing to predicts
    predicts = np.abs(predicts - 1e-9)
    predicts = predicts / predicts.sum()
    return predicts

def BCE_loss(predicts, labels):
    cross_entropies = np.sum(labels * np.log2(predicts))
    cost = -1/len(labels) * np.sum(cross_entropies)

    return cost
```

```python
def make_pred_fn(circuits):
    def pred_fn(params):
        predictions = np.array([
            normalise(np.abs(c.lambdify(*parameters)(*params).eval().array))
            for c in circuits
        ])
        return predictions

    return pred_fn
```

```python
train_pred_fn = jit(make_pred_fn(train_circuits))
dev_pred_fn   = jit(make_pred_fn(val_circuits))
test_pred_fn  = jit(make_pred_fn(test_circuits))

import numpy as onp
onp.random.seed(0)

x0 = np.array(onp.random.random(len(parameters)))
print("Performing Just-In-Time compilation, this may take a while...")
train_pred_fn(x0)
dev_pred_fn(x0)
```

    Performing Just-In-Time compilation, this may take a while...

    DeviceArray([[[[[[[0.01854655, 0.01149995],
                      [0.01615188, 0.01149995]],

                     [[0.01884021, 0.013414  ],
                      [0.02163345, 0.013414  ]]],

                    [[[0.01149995, 0.01854655],
                      [0.01149995, 0.01615188]],

                     [[0.013414  , 0.01884021],
                      [0.013414  , 0.02163345]]]],

                   [[[[0.01615188, 0.01149995],
                      [0.01854655, 0.01149995]],

                     [[0.02163345, 0.013414  ],
                      [0.01884021, 0.013414  ]]],

                    [[[0.01149995, 0.01615188],
                      [0.01149995, 0.01854655]],

                     [[0.013414  , 0.02163345],
                      [0.013414  , 0.01884021]]]]],

                  [[[[[0.01884021, 0.013414  ],
                      [0.02163345, 0.013414  ]],

                     [[0.01854655, 0.01149995],
                      [0.01615188, 0.01149995]]],

                    [[[0.013414  , 0.01884021],
                      [0.013414  , 0.02163345]],

                     [[0.01149995, 0.01854655],
                      [0.01149995, 0.01615188]]]],

                   [[[[0.02163345, 0.013414  ],
                      [0.01884021, 0.013414  ]],

                     [[0.01615188, 0.01149995],
                      [0.01854655, 0.01149995]]],

                    [[[0.013414  , 0.02163345],
                      [0.013414  , 0.01884021]],

                     [[0.01149995, 0.01615188],
                      [0.01149995, 0.01854655]]]]]],

                 [[[[[[0.01854655, 0.01149995],
                      [0.01615188, 0.01149995]],

                     [[0.01884021, 0.013414  ],
                      [0.02163345, 0.013414  ]]],

                    [[[0.01149995, 0.01854655],
                      [0.01149995, 0.01615188]],

                     [[0.013414  , 0.01884021],
                      [0.013414  , 0.02163345]]]],

                   [[[[0.01615188, 0.01149995],
                      [0.01854655, 0.01149995]],

                     [[0.02163345, 0.013414  ],
                      [0.01884021, 0.013414  ]]],

                    [[[0.01149995, 0.01615188],
                      [0.01149995, 0.01854655]],

                     [[0.013414  , 0.02163345],
                      [0.013414  , 0.01884021]]]]],

                  [[[[[0.01884021, 0.013414  ],
                      [0.02163345, 0.013414  ]],

                     [[0.01854655, 0.01149995],
                      [0.01615188, 0.01149995]]],

                    [[[0.013414  , 0.01884021],
                      [0.013414  , 0.02163345]],

                     [[0.01149995, 0.01854655],
                      [0.01149995, 0.01615188]]]],

                   [[[[0.02163345, 0.013414  ],
                      [0.01884021, 0.013414  ]],

                     [[0.01615188, 0.01149995],
                      [0.01854655, 0.01149995]]],

                    [[[0.013414  , 0.02163345],
                      [0.013414  , 0.01884021]],

                     [[0.01149995, 0.01615188],
                      [0.01149995, 0.01854655]]]]]],

                 [[[[[[0.01854655, 0.01149995],
                      [0.01615188, 0.01149995]],

                     [[0.01884021, 0.013414  ],
                      [0.02163345, 0.013414  ]]],

                    [[[0.01149995, 0.01854655],
                      [0.01149995, 0.01615188]],

                     [[0.013414  , 0.01884021],
                      [0.013414  , 0.02163345]]]],

                   [[[[0.01615188, 0.01149995],
                      [0.01854655, 0.01149995]],

                     [[0.02163345, 0.013414  ],
                      [0.01884021, 0.013414  ]]],

                    [[[0.01149995, 0.01615188],
                      [0.01149995, 0.01854655]],

                     [[0.013414  , 0.02163345],
                      [0.013414  , 0.01884021]]]]],

                  [[[[[0.01884021, 0.013414  ],
                      [0.02163345, 0.013414  ]],

                     [[0.01854655, 0.01149995],
                      [0.01615188, 0.01149995]]],

                    [[[0.013414  , 0.01884021],
                      [0.013414  , 0.02163345]],

                     [[0.01149995, 0.01854655],
                      [0.01149995, 0.01615188]]]],

                   [[[[0.02163345, 0.013414  ],
                      [0.01884021, 0.013414  ]],

                     [[0.01615188, 0.01149995],
                      [0.01854655, 0.01149995]]],

                    [[[0.013414  , 0.02163345],
                      [0.013414  , 0.01884021]],

                     [[0.01149995, 0.01615188],
                      [0.01149995, 0.01854655]]]]]],

                 ...,

                 [[[[[[0.01854655, 0.01149995],
                      [0.01615188, 0.01149995]],

                     [[0.01884021, 0.013414  ],
                      [0.02163345, 0.013414  ]]],

                    [[[0.01149995, 0.01854655],
                      [0.01149995, 0.01615188]],

                     [[0.013414  , 0.01884021],
                      [0.013414  , 0.02163345]]]],

                   [[[[0.01615188, 0.01149995],
                      [0.01854655, 0.01149995]],

                     [[0.02163345, 0.013414  ],
                      [0.01884021, 0.013414  ]]],

                    [[[0.01149995, 0.01615188],
                      [0.01149995, 0.01854655]],

                     [[0.013414  , 0.02163345],
                      [0.013414  , 0.01884021]]]]],

                  [[[[[0.01884021, 0.013414  ],
                      [0.02163345, 0.013414  ]],

                     [[0.01854655, 0.01149995],
                      [0.01615188, 0.01149995]]],

                    [[[0.013414  , 0.01884021],
                      [0.013414  , 0.02163345]],

                     [[0.01149995, 0.01854655],
                      [0.01149995, 0.01615188]]]],

                   [[[[0.02163345, 0.013414  ],
                      [0.01884021, 0.013414  ]],

                     [[0.01615188, 0.01149995],
                      [0.01854655, 0.01149995]]],

                    [[[0.013414  , 0.02163345],
                      [0.013414  , 0.01884021]],

                     [[0.01149995, 0.01615188],
                      [0.01149995, 0.01854655]]]]]],

                 [[[[[[0.01854655, 0.01149995],
                      [0.01615188, 0.01149995]],

                     [[0.01884021, 0.013414  ],
                      [0.02163345, 0.013414  ]]],

                    [[[0.01149995, 0.01854655],
                      [0.01149995, 0.01615188]],

                     [[0.013414  , 0.01884021],
                      [0.013414  , 0.02163345]]]],

                   [[[[0.01615188, 0.01149995],
                      [0.01854655, 0.01149995]],

                     [[0.02163345, 0.013414  ],
                      [0.01884021, 0.013414  ]]],

                    [[[0.01149995, 0.01615188],
                      [0.01149995, 0.01854655]],

                     [[0.013414  , 0.02163345],
                      [0.013414  , 0.01884021]]]]],

                  [[[[[0.01884021, 0.013414  ],
                      [0.02163345, 0.013414  ]],

                     [[0.01854655, 0.01149995],
                      [0.01615188, 0.01149995]]],

                    [[[0.013414  , 0.01884021],
                      [0.013414  , 0.02163345]],

                     [[0.01149995, 0.01854655],
                      [0.01149995, 0.01615188]]]],

                   [[[[0.02163345, 0.013414  ],
                      [0.01884021, 0.013414  ]],

                     [[0.01615188, 0.01149995],
                      [0.01854655, 0.01149995]]],

                    [[[0.013414  , 0.02163345],
                      [0.013414  , 0.01884021]],

                     [[0.01149995, 0.01615188],
                      [0.01149995, 0.01854655]]]]]],

                 [[[[[[0.01854655, 0.01149995],
                      [0.01615188, 0.01149995]],

                     [[0.01884021, 0.013414  ],
                      [0.02163345, 0.013414  ]]],

                    [[[0.01149995, 0.01854655],
                      [0.01149995, 0.01615188]],

                     [[0.013414  , 0.01884021],
                      [0.013414  , 0.02163345]]]],

                   [[[[0.01615188, 0.01149995],
                      [0.01854655, 0.01149995]],

                     [[0.02163345, 0.013414  ],
                      [0.01884021, 0.013414  ]]],

                    [[[0.01149995, 0.01615188],
                      [0.01149995, 0.01854655]],

                     [[0.013414  , 0.02163345],
                      [0.013414  , 0.01884021]]]]],

                  [[[[[0.01884021, 0.013414  ],
                      [0.02163345, 0.013414  ]],

                     [[0.01854655, 0.01149995],
                      [0.01615188, 0.01149995]]],

                    [[[0.013414  , 0.01884021],
                      [0.013414  , 0.02163345]],

                     [[0.01149995, 0.01854655],
                      [0.01149995, 0.01615188]]]],

                   [[[[0.02163345, 0.013414  ],
                      [0.01884021, 0.013414  ]],

                     [[0.01615188, 0.01149995],
                      [0.01854655, 0.01149995]]],

                    [[[0.013414  , 0.02163345],
                      [0.013414  , 0.01884021]],

                     [[0.01149995, 0.01615188],
                      [0.01149995, 0.01854655]]]]]]], dtype=float32)

```python
def make_cost_fn(pred_fn, labels):
    costs = []
    accs = []
    params_list = []
    def cost_fn(params, **kwargs):
        predictions = pred_fn(params)

        cost = BCE_loss(predictions, labels)
        acc = np.sum(np.round(predictions) == labels) / len(labels) / 2
        costs.append(cost)
        accs.append(acc)
        params_list.append(params)

        return cost

    return cost_fn, costs, accs, params_list
```

```python
# Rerun this cell onwards, so you can reuse the JITed prediction functions

train_cost_fn, train_costs, train_accs, spsa_params = make_cost_fn(train_pred_fn, train_labels)
dev_cost_fn, dev_costs, dev_accs, callback_params = make_cost_fn(dev_pred_fn, dev_labels)
test_cost_fn, test_costs, test_accs, _ = make_cost_fn(test_pred_fn, test_labels)
```

```python
train_costs
```

    []

```python
from noisyopt import minimizeSPSA

minimizeSPSA(train_cost_fn, x0=x0, a=0.13, c=0.1, niter=1, callback=dev_cost_fn)
```

```python
import matplotlib.pylab as plt
fig, ax1 = plt.subplots()
ax1.plot(train_costs[::2], color='#54436B')
ax1.set_ylabel('Cost')
ax1.set_xlabel('Iterations')
ax1.set_title('Training Set')

ax2 = ax1.twinx()
ax2.plot(train_accs[::2], color='#50CB93')
ax2.set_ylabel('Accuracy')
plt.show()
```

```python
fig, ax1 = plt.subplots()
ax1.plot(dev_costs, color='#A03C78')
ax1.set_ylabel('Cost')
ax1.set_xlabel('Iterations')
ax1.set_title('Development Set')

ax2 = ax1.twinx()
ax2.plot(dev_accs, color='#ED8E7C')
ax2.set_ylabel('Accuracy')
plt.show()
```

```python
# when you are happy with the development set results
# run your model on the testing set

# for p in callback_params:
#     test_cost_fn(p)
```

```python
fig, ax1 = plt.subplots()
ax1.plot(test_costs, color='#7C83FD')
ax1.set_ylabel('Cost')
ax1.set_xlabel('Iterations')
ax1.set_title('Testing Set')

ax2 = ax1.twinx()
ax2.plot(test_accs, color='#96BAFF')
ax2.set_ylabel('Accuracy')
plt.show()
```

```python

```


### Writing a genetic algorithm for the ARC dataset just for fun

```python
import numpy as np
import json
from PIL import Image, ImageDraw
from IPython.display import Image as Im
import matplotlib.pyplot as plt
from itertools import permutations, combinations
from sympy.utilities.iterables import multiset_permutations
```

```python
filename = "/Users/aysjajohnson/Desktop/ARC-master/data/training/0ca9ddb6.json"
with open(filename, 'r') as f:
        train1 = json.load(f)
```

```python
colorMap = {0:"black",1:"blue",2:"red", 3:"green",4:"yellow",5:"grey",6:"magenta",7:"orange",8:"cyan",9:"brown"}
```

```python
# displaying a single grid
def DisplayGrid(grid):
    grid = np.asarray(grid)
    nrows = len(grid[:,0])
    ncols = len(grid[0,:])
    height, width = nrows*50, ncols*50
    image = Image.new(size=(width,height),mode='RGB',color=(255,255,255))
    draw = ImageDraw.Draw(image)
    r = 0
    for row in grid:
        c = 0
        for col in row:
            draw.rectangle(xy=[c*50,r*50,(c+1)*50,(r+1)*50], fill=colorMap[np.abs(grid[r][c])])
            c += 1
        r += 1
    for i in range(ncols):
        draw.line([(i+1)*50,0,(i+1)*50,height],fill="grey")
    for i in range(nrows):
        draw.line([0,(i+1)*50,width,(i+1)*50],fill="grey")
    display(image)
```

```python
# displaying two grids side by side
def DisplayGrids(grid1,grid2):
    # Grid 1
    grid1 = np.asarray(grid1)
    nrows = len(grid1[:,0])
    ncols = len(grid1[0,:])
    height, width = nrows*50, ncols*50
    image1 = Image.new(size=(width,height),mode='RGB',color=(255,255,255))
    draw = ImageDraw.Draw(image1)
    r = 0
    for row in grid1:
        c = 0
        for col in row:
            draw.rectangle(xy=[c*50,r*50,(c+1)*50,(r+1)*50], fill=colorMap[np.abs(grid1[r][c])])
            c += 1
        r += 1
    for i in range(ncols):
        draw.line([(i+1)*50,0,(i+1)*50,height],fill="grey")
    for i in range(nrows):
        draw.line([0,(i+1)*50,width,(i+1)*50],fill="grey")

    # Grid 2
    grid2 = np.asarray(grid2)
    nrows = len(grid2[:,0])
    ncols = len(grid2[0,:])
    height, width = nrows*50, ncols*50
    image2 = Image.new(size=(width,height),mode='RGB',color=(255,255,255))
    draw = ImageDraw.Draw(image2)
    r = 0
    for row in grid2:
        c = 0
        for col in row:
            draw.rectangle(xy=[c*50,r*50,(c+1)*50,(r+1)*50], fill=colorMap[np.abs(grid2[r][c])])
            c += 1
        r += 1
    for i in range(ncols):
        draw.line([(i+1)*50,0,(i+1)*50,height],fill="grey")
    for i in range(nrows):
        draw.line([0,(i+1)*50,width,(i+1)*50],fill="grey")

    # Displaying
    fig, ax = plt.subplots(1,2,figsize=(50,50))
    ax[0].imshow(image1)
    ax[0].axis("off")
    ax[1].imshow(image2)
    ax[1].axis("off")
```

```python
numTrain = len(train1["train"])
```

```python
# creating action table
# np array 100,000 by 5, the columns are N, S, E, W, Current
a = [0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,\
    0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9]
# This will give us all the non duplicated permutations, i.e., all possible combinations of states
b = list(multiset_permutations(a,8))
```

```python
lookupTable = np.asarray(b)
print(lookupTable.shape)
```

    (100000000, 8)

```python
# initialize 200 agents
agents = np.zeros((1000000000,200))
for i in range(200):
    agents[:,i] = np.random.randint(0,10,1000000000)
```

```python
def padding(grid):
    grid = np.asarray(grid)
    height = grid.shape[0]
    width = grid.shape[1]
    paddedGrid = np.zeros((height+2,width+2))
    paddedGrid[1:height+1,1:width+1] = grid
    return paddedGrid
```

```python
# evaluation, run the strategy on each square of the grid, with wrarp around, i.e., add one block of black around the whole thing
gridTrain = np.asarray(train1["train"][0]["input"])
gridTest = np.asarray(train1["train"][0]["output"])
DisplayGrids(gridTrain,gridTest)
```

```python
def accuracy(inputGrid,outputGrid):
    acc = 0
    if inputGrid.shape != outputGrid.shape:
        return None
    for i in range(inputGrid.shape[0]):
        for j in range(inputGrid.shape[1]):
            acc += np.abs(inputGrid[i][j]-outputGrid[i][j])
    return acc
```

```python
def viewConv(currentState,gridInput):
    image = Image.new(size=(200,200),mode='RGB',color=(255,255,255))
    draw = ImageDraw.Draw(image)
    draw.rectangle(xy=[50,100,100,150], fill=colorMap[currentState[0]])
    draw.rectangle(xy=[50,0,100,50], fill=colorMap[currentState[1]])
    draw.rectangle(xy=[0,50,50,100], fill=colorMap[currentState[2]])
    draw.rectangle(xy=[100,50,150,100], fill=colorMap[currentState[3]])
    draw.rectangle(xy=[50,50,100,100], fill=colorMap[currentState[4]])
    display(image)
```

```python
def fitness(gridInput, gridOutput, agent):
    output = np.zeros((gridInput.shape[0],gridInput.shape[1]))
    grid = padding(gridInput)
    currentState = [0,0,0,0,0]
    for i in range(1,grid.shape[0]-1):
        for j in range(1,grid.shape[1]-1):
            # south
            currentState[0] = grid[i+1][j]
            # north
            currentState[1] = grid[i-1][j]
            # east
            currentState[2] = grid[i][j-1]
            # west
            currentState[3] = grid[i][j+1]
            # current
            currentState[4] = grid[i][j]
            viewConv(currentState,gridInput)
            index = np.where((lookupTable==currentState).all(axis=1))[0][0]
            action = agent[index]
            print(currentState,action)
            output[i-1,j-1] = action
            DisplayGrid(output)
    acc = accuracy(gridOutput,output)
    return(output,acc)
```

```python
f = fitness(gridTrain, gridTest, agents[:,0])[0]
# DisplayGrid(f)
```

```python
def bestFit(gridInput,gridOutput,agents):
    numAgents = len(agents[0,:])
    fit = np.zeros((200,))
    for i in range(numAgents):
        fit[i]=(fitness(gridInput,gridOutput,agents[:,i])[1])
    return(fit)
```

```python
best = bestFit(gridTrain,gridTest,agents)
```

```python
# Pick the best 20
topAgents = agents[:,best.argsort()[:20]]
DisplayGrid(fitness(gridTrain,gridTest,topAgents[:,0])[0])
def bestAgents(a,b,n):
    return(a[:,b.argsort()[:n]])
```

```python
def newAgent(agent1,agent2):
    newAgent = np.zeros((100000,))
    if np.random.random() < 0.5:
        newAgent[:50000] = agent1[:50000].flatten()
        newAgent[50000:] = agent2[50000:].flatten()
    else:
        newAgent[:50000] = agent2[:50000].flatten()
        newAgent[50000:] = agent1[50000:].flatten()
    return newAgent
```

```python
def reproduction(agents):
    numAgents = len(agents[0,:])
    newAgents = np.zeros((100000,int(numAgents/2)))
    sample = range(numAgents)
    for i in range(int(numAgents/2)):
        agent1 = topAgents[:,np.random.choice(sample,1,replace=False)]
        agent2 = topAgents[:,np.random.choice(sample,1,replace=False)]
        new = newAgent(agent1,agent2)
        newAgents[:,i] = new
    return newAgents
```

```python
evo = reproduction(topAgents)
print(len(evo[0,:]))
```

    10

```python
for i in range(len(evo[0,:])):
    DisplayGrid(fitness(gridTrain,gridTest,evo[:,i])[0])
```

```python
# spawn 10 more mutations for each for a total of 200 and add 100 hundred random ones into the mix
```

```python
def mutation(agent):
    c = 0
    for i in range(len(agent)):
        if np.random.random() < 0.005:
            c+=1
            agent[i] = np.random.choice(range(10),1,replace=True)
    return agent
```

```python
def nextAgents(topAgents):
    agents = np.zeros((100000,200))
    for i in range(10):
        for j in range(10):
            agents[:,i] = mutation(topAgents[:,i])
    for i in range(100,200):
        agents[:,i] = np.random.randint(0,10,100000)
    return agents
```

```python
newBatch = nextAgents(evo)
print(newBatch)
```

    [[1. 0. 1. ... 5. 8. 4.]
     [1. 3. 1. ... 2. 4. 3.]
     [1. 8. 2. ... 0. 5. 9.]
     ...
     [0. 9. 3. ... 7. 8. 2.]
     [2. 6. 7. ... 1. 9. 3.]
     [4. 9. 1. ... 8. 2. 9.]]

```python
# second through n run
bFit = bestFit(gridTrain,gridTest,newBatch)
bAgents = bestAgents(newBatch,bFit,20)
evo = reproduction(bAgents)
for i in range(len(evo[0,:])):
    DisplayGrid(fitness(gridTrain,gridTest,evo[:,i])[0])
newBatch = nextAgents(evo)
```

```python
def evolution(gridInput,gridOutput,agents):
    bFit = bestFit(gridInput,gridOutput,agents)
    bAgents = bestAgents(newBatch,bFit,20)
    evo = reproduction(bAgents)
    for i in range(len(evo[0,:])):
        DisplayGrid(fitness(gridInput,gridOutput,evo[:,i])[0])
    newBatch = nextAgents(evo)
    return(newBatch)
```

```python
def evolutionRecursive(gridInput,gridOutput,bAgents,n):
    if n==1:
        return bAgents
    else:
        bFit = bestFit(gridInput,gridOutput,bAgents)
        bAgents = bestAgents(bAgents,bFit,20)
        evo = reproduction(bAgents)
        for i in range(len(evo[0,:])):
            DisplayGrid(fitness(gridInput,gridOutput,evo[:,i])[0])
        newBatch = nextAgents(evo)
        evolutionRecursive(gridInput,gridOutput,newBatch,n-1)
```

```python
evolutionRecursive(gridTrain,gridTest,newBatch,3)
```

```python

```


```
# default_exp yolov1
```

# YOLO V1 implementation

> YOLO v1.

```
#hide
from nbdev.showdoc import *
```

In this notebook the YOLOv1 will implemented based on the original [paper](https://arxiv.org/pdf/1506.02640v5.pdf)

TODO:
* [ ] Need to rewrite the plot function so to give the name and the probability prediction for each bounding box
* [ ] Get more metrics from the training function (e.g. training and validation losses)
* [ ] Write a function that will plot the training and validation loss as well as the training and validation accuracy
* [ ] Use the model on the videos that I have from towing tank to see how well the algorithm performs
* [ ] Use images/videos with darker light conditions to train and test the model.

# How Yolo works
Yolo is an object detection algorithm and uses features that learned from a cnn network to detect objects. When prerforming object detection we want to correctly identify in the image the objects in the given image. Most of the classic aproaches in the object detection algorithms using the sliding window method where the classifier is run over evenly spaces lacations over the entire image. Such types of algorithms are the Deformable Parts Models (DPM), the R-CNN which uses proposal methods to generate the bounding boxes in the given image and then run the classifier on the proposed bounding boxes. This approch, and particullarly the DPM method is slow and not optimal for real time uses, and the improved version of R-CNN models is gaining some speed by strategically selecting interesting regions and run through them the classifier.

On the other hand YOLO algorithm based on the idea to split the image in a grid, for axample for a given image we can split it in a 3 by 3 grid (**_SxS = 3x3_**) which gives as 9 cells. As the below image shows, the image consists by a 3 by 3 grid with 9 cells, and each cell has 2 bouning boxes (**_B_**) which finally will give the prediction bounding boxe for the object in the image.

[//]:

Figure 1

Generally, the YOLO algorithm has the following steps:

1. Divide the image into cells with an **_SxS_** grid
2. Each cell predicts **_B_** bounding boxes (_A cell is responsible for detecting an object if the object's bounding box is within the cell_
3. Return bounding boxes above a given confidence threshold. _The algorithm will show only the bounding box with the highest probability confidence (e.g. 0.90) and will reject all boxes with less values than this threshold_.

**Note:** In practice will like touse larger values of $S and B$, such as $S = 19$ and $B = 5$ to identify more objects, and each cell will output a prediction with a corresponding bounding box for a given image.

The below image shows the YOLO algorithm's result, which returns the bounding boxes for the detected objects. For the algorithm to perform efficiently needs to be trained sufficiently because with each iteration (epoch), the detection accuracy increases. Also, the bounding boxes can be in more than one cells without any issue, and the detection is performed in the cell where the midpoint of the bounding box belongs.

[//]:

Figure 2

The YOLO object detection algorithm is faster architecture because uses one Convolutional Neural Network (CNN) to run all components in the given image in contrast with the naive sliding window approach where for each image the algorithm (DPM, R-CNN etc) needs to scan it step by step to find the region of interest, the detected objects. The R-CNN for example needs classify around 2000 regions per image which makes the algorithm very time consuming and it's not ideal for real time applications.

The figure below shows how the YOLO model creates an $S x S$ grid in the input image and then for each grid cell creates multiple bounding boxes as well as class probability map, and at the end gives the final predictions of the objects in the image.

[//]:

Figure 3

## How the bouning boxes are encoded in YOLO?
One of the most important aspects of this algorithm is the it builds and specifies the bounding boxes, and the other is the the Loss function. The algorithm uses five components to predict an output:

1. The centre of a bounding box $(b_x b_y)$ relative to the bounds of the grid cell
2. The width $(b_w)$
3. The height $(b_h)$. The width and the height of the entire image.
4. The class of the object $(c)$
5. The prediction confidence $(p_c)$ which is the probability of the existance of an object within the bounding box.

Thus, we, optimally, want one bounding box for each object in the given image and we can be sure that only one object will be predicted for each object by taking the midpoint of the cell that is responsible for outputing that object.

So, each bounding box for each cell will have $[x_1, y_1, x_2, y_2]$ coordinates where in the YOLO algorithm will be $[x, y, w, h]$

* $x$ and $y$ will be the coordinates for object midpoint in cell -> these actually will be between $0 - 1$
* $w$ and $h$ will be the width and the heigth of that object relative to the cell -> $w$ can be _greater_ than 1, if the object is wider than the cell, and $h$ can also be _greater_ than 1, if the object is taller than the cell

The labels will look like the following:

$label_{cell} = [c_1, c_2, ..., c_5, p_c, x, y, w,h]$

where:

* $c_1$ to $c_5$ will be the dataset classes
* $p_c$ probability that there is an object (1 or 0)
* $x, y, w,h$ are the coordinates of the bounding boxes

Predictions will look very similar, but will output two bouning boxes (will specialise to output different bounfding boxes (wide vs tall).

$pred_{cell} = [c_1, c_2, ..., c_5, p_{c_1}, x_1, y_1, w_1, h_1, p_{c_2}, x_2, y_2, w_2, h_2]$

**Note:** A cell can only detect one object, this is also one of the YOLO limitations (we can have finer grid to achieve multiple detections as mentioned above.

This is for every cell and the **target** shape for one image will be $(S, S, 10)$

where:

* $S * S$ is the grid size
* $5$ is for the class predictions, $1$ is for the probability score, and $4$ is for the bouning boxes

The **predictions** shape will be $(S, S, 15)$ where there is and additional probability score and four extra bounding box predictions.

## The model architecture

[//]:

The original YOLO model consists of 24 convolutional layers followed by 2 fully connected layers.
The model accepts 448x448 images and at the first layer has a 7x7 kernel with 64 output filters with stride of 2 (**also need to have a padding of 3 to much the dimensions**), also there is a 2x2 Maxpool Layer with the stride of 2. Simillarly, the rest of the model consists of convolutional layers and Maxpool layers except the last two layers where there are a fully conected layers where the first one takes as and input the convolutional output and make it a linear layer of 4096 feature vector and outputs to the fully connected which is reshaped to become a 7 by 7 by 30 which is the final split size of the image ($S = 7$ which is a $7$ x $7$ grid) with a vector output of 30 (in my case this will be 15).

To help whith the architecture building it will be usefull to pre-determine the architecure configuration:

```python
architecture_config = [
    # Tuple: (kernel_size, num_filters, stride, padding)
    (7, 64, 2, 3),
    "M",    # M stands for the MaxPolling Layer and has stride 2x2 and kernel 2x2
    (3, 192, 1, 1),
    "M",
    (1, 128, 1, 0),
    (3, 256, 1, 1),
    (1, 256, 1, 0),
    (3, 512, 1, 1),
    "M",
    # List of tuples: (kernel_size, num_filters, stride, padding), num_of_repeats
    [(1, 256, 1, 0), (3, 512, 1, 1), 4],
    (1, 512, 1, 0),
    (3, 1024, 1, 1),
    "M",
    [(1, 512, 1, 0), (3, 1024, 1, 1), 2],
    (3, 1024, 1, 1),
    (3, 1024, 2, 1),
    (3, 1024, 1, 1),
    (3, 1024, 1, 1),
]
```

## The Loss Function

The YOLO loss function is the second most important aspect of the algorithm. The basic concept behind all these losses is that are the sum squared error, and if we look at the first part of the loss function is going to be the loss for the box coordinate for the midpoint (taking the $x$ midpoint value and subtractining from the predicted $\hat{x}$ squared). The $\mathbb{1}_{ij}^{obj}$ is the identity function which is calculated when there is an object in the cell, so summurizing there is:

* $\mathbb{1}_{i}^{obj}$ is 1 when there is an object in the cell $i$ otherwise is 0.
* $\mathbb{1}_{ij}^{obj}$ is the $j^{th}$ bounding box prediction for the cell $i$
* $\mathbb{1}_{ij}^{noobj}$ has the same concept with the previous one, except that is 1 when there is no object and 0 when there is an object.

So, to know which bounding box is responsible for outputing that bounding box is by looking at the cell and see which of the predicted bounding boxes has the highest Intersection over Union (IoU) value with the target bouning box. The one with the highest IoU will be the responsible bounding box for the prediction and will be send to the loss function.

\begin{align}
&\lambda_{coord} \sum_{i=0}^{S^2}\sum_{j=0}^B \mathbb{1}_{ij}^{obj}[(x_i-\hat{x}_i)^2 + (y_i-\hat{y}_i)^2 ] \\&+ \lambda_{coord} \sum_{i=0}^{S^2}\sum_{j=0}^B \mathbb{1}_{ij}^{obj}[(\sqrt{w_i}-\sqrt{\hat{w}_i})^2 +(\sqrt{h_i}-\sqrt{\hat{h}_i})^2 ]\\
&+ \sum_{i=0}^{S^2}\sum_{j=0}^B \mathbb{1}_{ij}^{obj}(C_i - \hat{C}_i)^2 + \lambda_{noobj}\sum_{i=0}^{S^2}\sum_{j=0}^B \mathbb{1}_{ij}^{noobj}(C_i - \hat{C}_i)^2 \\
&+ \sum_{i=0}^{S^2} \mathbb{1}_{i}^{obj}\sum_{c \in classes}(p_i(c) - \hat{p}_i(c))^2 \\
\end{align}

# Algorithm Implementation

```
#export

# imports
import os
import pandas as pd
import torch
import torch.nn as nn
import torchvision.transforms as transforms
import torch.optim as optim

from PIL import Image
from tqdm import tqdm
from torch.utils.data import DataLoader
from torch.random import seed
from torch.functional import chain_matmul
from torch.nn.modules import padding
```

```
#export

# Get the correct path for utils.py script
if os.getcwd() == '/media/ioannis/DATA/Documents/Machine_learning/Project/src/yolo_v1':
    print(f"The working direcory is: {os.getcwd()}")
else:
    os.chdir("/media/ioannis/DATA/Documents/Machine_learning/Project/src/yolo_v1")
    print(f"Change to yolo dir: {os.getcwd()}")
```

    Change to yolo dir: /media/ioannis/DATA/Documents/Machine_learning/Project/src/yolo_v1

```
#export

from utils import intersection_over_union
from utils import(
    intersection_over_union,
    non_max_suppression,
    mean_average_precision,
    cellboxes_to_boxes,
    get_bboxes,
    plot_image,
    save_checkpoint,
    load_checkpoint
)
```

### YOLO model architecure

#### Architecture configuration based on YOLO paper

```
#export

architecture_config = [
    (7, 64, 2, 3),
    "M",    # M stands for the MaxPolling Layer and has stride 2x2 and kernel 2x2
    (3, 192, 1, 1),
    "M",
    (1, 128, 1, 0),
    (3, 256, 1, 1),
    (1, 256, 1, 0),
    (3, 512, 1, 1),
    "M",
    [(1, 256, 1, 0), (3, 512, 1, 1), 4],
    (1, 512, 1, 0),
    (3, 1024, 1, 1),
    "M",
    [(1, 512, 1, 0), (3, 1024, 1, 1), 2],
    (3, 1024, 1, 1),
    (3, 1024, 2, 1),
    (3, 1024, 1, 1),
    (3, 1024, 1, 1),
]
```

```
#export

class CNNBlock(nn.Module):
    """
    This CNN block is used to as a blueprint of the conv layers for the YoloV1 model.
    Need to use convolutional layers multiple times, so we'll use the CNNBlock for easy of use.

    Args:
        nn ([type]): [description]
    """
    def __init__(self, in_channels, out_channels, **kwargs):
        super(CNNBlock, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, bias=False, **kwargs)
        self.batchnorm = nn.BatchNorm2d(out_channels)
        self.leakyrelu = nn.LeakyReLU(0.1)

    def forward(self, x):
        x = self.leakyrelu(self.batchnorm(self.conv(x)))
        return x

class YoloV1(nn.Module):
    def __init__(self, in_channels=3, **kwargs):
        super(YoloV1, self).__init__()
        self.architecture = architecture_config
        self.in_channels = in_channels
        self.darknet = self._create_conv_layers(self.architecture)
        self.fcs = self._create_fcs(**kwargs)

    def forward(self, x):
        x = self.darknet(x)
        return self.fcs(torch.flatten(x, start_dim=1))

    def _create_conv_layers(self, architecture):
        layers = []
        in_channels = self.in_channels

        for x in architecture:
            if type(x) == tuple:
                layers += [
                    CNNBlock(
                        in_channels, x[1], kernel_size=x[0], stride=x[2], padding=x[3],
                    )
                ]
                in_channels = x[1]

            elif type(x) == str:
                layers += [nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2))]

            elif type(x) == list:
                conv1 = x[0]
                conv2 = x[1]
                num_repeats = x[2]

                for _ in range(num_repeats):
                    layers += [
                        CNNBlock(
                            in_channels,
                            conv1[1],
                            kernel_size=conv1[0],
                            stride=conv1[2],
                            padding=conv1[3],
                        )
                    ]
                    layers += [
                        CNNBlock(
                            conv1[1],
                            conv2[1],
                            kernel_size=conv2[0],
                            stride=conv2[2],
                            padding=conv2[3],
                        )
                    ]
                    in_channels = conv2[1]

        return nn.Sequential(*layers)

    def _create_fcs(self, split_size, num_boxes, num_classes):
        S, B, C = split_size, num_boxes, num_classes

        return nn.Sequential(
            nn.Flatten(),
            nn.Linear(1024 * S * S, 496),
            nn.Dropout(0.0),
            nn.LeakyReLU(0.1),
            nn.Linear(496, S * S * (C + B * 5)),  # (S, S, 30) where C + B * 5 = 30
        )
```

```
def test(S=7, B=2, C=5):
    """
    A function to test YoloV1 model
    """
    model = YoloV1(split_size=S, num_boxes=B, num_classes=C)
    x = torch.randn((2, 3, 448, 448))
    print(model(x).shape)

test()
```

    torch.Size([2, 735])

```

```


```python
%matplotlib inline
%config InlineBackend.figure_format = 'svg'
```

    time: 167 ms

```python
import scqubits as scq
import numpy as np
```

    time: 380 ms

# Calculations and Visualization

scqubits allows us to predict and visualize the qubit coherence times due to various noise channels.

## Basic noise calculations: noise channels
We start by instantiating a qubit object:

```python
tune_tmon = scq.TunableTransmon(
    EJmax=20.0,
    EC=0.5,
    d=0.00,
    flux=0.1,
    ng=0.3,
    ncut=150
)

print(tune_tmon)
```

    TUNABLETRANSMON
     ———— PARAMETERS ————
    EJmax	: 20.0
    EC	: 0.5
    d	: 0.0
    flux	: 0.1
    ng	: 0.3
    ncut	: 150
    truncated_dim	: None
    Hilbert space dimension	: 301
    time: 1.03 ms

We can check what noise channels are supported by this qubit by calling

```python
tune_tmon.supported_noise_channels()
```

    ['tphi_1_over_f_flux',
     'tphi_1_over_f_cc',
     'tphi_1_over_f_ng',
     't1_capacitive_loss',
     't1_flux_bias_line',
     't1_charge_impedance']

    time: 2.8 ms

Any of the above listed channels corresponds to a method that can be called directly. For example, to calculate the pure-dephasing time $T_{\phi}$ due to $1/f$ flux noise (with its default parmeters), we execute:

```python
tune_tmon.tphi_1_over_f_flux()
```

    8088.317057546061

    time: 7.85 ms

The above result is given in inverse frequncy units. The default frequency units in scqubits are GHz. One can check the currently active unit setting by calling

```python
scq.get_units()
```

    'GHz'

    time: 1.55 ms

Given the setting to GHz, the units of time are 1/GHz=$ns$. Accordingly, the above dephasing time is given in $ns$.

Instad of times all the noise functions can also return rates - this is done by passing the ``get_rate=True`` argument.
Each of the noise methods also takes a variety of other arguments, allowing one to specify information ranging from what energy levels should be used in the calculations, to various noise strenghts, and noise operators - see specific method documentations for details.

As an example of some of the non-default options, we consider a $T_1$ noise process due to direct coupling of the qubit to a charge line. The effects of such a noise channel can be calculated using a method called ``t1_charge_impedance``. For example, suppose we wish the calculate the rate for transitions between the 3rd and 1st energy levels, assuming a standard transmission line at a temperature of T=100mK. To obtain the resulting rate, we execute:

```python
tune_tmon.t1_charge_impedance(i=3, j=1, T=0.100, get_rate=True, total=False)
```

    5.615534273392123e-05

    time: 6.74 ms

The option ``total=False`` in the above code signals that only the rate from level 3 to level 1 should be calculated, without inclusion of the upward (excitation) rate. The contribution from excitation processes is included by passing the option ``total=True`` (which is also the default for all the methods that consider $T_1$ processes).

## Effective noise calculations
scqubits can also approximate effectivive noise, which is calculated by considering total contributions from a variety of noise chennels (that can be given as an argument). In the case of a depolarization noise, the effective noise is obtained from
\begin{equation}
\frac{1}{T_{1}^{\rm eff}} = \sum_k \frac{1}{T_{1}^{k}},
\end{equation}

where the sum runs over all the noise channels that the user wants included. By default those correpond to the the list returned by the `effective_noise_channels` method for each qubit. A differnt list of noise channels can be also provided as an argument by the user.

Simliarly, users can calculate effective depahsing, which includes contributions from both pure dephasing, as well as depolarization channels. Such a $T_{2}$ time is defined as

\begin{equation}
\frac{1}{T_{2}^{\rm eff}} = \sum_k \frac{1}{T_{\phi}^{k}} +  \frac{1}{2} \sum_j \frac{1}{T_{1}^{j}},
\end{equation}

where $k$ ($j$) run over the relevant pure dephasing (depolariztion) channels that can contribute to the effective noise.

To start with, let us see what noise channels are included in the default effective noise calculations in a `TunableTransmon` qubit:

```python
tune_tmon.effective_noise_channels()
```

    ['tphi_1_over_f_flux',
     'tphi_1_over_f_cc',
     'tphi_1_over_f_ng',
     't1_capacitive_loss',
     't1_flux_bias_line']

    time: 1.63 ms

Note that for some qubits, such as the Transmon, this list is different from all the `supported_noise_channels`, which in this case, is

```python
tune_tmon.supported_noise_channels()
```

    ['tphi_1_over_f_flux',
     'tphi_1_over_f_cc',
     'tphi_1_over_f_ng',
     't1_capacitive_loss',
     't1_flux_bias_line',
     't1_charge_impedance']

    time: 1.65 ms

As shown above the `supported_noise_channels` list contains an extra noise channel `t1_charge_impedance`. Since in most implementations, charge lines are not directly coupled to transmon qubits, this channel is by default not included in the effective noise calculations.

Let us see what the effecitve $T_{1}$ time is

```python
tune_tmon.t1_effective()
```

    15644.923932573467

    time: 7.19 ms

As well as the effective $T_{2}$

```python
tune_tmon.t2_effective()
```

    6341.746362164173

    time: 8.14 ms

Users can also specify both what channels should be included in effective noise calculations, as well as what the various parameters should be.
For example, to specify a smaller list of channels that we want to consdier, we can explicitly pass a channel list. We can also pass a common list of options as a dictionary, via the `common_noise_options` parameter. In the example below, we set the temperature to $T=0.050$K. The effects of `t1_charge_impedance` noise channel, are very damaging and dominant, as can be clearly seen by a small effective $T_{1}^{\rm eff}$ time.

```python
tune_tmon.t1_effective(noise_channels=['t1_charge_impedance', 't1_flux_bias_line'],
                       common_noise_options=dict(T=0.050))
```

    0.195010281086235

    time: 7.23 ms

Along with `common_noise_options`, channel-specific noise options can also be provided. This is done by replacing the a noise channel name with a tuple of the form `(name, noise_options)` where `noise_options` is a dictionary.
In the example below, we calculate an effective $T_{2}^{\rm eff}$, with a non-default value of $1/f$ flux noise strength `A_flux` that internally gets passed to the qubit's `tphi_1_over_f_flux` method.

```python
tune_tmon.t2_effective(noise_channels=['t1_flux_bias_line','t1_capacitive_loss',
                                      ('tphi_1_over_f_flux', dict(A_noise=3e-6))],
                       common_noise_options=dict(T=0.050))
```

    2482.0715515758156

    time: 7.79 ms

## Visualization of coherence times

A common way to understand and visualize how noise affects a given qubit, is to plot decoherence times as a function of one of the external parameters, such as the flux or charge offset. (Alternatively, it may be useful to consider how decoherence properties change as we vary one or more of the qubit's core energy parameters, such as $E_J$ or $E_C$).    Each noisy qubit provides a flexible method called ``plot_noise``, which facilitates this functionality. In the following, we give examples of some of this method's capabilities.

To provide an overview of decoherence properties on, say, flux, the effect of all supported noise channels can be visualized one by one. (The calculated and plotted data are expressed in the currently set units, by default $ns$ and GHz, respectively.)

```python
tune_tmon.plot_coherence_vs_paramvals(param_name='flux',
                                      param_vals=np.linspace(-0.5, 0.5, 100));
```

    HBox(children=(FloatProgress(value=0.0, description='Spectral data', style=ProgressStyle(description_width='in…

    time: 1.73 s

We can easily re-express the results in different time units by using the ``scale`` argument, for example ``scale=1e-3`` to convert times to $\mu s$. (Alternatively, before creating the qubit object, we could have changed the units to `MHz` via the ``set_units`` function.)

Instead of including all supported noise channels, we can also choose a few select ones. This is achieved by providing a list of specific noise channels that should be plotted. The plots can be customized either by passing plot options directly to the ``plot_coherence_vs_paramvals`` method, or by manipulating the properties of the matplotlib ``Axes`` object returned by ``plot_coherence_vs_paramvals``, as shown below.

```python
fig, ax = tune_tmon.plot_coherence_vs_paramvals(
    param_name='flux',
    param_vals=np.linspace(-0.5, 0.5, 100),
    noise_channels=['t1_charge_impedance', 't1_capacitive_loss'],
    scale=1e-3,
    ylabel=r"$\mu s$",
    color='black',
    ylim=(None, 1e3)
)

ax[1].set_title(r"$t_1$ from capacitive loss");   # customize the title
```

    HBox(children=(FloatProgress(value=0.0, description='Spectral data', style=ProgressStyle(description_width='in…

    time: 621 ms

For additional control, we may choose to calculate coherences with non-default options for all the noise channels we want to plot. This can be done by passing a `common_noise_options` argument to `plot_coherence_vs_paramvals`, which contains a dictionary of option/value pairs. We can also choose different noise-specific options or each noise channel. This is possible by supplying the `noise_channels` argument with tuples containting (1) a noise-channel name, and (2) a dictionary with options that are internally used to calculate the noise. In the example below, we set a non-default temperature for all the provided noise channels to 100 $mK$, but also choose a non-default energy levels 3 and 2 for the ``t1_capacitive_loss`` noise channel calculation (instead of default 1 and 0).

```python
tune_tmon.plot_coherence_vs_paramvals(
                     param_name='flux',
                     param_vals=np.linspace(-0.5, 0.5, 100),
                     noise_channels = ['tphi_1_over_f_flux',
                                      ('t1_capacitive_loss', dict(i=3, j=2))],
                     common_noise_options=dict(T=0.100),
                     scale=1e-3,
                     ylabel=r"$\mu s$",
                     ylim=(None, 1e3)
                    );
```

    HBox(children=(FloatProgress(value=0.0, description='Spectral data', style=ProgressStyle(description_width='in…

    time: 843 ms

## Visualization of effective noise

scqubits also provides methods to easily visualize how the effective $T_{1}$ and $T_{2}$ times vary as system parameters change. This is done via `plot_t1_effective_vs_paramvals` and `plot_t2_effective_vs_paramvals` methods.
These work in a similar way to `plot_coherence_vs_paramvals`, except now if `noise_channels` list is passed as an argument, it now is used to specify which noise channels are used to calculate the effective noise.

As the first example, let us explore how the effective $T_{1}^{\rm eff}$ noise of our tunable tranmon qubit varies as we change flux. Here we assume non-default temperature, as well as level transition, by passing an appropriate `common_noise_options` dictionary.

```python
tune_tmon.plot_t1_effective_vs_paramvals(param_name='flux',
                            param_vals=np.linspace(-0.5, 0.5, 100),
                            common_noise_options=dict(T=0.050, i=2, j=0));
```

    HBox(children=(FloatProgress(value=0.0, description='Spectral data', style=ProgressStyle(description_width='in…

    time: 576 ms

We can also consider a more complicated example $T_{2}^{\rm eff}$ example, with a custom list of channels, and with custom plotting options.

```python
tune_tmon.plot_t2_effective_vs_paramvals(param_name='flux',
                            param_vals=np.linspace(-0.5, 0.5, 100),
                            noise_channels=['t1_capacitive_loss', 't1_flux_bias_line',
                                            ('tphi_1_over_f_flux', dict(A_flux=3e-6))],
                            common_noise_options=dict(T=0.050),
                            scale=1e-3,
                            ylabel=r"$\mu s$",
                            xlabel=r"$\Phi_x/\Phi_0$",
                            title=r"Effective $T_{2}$",
                            grid='red'
                            );
```

    HBox(children=(FloatProgress(value=0.0, description='Spectral data', style=ProgressStyle(description_width='in…

    time: 617 ms

For more details on method signatures, see the API documentation [here][apilink].

[apilink]: https://scqubits.readthedocs.io/en/latest/api-doc/apidoc.html