# Project 2, The restricted Boltzmannn machine applied to the quantum many body problem. Deadline June 1, Spring 2021

**[Computational Physics II FYS4411/FYS9411](http://www.uio.no/studier/emner/matnat/fys/FYS4411/index-eng.html)**, Department of Physics, University of Oslo, Norway

Date: **Apr 12, 2021**

Copyright 1999-2021, [Computational Physics II FYS4411/FYS9411](http://www.uio.no/studier/emner/matnat/fys/FYS4411/index-eng.html). Released under CC Attribution-NonCommercial 4.0 license

## Introduction

The idea of representing the wave function with
a restricted Boltzmann machine (RBM) was presented recently by [G. Carleo and M. Troyer, Science **355**, Issue 6325, pp. 602-606 (2017)](http://science.sciencemag.org/content/355/6325/602). They
named such a wave function/network a \textit{neural network quantum
state} (NQS). In their article they apply it to the quantum mechanical
spin lattice systems of the Ising model and Heisenberg model, with
encouraging results. To further test the applicability of RBM's to
quantum mechanics we will in this project apply it to a system of
two interacting electrons (or bosons) confined to move in a harmonic oscillator trap.

## Theoretical background and description of the physical system

We consider a system of electrons confined in a pure two-dimensional
isotropic harmonic oscillator potential, with an idealized  total Hamiltonian given by

$$
\begin{equation}
\label{eq:finalH} \tag{1}
\hat{H}=\sum_{i=1}^{N} \left(  -\frac{1}{2} \nabla_i^2 + \frac{1}{2} \omega^2r_i^2  \right)+\sum_{i<j}\frac{1}{r_{ij}},
\end{equation}
$$

where natural units ($\hbar=c=e=m_e=1$) are used and all energies are in so-called atomic units a.u. We will study systems of many electrons $N$ as functions of the oscillator frequency  $\omega$ using the above Hamiltonian.  The Hamiltonian includes a standard harmonic oscillator part

$$
\hat{H}_0=\sum_{i=1}^{N} \left(  -\frac{1}{2} \nabla_i^2 + \frac{1}{2} \omega^2r_i^2  \right),
$$

and the repulsive interaction between two electrons given by

$$
\hat{H}_1=\sum_{i<j}\frac{1}{r_{ij}},
$$

with the distance between electrons given by $r_{ij}=\vert \boldsymbol{r}_1-\boldsymbol{r}_2\vert$. We define the
modulus of the positions of the electrons (for a given electron $i$) as $r_i = \sqrt{r_{i_x}^2+r_{i_y}^2}$.

In this project  we will deal only with a system of
two electrons in a quantum dot with a frequency of $\hbar\omega = 1$.
The reason for this is that we have exact closed form expressions
for the ground state energy from Taut's work for selected values of $\omega$,
see M. Taut, Phys. Rev. A \textbf{48}, 3561 (1993).
The energy is given by $3$ a.u.  (atomic units) when the interaction between the electrons is included. We can however easily extend our system to say interacting bosons as in project 1.

If only the harmonic oscillator part of the Hamiltonian is included,
the so-called unperturbed part,

$$
\hat{H}_0=\sum_{i=1}^{N} \left(  -\frac{1}{2} \nabla_i^2 + \frac{1}{2} \omega^2r_i^2  \right),
$$

the energy is $2$ a.u.
The wave function for one electron in an oscillator potential in two dimensions is

$$
\phi_{n_x,n_y}(x,y) = A H_{n_x}(\sqrt{\omega}x)H_{n_y}(\sqrt{\omega}y)\exp{(-\omega(x^2+y^2)/2}.
$$

The functions $H_{n_x}(\sqrt{\omega}x)$ are so-called Hermite polynomials  while $A$ is a normalization constant.
For the lowest-lying state we have $n_x=n_y=0$ and an energy $\epsilon_{n_x,n_y}=\omega(n_x+n_y+1) = \omega$.
Convince yourself that the lowest-lying energy for the two-electron system  is simply $2\omega$.

The unperturbed wave function for the ground state of the two-electron system is given by

$$
\Phi(\boldsymbol{r}_1,\boldsymbol{r}_2) = C\exp{\left(-\omega(r_1^2+r_2^2)/2\right)},
$$

with $C$ being a normalization constant and $r_i = \sqrt{r_{i_x}^2+r_{i_y}^2}$. Note that the vector $\boldsymbol{r}_i$
refer to the $x$ and $y$ position for a given particle.
What is the total spin of this wave function? Find arguments for why the ground state should have
this specific total spin.

### Representing the wave function with a neural network

Our neural network of choice is the restricted Boltzmann machine. It
is a two layer net where one is called the layer of visible nodes and
the other the layer of hidden nodes. It is called restricted because
there are no connections between nodes in the same layer. Meaning
there's only a connection between two nodes if one is visible and the
other hidden. These type of networks constitute the building blocks of
the deep belief networks. The RBM is a
generative network, meaning that the idea is for it to learn a
\textit{probability distribution}. Thus the network does not produce
an output directly, but a probability distribution from which we can
generate an output. In our case this distribution corresponds to the
wave function and the output we wish to generate are the positions
taken by the particles in our system.

Neural networks are referred to as falling
under either supervised or unsupervised learning. Here we are not
working with training data, thus it is not supervised. It's rather
called reinforcement learning. From the variational principle we know
that the NQS wave fucntion represents the ground state once the
quantum mechanical energy is minimized. This information is used to
optimize the weights and biases of the network.

For more information and practical guides to the RBM, check out the links in the literature section.

When working with the restricted Boltzmann machine we are given the joint probability distribution between the hidden and visible nodes.

### Restricted Boltzmann Machine (RBM)

The joint probability distribution is defined as

$$
\begin{equation}
	F_{rbm}(\mathbf{X},\mathbf{H}) = \frac{1}{Z} e^{-\frac{1}{T_0}E(\mathbf{X},\mathbf{H})}
\label{_auto1} \tag{2}
\end{equation}
$$

where $Z$ is the partition function/normalization constant

$$
\begin{equation}
	Z = \int \int \frac{1}{Z} e^{-\frac{1}{T_0}E(\mathbf{x},\mathbf{h})} d\mathbf{x} d\mathbf{h}
\label{_auto2} \tag{3}
\end{equation}
$$

It is common to ignore $T_0$ by setting it to one. Here $E$ is known
as the energy of a configuration of the nodes. Do not confuse this
with the energy of the quantum mechanical system. Here it is a
function which gives the specifics of the relation between the hidden
and visible nodes. Different versions of RBMs will implement the
energy function differently.

### Gaussian-Binary RBM

The original and most common version of an RBM is called "binary-binary", meaning both visible and hidden nodes only take on binary values. In our case we wish to model continuous values (positions), thus the visible nodes should be continuous. We therefore choose an RBM called "Gaussian-binary".

$$
\begin{equation}
	E(\mathbf{X}, \mathbf{H}) = \sum_i^M \frac{(X_i - a_i)^2}{2\sigma_i^2} - \sum_j^N b_j H_j - \sum_{i,j}^{M,N} \frac{X_i w_{ij} H_j}{\sigma_i^2}
\label{_auto3} \tag{4}
\end{equation}
$$

If $\sigma_i = \sigma$ then

$$
\begin{equation}
	E(\mathbf{X}, \mathbf{H})= \frac{||\mathbf{X} - \mathbf{a}||^2}{2\sigma^2} - \mathbf{b}^T \mathbf{H} - \frac{\mathbf{X}^T \mathbf{W} \mathbf{H}}{\sigma^2}
\label{_auto4} \tag{5}
\end{equation}
$$

Here $\mathbf{X}$ are the visible nodes (the position coordinates), $\mathbf{H}$ are the hidden nodes, $\mathbf{a}$ are the visible biases, $\mathbf{b}$ are the hidden biases and $\mathbf{W}$ is a matrix containing the weights characterizing the connection of each visible node to a hidden node.

### The Wave Function

To find the marginal probability $F_{rbm}(X)$ we set:

$$
\begin{equation}
	F_{rbm}(\mathbf{X}) = \sum_\mathbf{h} F_{rbm}(\mathbf{X}, \mathbf{h})
\label{_auto5} \tag{6}
\end{equation}
$$

$$
\begin{equation}
				= \frac{1}{Z}\sum_\mathbf{h} e^{-E(\mathbf{X}, \mathbf{h})}
\label{_auto6} \tag{7}
\end{equation}
$$

This is used to represent the wave function:

$$
\begin{equation}
\Psi (\mathbf{X}) = F_{rbm}(\mathbf{X})
\label{_auto7} \tag{8}
\end{equation}
$$

$$
\begin{equation}
= \frac{1}{Z}\sum_{\{h_j\}} e^{-E(\mathbf{X}, \mathbf{h})}
\label{_auto8} \tag{9}
\end{equation}
$$

$$
\begin{equation}
= \frac{1}{Z} \sum_{\{h_j\}} e^{-\sum_i^M \frac{(X_i - a_i)^2}{2\sigma^2} + \sum_j^N b_j h_j + \sum_{i,j}^{M,N} \frac{X_i w_{ij} h_j}{\sigma^2}}
\label{_auto9} \tag{10}
\end{equation}
$$

$$
\begin{equation}
= \frac{1}{Z} e^{-\sum_i^M \frac{(X_i - a_i)^2}{2\sigma^2}} \prod_j^N (1 + e^{b_j + \sum_i^M \frac{X_i w_{ij}}{\sigma^2}})
\label{_auto10} \tag{11}
\end{equation}
$$

$$
\begin{equation}
\label{_auto11} \tag{12}
\end{equation}
$$

### The Monte Carlo procedure

In many aspects, the procedure of optimizing the NQS wave function
will be very similar to the VMC method in project one. However, it
requires a heavier emphasis on the minimization process. Whereas in
project one you only had one or two parameters to optimize and could
even determine them analytically, in this situation the biases and
weights quickly add up to a high number of parameters to optimize, and
it's hard, if possible at all, to determine them analytically. Thus
minimizing the quantum mechanical energy and optimizing the parameters
is important from the beginning. Still, the structure of the process
is similar. You set up an initial guess of the NQS wave function by
giving the weights and biases random, preferably small values. The
process then follows the same structure as the VMC method.

### Project 2 a): Analytical expressions

Once again you should start by analytically determining the local energy, given by

$$
\begin{equation}
	E_L = \frac{1}{\Psi} \hat{\mathbf{H}} \Psi
\label{_auto12} \tag{13}
\end{equation}
$$

using the NQS $\Psi$ and the Hamiltonian as defined earlier.

If your minimization method of choice is for example stochastic gradient descent (when using this method for neural network training, the step size is often referred to as the learning rate), you will also need the gradient of the local energy with respect to the RBM parameters $\mathbf{\alpha}$ ($\mathbf{a}$, $\mathbf{b}$ and $\mathbf{W}$). It is given by

$$
\begin{equation}
	G_i = \frac{\partial \langle E_L \rangle}{\partial \alpha_i}
	= 2(\langle E_L \frac{1}{\Psi}\frac{\partial \Psi}{\partial \alpha_i} \rangle - \langle E_L \rangle \langle \frac{1}{\Psi}\frac{\partial \Psi}{\partial \alpha_i} \rangle )
\label{_auto13} \tag{14}
\end{equation}
$$

where $\alpha_i = a_1,...,a_M,b_1,...,b_N,w_{11},...,w_{MN}$.

In addition to $E_L$ then you will also need to find the expression for
$\frac{\partial \Psi}{\partial \alpha_i}$.

You see here that the visible nodes (the position coordinates) and the corresponding visible biases are vectors of length $M$. The hidden nodes and the corresponding hidden biases are vectors of length $N$. The weight matrix is of size $M\times N$. While the number of hidden nodes (that is, $N$) is your own choice and should be experimented with, the number of visible nodes ($M$) should correspond to the number of particles ($P$) and the number of dimensions ($D$) in the system, that is $M = P \cdot D$.

### Project 2 b): Initial code

Now implement the code. The structure of the code (how you organize
your classes) can (probably) imitate what you did in project 1.  Use
standard Metropolis sampling and ignore interaction. This means
excluding the repulsive interaction from your Hamiltonian and local
energy calculations. In this case the analytically correct energy of
the system is given by $E=\frac{1}{2}P\cdot D$.  Optimize the NQS and
compute the energy of 1 particle in 1D with 2 hidden units as
accurately as you can. Experiment with the learning rate. What
precision do you achieve?  Eventually you may also experiment with
changing the number of hidden units. Document your findings.

### Project 2 c): Importance sampling

Add importance sampling to improve your method. Document the results and compare them to the brute force method.

### Project 2 d): Statistical analysis

Include a proper statistical analysis by use of the blocking method for your results.

### Project 2 e): From Metropolis to Gibbs sampling

For a system such as the one we currently study, where we know that
the wave function is positive definite, we may use a sampling method
called Gibbs sampling. In this case we represent the wave function as
$\Psi(x) = \sqrt(F_{rbm})$ rather than $\Psi(x) = F_{rbm}(x)$.

In this method we sample from the joint probability of $\mathbf{x}$ and
$\mathbf{h}$, in the form of a two step sampling process. The samples $\mathbf{x}$
by themselves then model the probability density $|\Psi(\mathbf{x})|^2$ as we
wish.  The updated samples are generated according to the conditional
probabilities $P(X_i|\mathbf{h})$ and $P(H_j|\mathbf{x})$ respectively and accepted
with the probability of $1$.

$$
\begin{equation}
	P(X_i|\mathbf{h}) = \mathcal{N}(X_i; a_i+\mathbf{w}_{i*} \mathbf{h}, \sigma^2)
\label{_auto14} \tag{15}
\end{equation}
$$

$$
\begin{equation}
	P(\mathbf{X}|\mathbf{h}) = \prod_i^M \mathcal{N}(X_i; a_i+\mathbf{w}_{i*} \mathbf{h}, \sigma^2)
\label{_auto15} \tag{16}
\end{equation}
$$

$$
\begin{equation}
	= \mathcal{N} (\mathbf{X}; \mathbf{a}+\mathbf{W}\mathbf{h},\sigma^2)
\label{_auto16} \tag{17}
\end{equation}
$$

and

$$
\begin{equation}
	P(H_j|\mathbf{x}) = \frac{e^{(b_j+\frac{\mathbf{x}^T\mathbf{w}_{*j}}{\sigma^2})H_j}}{\sum_{h_j}e^{(b_j+\frac{\mathbf{x}^T\mathbf{w}_{*j}}{\sigma^2})h_j}}
\label{_auto17} \tag{18}
\end{equation}
$$

$$
\begin{equation}
	= \frac{e^{(b_j+\frac{\mathbf{v}^T\mathbf{w}_{*j}}{\sigma^2})H_j}}{1+e^{b_j+\frac{\mathbf{x}^T\mathbf{w}_{*j}}{\sigma^2}}}
\label{_auto18} \tag{19}
\end{equation}
$$

$$
\begin{equation}
	P(\mathbf{H}|\mathbf{x}) = \prod_j^N \frac{e^{(b_j+\frac{\mathbf{x}^T\mathbf{w}_{*j}}{\sigma^2})H_j}}{1+e^{b_j+\frac{\mathbf{x}^T\mathbf{w}_{*j}}{\sigma^2}}}
\label{_auto19} \tag{20}
\end{equation}
$$

$$
\begin{equation}
\label{_auto20} \tag{21}
\end{equation}
$$

Meaning

$$
\begin{equation}
	P(H_j=1|\mathbf{x}) = \frac{e^{b_j+\frac{\mathbf{x}^T\mathbf{w}_{*j}}{\sigma^2}}}{1+e^{b_j+\frac{\mathbf{x}^T\mathbf{w}_{*j}}{\sigma^2}}}
\label{_auto21} \tag{22}
\end{equation}
$$

$$
\begin{equation}
	= \frac{1}{1+e^{-b_j-\frac{\mathbf{x}^T\mathbf{w}_{*j}}{\sigma^2}}}
\label{_auto22} \tag{23}
\end{equation}
$$

$$
\begin{equation}
	P(H_j=0|\mathbf{x}) = \frac{1}{1+e^{b_j+\frac{\mathbf{x}^T\mathbf{w}_{*j}}{\sigma^2}}}
\label{_auto23} \tag{24}
\end{equation}
$$

Recalculate the values needed for the local energy and its gradient with the form of the wave function given by $\Psi(x) = \sqrt(F_{rbm})$.

\paragraph{Project 2 f): Gibbs sampling}
Implement a Gibbs sampling method as an alternative to the Metropolis sampling. Reproduce the energy of the same system as in b), document your results. Again experiment with the learning rate and the number of hidden values. Experiment with changing the $\sigma$ of the neural netwrok. Comment. Eventually you may increase the number of particles and dimensions. Do the produced energy compare well with the analytical values?

### Project 2 g): Interaction

Include the interaction. Remember that for the interacting case we
have an analytical answer when we look at two particles in two
dimensions (the energy shoud be 3 a.u.). Also, in the interacting case
it does not make sense to look at more than two particles since we are
currently looking at fermions and have not accounted for the Pauli
exclusion principle.  As before, experiment with the learning rate and
number of hidden values and document how well the network reproduces
the analytical value.

### Literature

1. [M. Taut, Phys. Rev. A **48**, 3561 - 3566 (1993)](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.48.3561)

2. [G. Carleo and M. Troyer, Science **355**, Issue 6325, pp. 602-606 (2017)](http://science.sciencemag.org/content/355/6325/602)

3. [Restricted Boltzmann machines in quantum physics](https://www.nature.com/articles/s41567-019-0545-1)

4. [A Practical Guide to Training Restricted Boltzmann Machines](https://www.cs.toronto.edu/~hinton/absps/guideTR.pdf)

## Introduction to numerical projects

Here follows a brief recipe and recommendation on how to write a report for each
project.

  * Give a short description of the nature of the problem and the eventual  numerical methods you have used.

  * Describe the algorithm you have used and/or developed. Here you may find it convenient to use pseudocoding. In many cases you can describe the algorithm in the program itself.

  * Include the source code of your program. Comment your program properly.

  * If possible, try to find analytic solutions, or known limits in order to test your program when developing the code.

  * Include your results either in figure form or in a table. Remember to        label your results. All tables and figures should have relevant captions        and labels on the axes.

  * Try to evaluate the reliabilty and numerical stability/precision of your results. If possible, include a qualitative and/or quantitative discussion of the numerical stability, eventual loss of precision etc.

  * Try to give an interpretation of you results in your answers to  the problems.

  * Critique: if possible include your comments and reflections about the  exercise, whether you felt you learnt something, ideas for improvements and  other thoughts you've made when solving the exercise. We wish to keep this course at the interactive level and your comments can help us improve it.

  * Try to establish a practice where you log your work at the  computerlab. You may find such a logbook very handy at later stages in your work, especially when you don't properly remember  what a previous test version  of your program did. Here you could also record  the time spent on solving the exercise, various algorithms you may have tested or other topics which you feel worthy of mentioning.

## Format for electronic delivery of report and programs

The preferred format for the report is a PDF file. You can also use DOC or postscript formats or as an ipython notebook file.  As programming language we prefer that you choose between C/C++, Fortran2008 or Python. The following prescription should be followed when preparing the report:

  * Use canvas to hand in your projects, log in  at  <http://canvas.uio.no> with your normal UiO username and password.

  * Upload **only** the report file!  For the source code file(s) you have developed please provide us with your link to your github domain.  The report file should include all of your discussions and a list of the codes you have developed.  The full version of the codes should be in your github repository.

  * In your github repository, please include a folder which contains selected results. These can be in the form of output from your code for a selected set of runs and input parameters.

  * Still in your github make a folder where you place your codes.

  * In this and all later projects, you should include tests (for example unit tests) of your code(s).

  * Comments  from us on your projects, approval or not, corrections to be made  etc can be found under your Devilry domain and are only visible to you and the teachers of the course.

Finally,
we encourage you to work two and two together. Optimal working groups consist of
2-3 students. You can then hand in a common report.


# CHEM 1000 - Spring 2022
Prof. Geoffrey Hutchison, University of Pittsburgh

## Recitation 1 - Python Basics

The best way to learn coding or a new programming language is to do something useful. Not only will you have more motivation, but the time invested for the skill will be repaid in (hopefully) doing that useful task.

**Learning Objectives**

By the end of this recitation / notebook, you should be able to:
- Read and write basic Python
- Assign variables in Python
- Call Python functions and methods
- Load Python modules with `import`
- Understand simple `for` and `while` loops

**Attribution**

Much of this recitation has been adapted from [Software Carpentry - Scientific Python Basics](https://bids.github.io/2016-01-14-berkeley/python/00-python-intro.html) and [Mathematical Python by Patrick Wills](https://github.com/patrickwalls/mathematical-python/tree/master/python)

## 1. Variables / Things

The most basic component of any programming language are "things", also called variables or (in special cases) objects.

The most common basic "things" in Python are:
- integers (e.g., `0`, `1`, `-2`, etc.)
- floats (i.e., floating-point decimals like `2.5`, `3.14159`, `6.02e23` = $6.02 \times 10^{23}$, `1.5e-10` = $1.5\times10^{-10}$, etc.)
- strings (e.g., `'Chem 1000'`, `'H2P'`, `"Math for Chemistry"`)
- booleans (i.e., `True` and `False` - note that these are case-sensitive)
- some special objects of various types.
We'll meet many of these as we go through the lesson.

**TIP:** To run the code in a cell quickly, press `Ctrl-Enter`.

```python
# a thing
2
```

```python
# Use print to show multiple things in the same cell
# Note that you can use single or double quotes for strings
print(2)
print('hello')
```

### 1.1 Assign Values to Variables

We assign a value to a variable using the assignment operator `=`. For example, assign the integer 2 to the variable `x`:

```python
# Things can be stored as variables
x = 2
y = 'hello' # strings can be in single quotes or double quotes
z = True  # True and False are case sensitive
```

The assignment operator does not produce any output and so the cell above does not produce any output in a Jupyter notebook. Use the built-in function `print` to display the value assigned to a variable:

```python
print(x)
```

### 1.2 Naming Conventions

We can use any set of letters, numbers and underscores to make variable names however a variable name *cannot* begin with a number. There are many different kinds of naming conventions and we refer to the [Style Guide for Python Code (PEP8)](https://www.python.org/dev/peps/pep-0008/#naming-conventions) for a summary.

In general, in Python, people use `lower_case_with_underscores` variable names and single lowercase letter variable names such as `x`. It is good practice to use descriptive variable names, like `height_in_meters` to make your code more readable for other people (including yourself looking back later).

However there are words that we should not use as variable names because these words already have special meaning in Python, including [reserved words](https://docs.python.org/3.3/reference/lexical_analysis.html#keywords) and built-in functions For example, do not use `class`, `import`, `sum`, `min`, `max`, `list` or `sorted` as a variable name. See the full list of [builtin functions](https://docs.python.org/3/library/functions.html). Generally if you either use single letters or descriptive `words_with_underscores` you should be fine.

## 2. Commands that operate on things

Just storing data in variables isn't much use to us. Right away, we'd like to start performing operations and manipulations on data and variables.

There are three very common means of performing an operation on a thing.

### 2.1 Use an operator
All of the basic math operators work like you think they should for numbers. They can also do some useful operations on other things, like strings.

Number operators include `+`, `-`, `*`, and `/` as well as `%` for integer remainder and `**` for exponents:

| operator name|operator|syntax|How to read|
|---|---|---|---|
| addition|+|a + b|a plus b|
| subtraction|-|a - b|a minus b|
| multiplication|\*|a \* b|a times b|
| division|\/|a \/ b|a divided by b|
| remainder|%|a % b|a modulo b|
| power|**|a**b|a to the power b|

```python
# Standard math operators work as expected on numbers
a = 2
b = 3
```

Okay, we can add them:

```python
a + b
```

We can subtract:

```python
a - b
```

Multiply:

```python
a * b
```

Divide:

```python
a / b
```

Get a remainder:

```python
b % a
```

And perform powers / exponents:

```python
a**b
```

A few notes.. we can also perform exponents with fractions (e.g., square root). Note that the parentheses will take precedence over the exponent:

```python
2**(1/2)
```

```python
# There are also operators for strings
print('hello' + 'world') # notice that the + operator won't include a space
print('hello' * 3) # This can be very useful for building up larger strings as sequences
```

### 2.2 Use a function

These will be very familiar to anyone who has programmed in any language, and work like you
would expect. Functions have a syntax like:

`name( *parameters* ) # sometimes with multiple parameters.`

**TIP:** Many useful functions are not in the Python itself, but are in external "modules" or "libraries". These need to be imported into your Python notebook (or program) before they can be used. Some of the most important of these are math, numpy and matplotlib.

```python
print(round(3.3))

import math
print(math.sqrt(4))
print(math.pi) # just a constant
print(round(math.pi, 4)) # round pi to 4 decimal places - use "from sigfig import round" for the sigfig version
print(math.sin(math.pi/2))
```

### Modules

As mentioned above, much of the power of Python is that there are many, many useful modules, particularly for science, math, statistics, machine learning, .. We load these tools with statements like:

`import cmath`

`from sympy import ode`

These statements will load additional functions to our Python notebooks and scripts. To prevent confusion, we will typically use a prefix for these functions:

- `math.sqrt` (from the math module)
- `numpy.sqrt` (from the numpy module)

## Exercise 1 - Conversion

Throughout this lesson, we will successively build towards a program that will calculate the variance of some measurements, in this case Height in meters. The first thing we want to do is convert from an antiquated measurement system.

To change inches into meters we use the following equation (conversion factor is rounded - feel free to look up the exact conversion - we'll use another tool, `pint`, later)

$$ meter = \frac{inches}{39}$$

- Create a variable for the conversion factor, called `inches_in_meters`
- Create a variable (`inches`) for your height in inches, as inaccurately as you want.
- Divide inches by `inches_in_meters`, and store the result in a new variable, `meters`.
- Print the result

```python
## Enter your code here ##

```

Hmm, that seems like too many significant figures. We can use the round() function:

```python
print(round(meters, 3))
```

## 3. Collections

While it is interesting to explore your own height, in science we work with larger  slightly more complex datasets. In this example, we are interested in the characteristics and distribution of heights. Python provides us with a number of objects to handle collections.

Probably 99% of your work in scientific Python will use `lists` and `numpy` `arrays`. We'll talk about `numpy` in another session - basically, it offers ways to do math on lists/vectors, matrices, and the like.

Lists are probably the handiest and most flexible type of container.

Lists are declared with square brackets `[]`.

Individual elements of a list can be selected using the syntax `a[ind]`.

One "gotcha" for people new to programming is that lists in Python (and many other programming languages) start at 0.

```python
# Lists are created with square bracket syntax
pies = ['blueberry', 'strawberry', 'apple', 'rubarb', 'cherry']
print(pies, type(pies))
```

```python
# Lists (and all collections) are also indexed with square brackets
# NOTE: The first index is zero, not one
print(pies[0])
print(pies[1])

## You can also count from the end of the list
print('last item is:', pies[-1])
print('second to last item is:', pies[-2]) # notice that the comma between things in a print() adds spaces
```

```python
# We can add things to the list
pies.append('pumpkin')
print('last item is now:', pies[-1])
```

What is a method? It's a type of function. Some types of variables, called objects contain data as well as functions (called methods) to manipulate that data. Everything in Python is an object! The list `pies` in the cell above contains the string entries (the data) but it also has methods like `append()` to manipulate the data.

```python
# lists have a .sort(method) .. and a .reverse() method
pies.sort()
pies.reverse()
print(pies)
# you can also remove items (e.g., if my kids eat one of the pies)
pies.remove('cherry')
print(pies)
# if you try to remove something not in the list, you'll get an error
pies.remove('cherry') # we only had one cherry pie, sorry
```

### Exercise 2 - Store a bunch of heights (in meters) in a list

1. Ask five people around you for their heights (in meters). (For social distancing, just make up some heights.)
2. Store these in a list called `heights`.
3. Append your own height, calculated above in the variable *meters*, to the list.
4. Get the first height from the list and print it.

__Bonus__

1. Extract the last value in two different ways: first, by using the index for
the last item in the list, and second, presuming that you do not know how long the list is.

__HINT:__ **len()** can be used to find the length of a collection

```python
## Enter your code here
```

## 4. Loops

Let's say you're asked to take a cognitive test and count backwards from 100 by 7:

```python
number = 100
while number > 0:
    print(number)
    number = number - 7
print('Congrats, you passed the test')
```

Notice that inside the `while` loop, we *indent* the code. You can do this by typing `Tab` as you type code, and Jupyter will insert 4 spaces. Any code that should run after the loop should have the same indentation as the `while` statement (i.e., at the left edge of the cell) like the final `print()` statement. Python uses indentation to track which code lines should run in particular scope / context (e.g, as part of a loop or not).

Loops like these are a common case in programming, and we can do the same thing with a `for` loop. We'll use a new function - the `range()` function has three options:
- start (100)
- end (0)
- step (-7 each time through the loop)

```python
# repeat starting at 100, ending at 0, and going by -7 each time
for number in range(100, 0, -7):
    print(number)
print('Finished')
```

This is an important lesson - there are usually many ways to do the same thing in programming. For this class, we're using a small amount of code to help us get the correct answer. I don't care what the code looks like, although we need to read it for grading, and it needs to work. 😀

This is the origin of "Software Carpentry" for scientists - teaching researchers the key computing skills they need to get more done in less time and with less pain - not necessarily building large programs.

## Exercise 3 - Calculate the average height in your list

There are a few ways to do this, but let's use a loop.
- Create a variable for the total height
  - **Hint:** What would you use for the initial value of `total`?
- Loop through the list of `heights`
- For each item in the list, add it to the total (e.g., `total = total + ...`
- Once you have the total of all the heights, print total / count

```python
## Enter your code here
```

## 5. Final Thoughts

There's obviously a lot more to Python programming, but this should cover the basics we need for this course. If you have questions about the tutorial here, or code in the future, please don't hestiate to ask in the discussion forum.

-------
This notebook is from Prof. Geoffrey Hutchison, University of Pittsburgh
https://github.com/ghutchis/chem1000

Portions have been adapted from [Software Carpentry - Scientific Python Basics](https://bids.github.io/2016-01-14-berkeley/python/00-python-intro.html) and [Mathematical Python by Patrick Wills](https://github.com/patrickwalls/mathematical-python/tree/master/python)


# Skeleton Matching using Skeleton Context

## Introduction
This is a demo of our skeleton matching using Skeleton Context described in publications below. If you use this code or part of it for your research please respectfully cite these publications:

[1] Kamani, M.M., Farhat, F., Wistar, S. and Wang, J.Z., 2017. Skeleton Matching with Applications in Severe Weather Detection, Applied Soft Computing. [Link1](http://www.sciencedirect.com/science/article/pii/S1568494617303009) [Link2](https://pdfs.semanticscholar.org/a90c/ffa078ed566a1cb046ec68fd7195f01c7a76.pdf)

[2] Kamani, M.M., Farhat, F., Wistar, S. and Wang, J.Z., 2016, December. Shape matching using skeleton context for automated bow echo detection. In Big Data (Big Data), 2016 IEEE International Conference on (pp. 901-908). IEEE. [Link1](http://ieeexplore.ieee.org/abstract/document/7840685/) [Link2](https://pdfs.semanticscholar.org/ae62/d5802980859ead8288f6ff436754253882d6.pdf)

som part of the codes are transfered from Matlab version of shape context developed by UC Berkeley's vision group [here](https://www2.eecs.berkeley.edu/Research/Projects/CS/vision/code/sc_demo/)

```python
from __future__ import division
import os
import skeletonContext as sc
from skeleton2Graph import skeleton2Graph, mat2gray
from skimage import io
from matplotlib import pyplot as plt
import numpy as np
from IPython import display
```

First we load images of the skeletons to be matched. Then we would start from one end and traverse to the other end of the skeleton in order to calulate thet tangent estimate of it.

```python
s1 = mat2gray(io.imread(os.path.join(os.getcwd(), 'img/s1.png'),as_grey=True)).astype('float64')
s2 = mat2gray(io.imread(os.path.join(os.getcwd(), 'img/s2.png'),as_grey=True)).astype('float64')
_, edgeList1, _, _, _, _, _, _ = skeleton2Graph(s1,s1)
_, edgeList2, _, _, _, _, _, _ = skeleton2Graph(s2,s2)
s1Tangent, s1PointsList = sc.skeletonTangentEstimate(edgeList1)
s2Tangent, s2PointsList = sc.skeletonTangentEstimate(edgeList2)
```

Setting initial parameters for matching.

```python
'''Initial Paramters'''
displayFlag=1
affineStartFlag=1
polarityFlag=0
nsamp= 100
costDum=0.15
numDumRate=0.6
thetaWeight=0.2
nbins_theta=12
nbins_r=5
r_inner=1/8
r_outer=2
tan_eps=1.0
numIter=20
betaInit=1
r=1
w=4
sf=2.5
neighborWeight = 0.6
skeletonMatchCost  = np.zeros(numIter)
affineCost         = np.zeros(numIter)
bendingEnergy      = np.zeros(numIter)
matchRatio         = np.zeros(numIter)
dimSize = np.max(np.array([s1.shape,s2.shape]),0)
```

```python
t2 = sc.bdry_extract(s2,s2PointsList)
if len(s2PointsList) >= nsamp:
    s2PointsList, t2,s2Tangent = sc.get_samples(s2PointsList,t2,s2Tangent, nsamp)
else:
    raise ValueError('Skeleton 2 does not have enough samples')
t1 = sc.bdry_extract(s1,s1PointsList)
if len(s1PointsList) >= nsamp:
    s1PointsList, t1,s1Tangent = sc.get_samples(s1PointsList,t1,s1Tangent, nsamp)
else:
    raise ValueError('Skeleton 1 does not have enough samples')
```

```python
if displayFlag:
    f, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2,figsize=(6,8))
    ax1.imshow(s1,cmap='gray_r')
    ax1.set_title('Original Skeleton')
    ax2.imshow(s2,cmap='gray_r')
    ax2.set_title('Destination Skeleton')
    ax3.quiver(s1PointsList[:,1],s1PointsList[:,0],np.sin(t1),np.cos(t1),color='b')
    ax3.invert_yaxis()
    ax3.set_title(str(len(s1PointsList)) + '  samples on original skeleton')
    ax4.quiver(s2PointsList[:,1],s2PointsList[:,0],np.sin(t2),np.cos(t2),color='r')
    ax4.invert_yaxis()
    ax4.set_title(str(len(s2PointsList)) +'  samples on destination skeleton')
    plt.tight_layout()
    plt.show()
```

## Skeleton Context

Shape Context as a powerful shape descriptor represents a rough distribution of all other points with respect to a selected point in terms of distance and angle. It has quite number of applications in object recognition, pose recognition, animation construction, to name but a few. Shape context is used to find correspondences between samples from border of two shapes, and then find the cost of matching two shapes using bipartite graph matching. After that, parameters for an affine transform are extracted using thin plate spline ($TPS$), in order to map points from one shape to their correspondences in the other shape with warping the coordinates. Finally, a notion of shape distance for recognition purposes is exploited.

As there are a lot of fluctuations over the boundaries of some shapes in images, these make object matching with boundary samples less effective, and it may result in false matching. On the other hand, matching objects using skeleton samples sounds more robust in the sense that pruned skeleton contains complete shape topology regardless of its boundary variations. Hence, we use shape context to introduce a new descriptor called skeleton context. As it is shown in an example in the figure below, skeleton context is log-polar histogram, formed for each sample point on the skeleton. For each sample point $P_i$, the center of this log-polar histogram is located on that sample point, then each bin in the histogram represents the number of sample points in the specific angle and range of distance from the center (i.e. $P_i$) determined by that bin. We use the notation of $H_{SC}(P_i,r_{k_1},\theta_{k_2})$, to show the value of skeleton context's histogram for point $P_i$, in the $(r_{k_1},\theta_{k_2})$ bin. For instance, when $H_{SC}(P_i,r_{m},\theta_{n}) = 10$, it means that  in the distance range of $r_{m-1} \leq r < r_m$ and in the angle range of $\theta_{n-1} \leq \theta < \theta_n$ from the point $P_i$ in the skeleton, there are 10 other sample points. Basically, these histograms for each point visualize the distribution of other points on the skeleton with respect to that point, and hence it could play the object descriptor role for the shape matching purposes. As a result, we could use these descriptors as the feature data for bow echo detection in the next step. The skeleton context can be calculated using the following equation:
\begin{equation}
\begin{split}
H_{SC}(P_i,r_{k_1},\theta_{k_2}) =& |Bin(P_i,r_{k_1},\theta_{k_2})|\;,\\
Bin(P_i,r_{k_1},\theta_{k_2}) =& \big\{ \,X \in \mathbb{S} \mid\, r_{k_1-1} \leq \|X - P_i\|_2 < r_{k_1}\,
\, \bigcap \, \theta_{k_2-1} \leq \measuredangle(X,P_i) < \theta_{k_2} \big\}\;,
\end{split}
\end{equation}
where $|.|$ shows the number of members in a set, $P_i$ is a sample point on the skeleton that we want to calculate its skeleton context, $\mathbb{S}$ is the set of sample points in the skeleton, and $\measuredangle(X,P_i)$ calculate the angle of a vector from $P_i$ to $X$ when $P_i$ is on the origin of coordinates, with respect to the horizontal coordinate. In figure below the skeleton context is computed for two points of two objects, which matched together in our algorithm.

## Skeleton Matching using Skeleton Context
We use four indicators to measure the quality of the matched skeletons. We can compute the cost of mapping each skeleton point $P_{i}^1$ in image $1$, to each skeleton point $P_{j}^2$ in image 2, which simply is the normalized difference between skeleton contexts of each pair of points in two images:
\begin{equation}
C(P_{i}^1,P_{j}^2) =
\frac{1}{2}\sum_{k_1,k_2}\frac{\left(H_{SC}(P_{i}^1,r_{k_1},\theta_{k_2}) - H_{SC}(P_{j}^2,r_{k_1},\theta_{k_2})\right)^2}{H_{SC}(P_{i}^1,r_{k_1},\theta_{k_2}) + H_{SC}(P_{j}^2,r_{k_1},\theta_{k_2})}\;.
\end{equation}
Having the cost of all possible mappings, we can use one of the algorithms designed to solve the bipartite graph matching, such as Hungarian method, which finds the minimum cost solution for matching points in image $1$ to image $2$. We define a threshold for the cost of matching, which indicates that if the minimum cost of matching one point from image $1$ to the points of image $2$ is greater than that threshold, then we announce that there is no matching point for this sample point in image $1$. This would end in having some points without a proper pair from other image, and hence the matching ratio would be less than $1$.
This is necessary for having partial shape matching. Next, we use an affine transformation to warp coordinates of one image, in order to map its skeleton to other image skeleton.
As mentioned, for this step we could use $TPS$ interpolation which tries to minimize the bending energy. In warping coordinates, we need an indicator that shows the intensity of changes on shapes in the transformation . For instance, if the transformation is just a simple rotation or relocation, this indicator should be low, which means the transformation has not warped coordinates extensively. But it would be high, if the transformation is warping coordinates significantly to map the points together. If the affine transformation could be written in mathematical form as:
\begin{equation}
\vec{y} = f(\vec{x}) = A\vec{x} + \vec{b}\;,
\end{equation}
with matrix $A$ as linear map and vector $\vec{b}$ as the offset for translation, then we can write our indicator for affine transformation as follows:
\begin{equation}
\mathcal{C}_{A} = \log\frac{\sigma_1(A)}{\sigma_2(A)}\;,
\end{equation}
where $\sigma_i(A)$ shows $i$-th singular value of matrix $A$, with $\sigma_1 \geq \sigma_2$. The more $\mathcal{C}_A$ is closer to zero, the more the skeleton of two shapes are similar, and the less coordinates are warped. Overall, we can use four different indicators for defining the distance between two skeletons:

* __Matching Cost__ ($\mathcal{C}_{MC}$) : This is defined based on the cost of matching points in two shapes, as it was computed in an equation above.
* __Bending Energy__ ($\mathcal{C}_{BE}$) : The energy that $TPS$ wants to minimize.
* __Affine Indicator__ ($\mathcal{C}_{A}$) : An indication of how transformation changes the coordinates. Defined in an equation ebove.
* __Matching Ratio__ ($\mathcal{C}_{MR}$) : It represents the ratio of the number of sample points in image $1$, that we could match with points in image $2$; to the total number of sample points. As it was discussed, some points would not match to any points if the cost of matching to the points in the other shape is greater than a predefined cost. This would allow the algorithm to achieve partial matching.

### Neighboring Effect
As it was mentioned, we need partial shape matching, as skeletons might be matched partially. Therefore, we should add an alteration to the shape matching part, in order to include partial shape matching in our algorithm. To do so, after the first iteration of shape matching, we can learn the initial mapping between sample points of two images. Because in our algorithm for extracting the edge list, points are listed in accordance with their spatial order, we can use this ordinal positions of sample points to define neighbors. For instance, if $(m-1)$-th and $(m+1)$-th sample points in image $1$ are mapped respectively to $(n-1)$-th and $(n+1)$-th sample points in image $2$ , then we expect $m$-th sample point in image $1$ to be mapped as close as possible to $n$-th sample point in image $2$. This is what we call neighboring effect, and we want to impose this constraint in the shape matching algorithm by adding a Neighbor Cost to the cost introduced before for shape matching. This Neighbor Cost for the point $m$ in the image 1 could be in the form of:
\begin{equation}
\mathcal{C}_N(n,m) =  \epsilon_N \left[1 - \exp \left(- \frac{((n - m) - E\{\delta_N\})^2}{2\sigma^2} \right) \right],
\end{equation}
where $\epsilon_N$ is the maximum amplitude of this cost, and random variable $\delta_N$ is the difference between sample points' numbers in image $1$ with respect to the their mapped sample points' in image $2$. This cost would be a function of indexes of sample points in image 2 (\textit{i.e.} $n$) and image 1 (\textit{i.e.} $m$), hence for each pair of $(m,n)$ we would calculate a neighboring cost according to this function to be added to their mapping cost. For instance, if the average of the mapping differences($E\{\delta_N\}$) is 5, we expect to map the point 10 in image 2, to the point 5 in image 1. Based on this equation, it would add $0$ to the cost of mapping pair of $(10,5)$, however it increases as $m$ deviating from $5$. This Gaussian shape function would try to keep mapping of each sample point in accordance to its neighbors.

```python
'''Compute Matching points'''
X = np.copy(s1PointsList)
Y = np.copy(s2PointsList)
Xk = X
tk = t1

numDumPoints = int(numDumRate * nsamp)
outVec1 = np.zeros(nsamp)
outVec2 = np.zeros(nsamp)
neighborMap = np.tile(np.arange(1,nsamp+1),(nsamp,1))
neighborCost = np.zeros((nsamp,nsamp))
if displayFlag:
    f, (ax1, ax2,ax3) = plt.subplots(1,3,figsize=(21,7))
    ax1.invert_yaxis()
    ax2.invert_yaxis()
    ax3.invert_yaxis()
    x = np.linspace(0, dimSize[1], 36)
    y = np.linspace(0, dimSize[0],36)
    xv, yv = np.meshgrid(x, y)
    xv = np.reshape(xv.T,-1)
    yv = np.reshape(yv.T,-1)
    M = len(xv)
    printList = ['Bending Energy','Affine Cost', 'Matching Cost', 'Matching Ratio']
    row_format ="{:>20}" * (len(printList) + 1)
    print row_format.format("Iteration ", *printList)
for k in range(numIter):
    pointHistogram1, meanDistance1 = sc.skeletonContext(Xk.T,np.zeros(nsamp),nbins_theta,nbins_r,r_inner,r_outer,outVec1)
    pointHistogram2, meanDistance2 = sc.skeletonContext(Y.T,np.zeros(nsamp),nbins_theta,nbins_r,r_inner,r_outer,outVec2)

    if affineStartFlag:
        if k == 0:
            lambda_o = 1000;
        else:
            lambda_o = betaInit * pow(r,k-1)
    else:
        lambda_o = betaInit * pow(r,k)
    beta_k=(pow(meanDistance2,2)) * lambda_o

    costMatShape = sc.HistCost(pointHistogram1,pointHistogram2)
    thetaDiff = np.tile(tk,[nsamp,1]).T - np.tile(t2,[nsamp,1])

    if polarityFlag:
        costMatTheta = 0.5 * (1 - np.cos(thetaDiff))
    else:
        costMatTheta = 0.5 * (1 - np.cos(2 * thetaDiff))

    costMat = (1 - thetaWeight) * costMatShape + thetaWeight * costMatTheta
    costMat += neighborCost

    # Calculate Skeleton Context cost
    costMatTemp = costMat - neighborCost
    a1 = np.min(costMatTemp,0)
    a2 = np.min(costMatTemp,1)
    skeletonMatchCost[k] = max(np.mean(a1),np.mean(a2))

    numMatchPoints = nsamp + numDumPoints
    costMatDum = costDum * np.ones((numMatchPoints,numMatchPoints))
    costMatDum[0:nsamp,0:nsamp] = costMat
    matchedVec, _ = sc.hungarian(costMatDum)
    matchedVec2 = np.argsort(matchedVec)

    '''Neighboring Effect'''
    s1PointsMatchedInd = matchedVec2[:nsamp] <= nsamp
    s1PointsMatched = np.argwhere(s1PointsMatchedInd)
    matchDifference = s1PointsMatched - matchedVec2[s1PointsMatched]
    matchDifferenceMean = np.mean(matchDifference)
    matchDifferenceStd = np.std(matchDifference)
    outlierInd = (matchDifference >=  (matchDifferenceMean + 2.5 * matchDifferenceStd)) | (matchDifference <=  (matchDifferenceMean - 2.5 * matchDifferenceStd))
    matchRatio[k] = len(s1PointsMatched) / nsamp
    if matchRatio[k] <= 0.65:
        if (k != 0 & outlierInd.any() ):
            outlierMatchS2Points = s1PointsMatched[outlierInd]
            outlierMatchS1Points = matchedVec2[outlierMatchS2Points]
            matchedVec2[outlierMatchS2Points] = numMatchPoints
            matchedVec[outlierMatchS1Points] = numMatchPoints

            s1PointsMatchedInd = matchedVec2[:nsamp] <= nsamp
            s1PointsMatched = np.argwhere(s1PointsMatchedInd)
            matchDifference = s1PointsMatched - matchedVec2[s1PointsMatched]
            matchDifferenceMean = np.mean(matchDifference)
            matchDifferenceStd = np.std(matchDifference)

        neighborMean = neighborMap.T - matchDifferenceMean
        neighborCost = neighborWeight * (1 - np.exp(- (neighborMap - neighborMean)**2 / (2 * 10**2)))
    else:
        neighborCost = np.zeros((nsamp,nsamp))

    outVec1 = matchedVec2[:nsamp] > nsamp
    outVec2 = matchedVec[:nsamp] > nsamp

    X2 = np.nan * np.ones((numMatchPoints,2))
    X2[:nsamp,:] = np.copy(Xk)
    X2 = X2[matchedVec - 1,:]
    X2b = np.nan * np.ones((numMatchPoints,2))
    X2b[:nsamp,:] = X
    X2b = X2b[matchedVec - 1,:]
    Y2 = np.nan * np.ones((numMatchPoints,2))
    Y2[:nsamp,:] = np.copy(Y)

    indGood = np.where(~np.isnan(X2b[:nsamp,0]))[0]
    numGood = len(indGood)
    X3b = X2b[indGood,:]
    Y3  = Y2[indGood,:]

    if displayFlag:
        ax1.clear()
        ax1.scatter(X2[:,1],X2[:,0],c='b', marker='+',s=20)
        ax1.scatter(Y2[:,1],Y2[:,0],c='r',marker='o',s=20)
        ax1.plot(np.stack((X2[:,1],Y2[:,1]),axis=1).T,np.stack((X2[:,0],Y2[:,0]),axis=1).T,c='0.75')
        ax1.quiver(Xk[:,1],Xk[:,0],np.sin(tk),np.cos(tk),color='b')
        ax1.quiver(Y[:,1],Y[:,0],np.sin(t2),np.cos(t2),color='r')
        ax1.invert_yaxis()
        display.clear_output(wait=True)
        display.display(plt.gcf())
        ax2.clear()
        ax2.scatter(X[:,1],X[:,0],c='b', marker='+',s=20)
        ax2.scatter(Y[:,1],Y[:,0],c='r',marker='o',s=20)
        ax2.plot(np.stack((X2b[:,1],Y2[:,1]),axis=1).T,np.stack((X2b[:,0],Y2[:,0]),axis=1).T,c='0.75')
        ax2.invert_yaxis()
        display.clear_output(wait=True)
        display.display(plt.gcf())

    # Calculate bending energy
    cx,cy,E,_ = sc.bookstien(X3b , Y3, beta_k)
    bendingEnergy[k] = E
    # Calculating affine cost
    A = np.vstack((cx[numGood + 1:numGood + 3], cy[numGood + 1:numGood + 3]))
    _,s,_ = np.linalg.svd(A)
    affineCost[k] = np.log(s[0]/s[1])

    # warp coordinates
    fx_aff = np.dot(cx[numGood:numGood + 3].T,np.vstack((np.ones((1,nsamp)),X.T)))
    d2 = sc.dist2(X3b, X)
    U = d2 * np.log(d2 + np.finfo(float).eps)
    fx_wrp = np.dot(cx[:numGood].T,U)
    fx = fx_aff + fx_wrp
    fy_aff = np.dot(cy[numGood:numGood + 3].T,np.vstack((np.ones((1,nsamp)),X.T)))
    fy_wrp = np.dot(cy[:numGood].T,U)
    fy = fy_aff + fy_wrp

    Z=np.vstack((fx,fy)).T

    Xtan = X + tan_eps * np.vstack((np.cos(t1),np.sin(t1))).T
    fx_aff = np.dot(cx[numGood:numGood + 3].T,np.vstack((np.ones((1,nsamp)),Xtan.T)))
    d2 = sc.dist2(X3b, Xtan)
    U = d2 * np.log(d2 + np.finfo(float).eps)
    fx_wrp = np.dot(cx[:numGood].T,U)
    fx = fx_aff + fx_wrp
    fy_aff = np.dot(cy[numGood:numGood + 3].T,np.vstack((np.ones((1,nsamp)),Xtan.T)))
    fy_wrp = np.dot(cy[:numGood].T,U)
    fy = fy_aff + fy_wrp

    Ztan = np.vstack((fx,fy)).T
    tk = np.arctan2(Ztan[:,1] - Z[:,1], Ztan[:,0] - Z[:,0])

    if displayFlag:
        ax3.clear()
        ax3.scatter(Z[:,1],Z[:,0],c='b', marker='+',s=20)
        ax3.scatter(Y[:,1],Y[:,0],c='r',marker='o',s=20)
        fx_aff = np.dot(cx[numGood:numGood + 3].T,np.vstack((np.ones((1,M)),np.stack((xv,yv)))))
        d2 = sc.dist2(X3b,np.stack((xv,yv)).T)
        fx_wrp = np.dot(cx[:numGood].T, d2 * np.log(d2 + np.finfo(float).eps))
        fx = fx_aff + fx_wrp
        fy_aff = np.dot(cy[numGood:numGood + 3].T,np.vstack((np.ones((1,M)),np.stack((xv,yv)))))
        fy_wrp = np.dot(cy[:numGood].T, d2 * np.log(d2 + np.finfo(float).eps))
        fy = fy_aff + fy_wrp
        ax3.scatter(fy,fx,c='0.25',marker='.', s=1)
        ax3.invert_yaxis()
        display.clear_output(wait=True)
        display.display(plt.gcf())

    # Update Paramters
    Xk = Z
    matchingData = np.vstack((bendingEnergy[0:k+1],affineCost[0:k+1],skeletonMatchCost[0:k+1],matchRatio[0:k+1])).T
    print row_format.format("Iteration ", *printList)
    for i, row in enumerate(matchingData):
        print row_format.format(i+1, *row)
    if np.sum(matchingData[-1,0:3]) < 0.9:
        break
plt.close()

```