# Confirming the violation of the CHSH inequality

In this first exercise, we will confirm that the device that we call a quantum computer indeed exhibits quantum mechanical behavior — entanglement, in particular. You will be introduced to the concepts of quantum mechanics and the fundamentals of quantum computing through this exercise.

```{contents} Contents
---
local: true
---
```

$\newcommand{\ket}[1]{|#1\rangle}$

## Is this really a quantum computer?

The aim of this workbook is to familiarize yourself with quantum computers (QCs), but until just a few years ago, QCs were something that only existed in science fiction. However, now we are told that they are available as real computational resources over the cloud — but are these devices really QCs? How can we check?

The fundamental way that QCs work is that they manipulate a physical system, composed of elements such as superconducting resonators or cold atoms, so that the results of computations are expressed in the quantum state of the system. In other words, a device can be called a quantum computer only when it contains a physical system whose quantum state can be manipulated in certain ways and also be retained for a long period of time. There must also be algorithms that translate abstract calculations into tangible operations on the physical system. The algorithm portion will be introduced little by little throughout this workbook. Here, let's confirm that our "QC" really operates a quantum mechanical system.

## The CHSH inequality

One way to experimentally verify that a system behaves quantum mechanically is to test the violation of the CHSH inequality{cite}`PhysRevLett.23.880,nielsen_chuang_epr`. To put it briefly, the CHSH inequality is an inequality of specific observables in a two-body system that is satisfied unless there are quantum-specific phenomena such as entanglement. Said more straightforwardly, if you measure these observables in a device and their values violate the CHSH inequality, the device may actually be using quantum phenomena.

Normally, this type of experiment would require a highly sophisticated lab setup (involving laser, non-linear crystals, cold atoms, etc.). But with a cloud-based QC, all that's needed is a web browser. In this workbook, you will use Jupyter Notebook to write Python programs and then use [IBM Q System One](https://www.ibm.com/quantum-computing/systems/) machines through [IBM Quantum](https://quantum-computing.ibm.com/).

## The basic structure of Qiskit

[Qiskit](https://qiskit.org/), a Python library provided by IBM, is used for programming IBM quantum computers. The basic procedure for using Qiskit is as follows:

1. Decide on the number of quantum bits to use.
1. Apply quantum computation operations (gates) to the quantum bits to create a quantum circuit.
1. Execute the circuit and produce calculation results. Here, there are two options:
   - Send the circuit to the actual QC device and get the results back.
   - Simulate the circuit on your computer.
1. Analyze the calculation results.

You will perform this process below, while being introduced to various important concepts. In this exercise, you will only use the actual device. Please refer to the first assignment for information on simulating circuits.

# Quantum bits and quantum registers

**Quantum bits**, or **qubits**, are the fundamental elements that make up the quantum computer. They are the smallest possible unit of quantum information. A number of quantum bits gathered together is referred to as a quantum register.

A quantum register in a quantum computer is always in one "state." Following a common practice used in physics, the states of quantum registers are referred to as "kets" and denoted with a symbol like $\ket{\psi}$[^mixed_state]. If you are unfamiliar with quantum mechanics, this notation method may look intimidating, but the ket itself is merely a symbol, so there is no need to be overly concerned. You could also write it as $\psi$, without enclosing, or even use the 🔱 emoji if you want. Anything works!

What's important is that two **basis states** can be defined for each qubit. Conventionally, the two basis states are labled as $\ket{0}$ and $\ket{1}$, and form what is called a computational basis. Any state of a qubit can then be expressed through a *superposition* of the two computational basis states, using complex numbers $\alpha$ and $\beta$:

$$
\alpha \ket{0} + \beta \ket{1}
$$

In quantum mechanics, the coefficients $\alpha$ and $\beta$ are called the probability amplitudes, or simply **amplitudes**. Again, the actual formatting used is not particularly significant. The states could just as well be written as $[\alpha, \beta]$[^complexarray].

Another way to look at this is that a single qubit carries an amount of information equivalent to two complex numbers. However, there is a caveat: Due to the rules of quantum mechanics, the amplitudes $\alpha$ and $\beta$ must satisfy the following requirement.

$$
|\alpha|^2 + |\beta|^2 = 1
$$

Furthermore, the overall phase of a quantum state is not physically meaningful. In other words,

$$
\alpha \ket{0} + \beta \ket{1} \sim e^{i\theta} (\alpha \ket{0} + \beta \ket{1}),
$$

where $\sim$ indicates that the two sides represents the same quantum state.

A single complex number can be written using two real numbers, so $\alpha$ and $\beta$ together would appear to have the same amount of information as four real numbers, but due to these two constraints, the actual degree of freedom is 4-2=2. Another expression of the state of a qubit that makes the number of degrees of freedom more explicit is

$$
e^{-i\phi/2}\cos\frac{\theta}{2}\ket{0} + e^{i\phi/2}\sin\frac{\theta}{2}\ket{1},
$$

which is sometimes called the Bloch sphere notation.

Things get more interesting when there are multiple qubits. For example, if there are two, each has a $\ket{0}, \ket{1}$ computational basis, so the overall state is a superposition parametrized by four complex numbers,

$$
\alpha \ket{0}\ket{0} + \beta \ket{0}\ket{1} + \gamma \ket{1}\ket{0} + \delta \ket{1}\ket{1}.
$$

The "products" of the computational basis states of the two qubits, $\ket{0}\ket{0}, \ket{0}\ket{1}, \ket{1}\ket{0}$, and $\ket{1}\ket{1}$, are the computational basis states of this quantum register. Their abbreviated notations are $\ket{00}, \ket{01}, \ket{10}$, and $\ket{11}$.

The rules of quantum mechanics regarding the amplitudes in this case are

$$
|\alpha|^2 + |\beta|^2 + |\gamma|^2 + |\delta|^2 = 1
$$

and

$$
\alpha \ket{00} + \beta \ket{01} + \gamma \ket{10} + \delta \ket{11} \sim e^{i\theta} (\alpha \ket{00} + \beta \ket{01} + \gamma \ket{10} + \delta \ket{11}).
$$

There are only two constraints, regardless of the number of qubits.

In other words, a register with $n$ qubits has $2^n$ basis states with a complex amplitude for each, so *the amount of information that it carries is equivalent to $2 \times 2^n - 2$ real numbers*. This is why the word "exponential" often appears in the discussion of quantum calculation.

There is another frequently used notation for the computational basis states of quantum registers. One could look at the string of 0/1s that appears in the ket as a binary number and express it with the corresponding decimal number. For example, the four-qubit register states $\ket{0000}$ and $\ket{1111}$ can be expressed as $\ket{0}$ and $\ket{15}$, respectively. However, in this case, care must be taken to clearly indicate which qubit, the leftmost or the rightmost, corresponds to the least significant bit (LSB). Whether $\ket{0100}$ becomes $\ket{4}$ (the rightmost qubit is the LSB) or $\ket{2}$ (the leftmost is the LSB) depends on which convention is used. In this workbook, in accordance with the definition used in Qiskit, we take the rightmost qubit to be the LSB. Furthermore, we want to make the first qubit of the register correspond to the LSB. Therefore, when expressing a computational basis state with an array of kets or 0/1s, the register's qubits will be arranged from right to left.

Qiskit has a quantum register class, whose instance is created as:
```{code-block} python
from qiskit import QuantumRegister
register = QuantumRegister(4, 'myregister')
```
i.e., by specifying the number of qubits (four in this case) and the name (`'myregister'`). By default, all qubits will be in the |0⟩ state. The register object is not very useful by itself. Instead, they are usually used as parts in quantum circuits, which are introduced below.

[^mixed_state]: Strictly speaking, the state of a register can be expressed with kets only when the register is not entangled with other registers, but we will skip the details here.
[^complexarray]: In the simulations of QCs on classical computers, the states of the quantum registers are expressed with arrays of complex numbers, which corresponds well with this notation.

### ゲート、回路、測定

量子計算とは、端的に言えば、量子レジスタに特定の状態を生成し、その振幅を利用することと言えます。

とは言っても、いきなり「えいや」と好きな量子状態を作れるわけではなく、パターンの決まった単純操作（$\ket{0}$と$\ket{1}$を入れ替える、ブロッホ球表現での位相角度$\phi$を増減させる、など）を順番に組み合わせて複雑な状態を作っていきます。この単純操作のオペレーションのことを一般に量子**ゲート**といい、ゲートの種類や順番を指定したプログラムに相当するものを量子**回路**と呼びます。

Qiskitでは、量子回路を`QuantumCircuit`オブジェクトで表します。
```{code-block} python
from qiskit import QuantumCircuit, QuantumRegister
register = QuantumRegister(4, 'myregister')
circuit = QuantumCircuit(register)
```
という具合です。

作られた量子回路は、量子ビットの数が決まっているもののゲートが一つもない「空っぽ」の状態なので、そこにゲートをかけていきます。例えば下で説明するアダマールゲートをレジスタの2個目の量子ビットに作用させるには
```{code-block} python
circuit.h(register[1])
```
とします。

上で「振幅を利用する」という曖昧な表現をしましたが、それはいろいろな利用の仕方があるからです。しかし、どんな方法であっても、必ず量子レジスタの**測定**という操作を行います。量子コンピュータから何かしらの情報を得るための唯一の方法が測定です。

測定は量子レジスタの状態を「覗き見る」ような操作ですが、一回の測定操作で具体的に起きることは、各量子ビットに対して0もしくは1という値が得られるというだけです。つまり、量子状態が$2^n$個の計算基底の複雑な重ね合わせであったとしても、測定をすると一つの計算基底に対応するビット列が出てくるだけということになります。しかも、一度測定してしまった量子ビットはもう状態を変えてしまっていて、複雑な重ね合わせは失われてしまいます。

ではこの「一つの計算基底」がどの基底なのかというと、実は特殊な場合を除いて決まっていません。全く同じ回路を何度も実行して測定を繰り返すと、その都度ランダムにビット列が決まります。ただし、このランダムさには法則があって、**特定のビット列が得られる確率は、対応する計算基底の振幅の絶対値自乗**となっています。つまり、$n$ビットレジスタの状態$\sum_{j=0}^{2^n-1} c_j \ket{j}$があるとき、測定でビット列$k$が得られる確率は$|c_k|^2$です。

したがって、回路の実行と測定を何度も繰り返して、それぞれのビット列が現れる頻度を記録すれば、だんだん$|c_j|^2$の値がわかっていき、レジスタがどんな量子状態にあるのか見えてきます。しかし、わかるのは$c_j$の絶対値だけで、複素位相については知る術なしです。どうもすっきりしませんが、これが量子コンピュータから情報を得る方法です。

逆に、指数関数的な内部の情報量をうまく使って計算を行いつつ、測定という限定的な方法でも答えが読み出せるように工夫するのが、量子アルゴリズム設計の真髄ということになります。例えば理想的には、何か計算の答えが整数$k$であり、それを計算する回路の終状態が単純に$\ket{k}$となるようであれば、一度の測定で答えがわかる（上でいった特殊な場合に相当）わけです。単純に$\ket{k}$でなくても、重ね合わせ$\sum_{j=0}^{2^n-1} c_j \ket{j}$において$|c_k| \gg |c_{j \neq k}|$を実現できれば、数回の測定で答えが高確率でわかります。{doc}`shor`で紹介する位相推定アルゴリズムはその好例です。

### よく使うゲート

IBM Q System Oneのような超電導振動子を利用した量子コンピュータでは、実際に使用できるゲートは量子ビット1つにかかるものと2つにかかるものに限定されます。しかし、それらを十分な数組み合わせれば、$n$量子ビットレジスタにおいてどのような状態も実現できることが、数学的に証明できます。

#### 1量子ビットの操作

1量子ビットの操作でよく使われるゲートには、以下のようなものがあります。（表中コードの`i`, `j`は量子ビットの番号）

```{list-table}
:header-rows: 1
* - ゲート名
  - 説明
  - Qiskitコード
* - $X$
  - $\ket{0}$と$\ket{1}$を入れ替える。
  - `circuit.x(i)`
* - $Z$
  - $\ket{1}$の振幅を$-1$倍する。
  - `circuit.z(i)`
* - $H$（アダマールゲート）
  - 計算基底それぞれに対して、以下の変形をする。
    ```{math}
    H\ket{0} = \frac{1}{\sqrt{2}} (\ket{0} + \ket{1}) \\
    H\ket{1} = \frac{1}{\sqrt{2}} (\ket{0} - \ket{1})
    ```
    （「量子状態にゲートを作用させる」ことをケットの記法で書くときは、ゲートに対応する記号をケットに左からかけます。）<br/>
    例えば状態$\ket{\psi} = \alpha\ket{0} + \beta\ket{1}$に対しては、
    ```{math}
    \begin{align}
    H\ket{\psi} & = \alpha \frac{1}{\sqrt{2}} (\ket{0} + \ket{1}) + \beta \frac{1}{\sqrt{2}} (\ket{0} - \ket{1}) \\
                & = \frac{1}{\sqrt{2}} (\alpha + \beta) \ket{0} + \frac{1}{\sqrt{2}} (\alpha - \beta) \ket{1}
    \end{align}
    ```
    となる。
  - `circuit.h(i)`
* - $R_{y}$
  - パラメータ$\theta$を取り、計算基底それぞれに対して、以下の変形をする。
    ```{math}
    R_{y}(\theta)\ket{0} = \cos\frac{\theta}{2}\ket{0} + \sin\frac{\theta}{2}\ket{1} \\
    R_{y}(\theta)\ket{1} = -\sin\frac{\theta}{2}\ket{0} + \cos\frac{\theta}{2}\ket{1}
    ```
  - `circuit.ry(theta, i)`
* - $R_{z}$
  - パラメータ$\phi$を取り、計算基底それぞれに対して、以下の変形をする。
    ```{math}
    R_{z}(\phi)\ket{0} = e^{-i\phi/2}\ket{0} \\
    R_{z}(\phi)\ket{1} = e^{i\phi/2}\ket{1}
  - `circuit.rz(phi, i)`
```

それでは、2量子ビットレジスタの第0ビットに$H, R_y, X$の順にゲートをかけて、最後に測定をする回路をQiskitで書いてみましょう。

```python
# まずは必要になるpythonモジュールをすべてインポートしておく
import numpy as np
import matplotlib.pyplot as plt
from qiskit import QuantumCircuit, IBMQ, execute
from qiskit.providers.ibmq import least_busy
from qiskit.tools.monitor import job_monitor
from qiskit.visualization import plot_histogram

print('notebook ready')
```

```python
circuit = QuantumCircuit(2) # レジスタを介さずビット数を指定して回路を作成することもできます
circuit.h(0) # その場合、ゲートにはregister[0]ではなく直接量子ビットの番号を指定します
circuit.ry(np.pi / 2., 0) #　θ = π/2
circuit.x(0)
circuit.measure_all()

print('This circuit has', circuit.num_qubits, 'qubits and', circuit.size(), 'operations')
```

最後のプリント文で、ゲートが3つなのにも関わらず "5 operations" と出てくるのは、各量子ビットの測定も一つのオペレーションとして数えられるからです。

量子計算に慣れる目的で、この$H, R_y(\pi/2), X$という順の操作で第0ビットに何が起こるかを辿ってみましょう。初期状態は$\ket{0}$で、ケット記法では操作は左からかけていくので、$X R_y(\pi/2) H \ket{0}$を計算することになります。

$$
\begin{align}
X R_y\left(\frac{\pi}{2}\right) H \ket{0} & = X R_y\left(\frac{\pi}{2}\right) \frac{1}{\sqrt{2}}(\ket{0} + \ket{1}) \\
& = \frac{1}{\sqrt{2}} X \left[\left(\cos\left(\frac{\pi}{4}\right)\ket{0} + \sin\left(\frac{\pi}{4}\right)\ket{1}\right) + \left(-\sin\left(\frac{\pi}{4}\right)\ket{0} + \cos\left(\frac{\pi}{4}\right)\ket{1}\right)\right] \\
& = \frac{1}{\sqrt{2}} X \frac{1}{\sqrt{2}} \left[\left(\ket{0} + \ket{1}\right) + \left(-\ket{0} + \ket{1}\right)\right] \\
& = X \ket{1} \\
& = \ket{0}
\end{align}
$$

なので、結局$\ket{0}$状態に戻る操作でした。

#### 2量子ビットの操作

2量子ビットの操作は、量子ビットの超電導素子での実装の都合上、全て「制御ゲート」（controlled gates）という方式で行われます。この方式では、2つのビットのうち片方を制御（control）、もう片方を標的（target）として、制御ビットが1の時だけ標的ビットに何らかの操作がかかります。

例として、任意の1ビットゲート$U$を制御ゲート化した$C^i_j[U]$を考えます。ここで$i$が制御、$j$が標的ビットとします。ケットの添字でビットの番号を表して（reminder: 並べて書くときは右から順に番号を振ります）

$$
\begin{align}
C^1_0[U](\ket{0}_1\ket{0}_0) & = \ket{0}_1\ket{0}_0 \\
C^1_0[U](\ket{0}_1\ket{1}_0) & = \ket{0}_1\ket{1}_0 \\
C^1_0[U](\ket{1}_1\ket{0}_0) & = \ket{1}_1U\ket{0}_0 \\
C^1_0[U](\ket{1}_1\ket{1}_0) & = \ket{1}_1U\ket{1}_0
\end{align}
$$

です。

上で紹介した頻出する1ビットゲート$X, Z, H, R_y, R_z$のうち、$H$以外は制御ゲート化バージョンもよく使われます。特に$C[X]$はCXやCNOTとも呼ばれ、量子計算の基本要素として多様されます。実際、全ての2量子ビット制御ゲートはCNOTと1量子ビットゲートの組み合わせに分解できます。

```{list-table}
:header-rows: 1
* - ゲート名
  - 説明
  - Qiskitコード
* - $C^i_j[X]$, CX, CNOT
  - ビット$i$が1である計算基底において、ビット$j$に$X$を作用させる。
  - `circuit.cx(i, j)`
* - $C^i_j[Z]$
  - ビット$i, j$が1である計算基底の符号を反転させる。
  - `circuit.cz(i, j)`
* - $C^i_j[R_{y}]$
  - パラメータ$\theta$を取り、ビット$i$が1である計算基底において、ビット$j$に$R_y$を作用させる。
  - `circuit.cry(theta, i, j)`
* - $C^i_j[R_{z}]$
  - パラメータ$\phi$を取り、ビット$i$が1である計算基底において、ビット$j$に$R_z$を作用させる。
  - `circuit.crz(phi, i, j)`
```

Qiskitで2ビットレジスタに制御ゲートを用い、計算基底$\ket{0}, \ket{1}, \ket{2}, \ket{3}$の振幅の絶対値自乗が$1:2:3:4$の比になるような状態を作ってみましょう。さらに$C^0_1[Z]$ゲートを使って$\ket{3}$だけ振幅の符号が他と異なるようにします。

```python
theta1 = 2. * np.arctan(np.sqrt(7. / 3.))
theta2 = 2. * np.arctan(np.sqrt(2.))
theta3 = 2. * np.arctan(np.sqrt(4. / 3))

circuit = QuantumCircuit(2)
circuit.ry(theta1, 1)
circuit.ry(theta2, 0)
circuit.cry(theta3 - theta2, 1, 0) # C[Ry]　1が制御で0が標的
circuit.cz(0, 1) # C[Z] 0が制御で1が標的（実はC[Z]ではどちらが制御でも結果は同じ）
circuit.measure_all()

print('This circuit has', circuit.num_qubits, 'qubits and', circuit.size(), 'operations')
```

やや複雑ですが、また計算を追ってみましょう。まず角度$\theta_1, \theta_2, \theta_3$は定義から以下の関係を満たします。

$$
\begin{align}
R_y(\theta_1)\ket{0} & = \sqrt{\frac{3}{10}} \ket{0} + \sqrt{\frac{7}{10}} \ket{1} \\
R_y(\theta_2)\ket{0} & = \sqrt{\frac{1}{3}} \ket{0} + \sqrt{\frac{2}{3}} \ket{1} \\
R_y(\theta_3 - \theta_2)R_y(\theta_2)\ket{0} & = R_y(\theta_3)\ket{0} = \sqrt{\frac{3}{7}} \ket{0} + \sqrt{\frac{4}{7}} \ket{1}.
\end{align}
$$

したがって、

$$
\begin{align}
& C^1_0[R_y(\theta_3 - \theta_2)]R_{y1}(\theta_1)R_{y0}(\theta_2)\ket{0}_1\ket{0}_0 \\
= & C^1_0[R_y(\theta_3 - \theta_2)]\left(\sqrt{\frac{3}{10}} \ket{0}_1 + \sqrt{\frac{7}{10}} \ket{1}_1\right) R_y(\theta_2)\ket{0}_0\\
= & \sqrt{\frac{3}{10}} \ket{0}_1 R_y(\theta_2)\ket{0}_0 + \sqrt{\frac{7}{10}} \ket{1}_1 R_y(\theta_3)\ket{0}_0 \\
= & \sqrt{\frac{3}{10}} \ket{0}_1 \left(\sqrt{\frac{1}{3}} \ket{0}_0 + \sqrt{\frac{2}{3}} \ket{1}_0\right) + \sqrt{\frac{7}{10}} \ket{1}_1 \left(\sqrt{\frac{3}{7}} \ket{0}_0 + \sqrt{\frac{4}{7}} \ket{1}_0\right) \\
= & \sqrt{\frac{1}{10}} \ket{00} + \sqrt{\frac{2}{10}} \ket{01} + \sqrt{\frac{3}{10}} \ket{10} + \sqrt{\frac{4}{10}} \ket{11}
\end{align}
$$

最初の行で、ビット0と1にかかる$R_y$ゲートをそれぞれ$R_{y0}, R_{y1}$と表しました。

最後に$C[Z]$をかけると、$\ket{11}$だけ符号が反転します。

### 回路図の書き方と読み方

量子回路を可視化する方法として、「回路図」の標準的な描き方が決まっています。Qiskitでは`QuantumCircuit`オブジェクトの`draw()`というメソッドを使って自動描画できます。

```python
circuit.draw('mpl')
```

　ここで`draw()`の引数`'mpl'`はmatplotlibライブラリを使ってカラーで描くことを指定しています。実行環境によっては対応していないこともあるので、その場合は引数なしの`draw()`を使います。結果は`mpl`の場合に比べて見劣りしますが、内容は同じです。

```python
circuit.draw()
```

回路図は左から右に読んでいきます。水平の2本の実線が上からそれぞれ第0、第1量子ビットに対応し、その上にかぶさっている四角がゲート、最後にある矢印が下に伸びている箱が測定を表します。1ビットゲートから伸びている先端の丸い縦線は制御を表します。一番下の二重線は「古典レジスタ」に対応し、測定結果の0/1が記録される部分です。

## CHSH不等式を計算する回路を書く

それではいよいよ本題に入りましょう。CHSH不等式は二体系の4つの観測量の関係式なので、2ビットの回路を4つ用意します。そして、それぞれにおいて「ベル状態」$1/\sqrt{2}(\ket{00} + \ket{11})$を実現します。ベル状態は「どちらの量子ビットについても$\ket{0}$でも$\ket{1}$でもない状態」つまり、全体としては一つの定まった（純粋）状態であるにも関わらず、部分を見ると純粋でない状態です。このような時、**二つの量子ビットはエンタングルしている**といいます。エンタングルメントの存在は量子力学の非常に重要な特徴です。

ベル状態はアダマールゲートとCNOTゲートを組み合わせて作ります。ここでは回路を4つ作るので、ループを回して`circuits`という配列に回路を足していきます。

CHSH不等式の検証用の観測量を作るために、量子ビット1に対し、回路0と2では測定の直前に$R_y(-\pi/4)$、1と3では$R_y(-3\pi/4)$を作用させます。また回路2と3では量子ビット0に$R_y(-\pi/2)$を同じく測定の直前に作用させます。

```python
circuits = []
for ic in range(4):
    circuit = QuantumCircuit(2, name='circuit{}'.format(ic))
    circuit.h(0)
    circuit.cx(0, 1)
    circuits.append(circuit)

circuits[0].ry(-np.pi / 4., 1)
circuits[1].ry(-3. * np.pi / 4., 1)
circuits[2].ry(-np.pi / 4., 1)
circuits[3].ry(-3. * np.pi / 4., 1)

circuits[2].ry(-np.pi / 2., 0)
circuits[3].ry(-np.pi / 2., 0)

for circuit in circuits:
    circuit.measure_all()

# draw()にmatplotlibのaxesオブジェクトを渡すと、そこに描画してくれる
# 一つのノートブックセルで複数プロットしたい時などに便利
for circuit in circuits:
    ax = plt.figure().add_subplot()
    circuit.draw('mpl', ax=ax)
```

それぞれの回路で2ビットレジスタの基底$\ket{00}, \ket{01}, \ket{10}, \ket{11}$が現れる確率を計算してみましょう。

回路0の状態は

$$
\begin{align}
R_{y1}\left(-\frac{\pi}{4}\right) C^0_1[X] H_0 \ket{0}_1\ket{0}_0 = & R_{y1}\left(-\frac{\pi}{4}\right) \frac{1}{\sqrt{2}} (\ket{0}_1\ket{0}_0 + \ket{1}_1\ket{1}_0) \\
= & \frac{1}{\sqrt{2}} \left[(c\ket{0}_1 - s\ket{1}_1)\ket{0}_0 + (s\ket{0}_1 + c\ket{1}_1)\ket{1}_0\right]\\
= & \frac{1}{\sqrt{2}} (c\ket{00} + s\ket{01} - s\ket{10} + c\ket{11}).
\end{align}
$$

簡単のため$c = \cos(\pi/8), s = \sin(\pi/8)$とおきました。

したがって回路0での確率$P^0_{l} \, (l=00,01,10,11)$は

$$
P^0_{00} = P^0_{11} = \frac{c^2}{2} \\
P^0_{01} = P^0_{10} = \frac{s^2}{2}
$$

同様に、回路1の状態は

$$
R_{y1}\left(-\frac{3\pi}{4}\right) \frac{1}{\sqrt{2}} (\ket{0}_1\ket{0}_0 + \ket{1}_1\ket{1}_0) = \frac{1}{\sqrt{2}} (s\ket{00} + c\ket{01} - c\ket{10} + s\ket{11})
$$

で確率$P^1_{l}$は

$$
P^1_{00} = P^1_{11} = \frac{s^2}{2} \\
P^1_{01} = P^1_{10} = \frac{c^2}{2}
$$

です。回路2の状態は

$$
\begin{align}
& R_{y1}\left(-\frac{\pi}{4}\right) R_{y0}\left(-\frac{\pi}{2}\right) \frac{1}{\sqrt{2}} (\ket{0}_1\ket{0}_0 + \ket{1}_1\ket{1}_0) \\
= & \frac{1}{\sqrt{2}} \left[ \frac{1}{\sqrt{2}} (c\ket{0}_1 - s\ket{1}_1) (\ket{0}_0 - \ket{1}_0) + \frac{1}{\sqrt{2}} (s\ket{0}_1 + c\ket{1}_1) (\ket{0}_0 + \ket{1}_0) \right] \\
= & \frac{1}{2} \left[ (s+c)\ket{00} + (s-c)\ket{01} - (s-c)\ket{10} + (s+c)\ket{11} \right]
\end{align}
$$

で確率$P^2_{l}$は

$$
P^2_{00} = P^2_{11} = \frac{(s + c)^2}{4} \\
P^2_{01} = P^2_{10} = \frac{(s - c)^2}{4}
$$

同様に回路3の状態と確率$P^3_i$は

$$
\begin{align}
& R_{y1}\left(-\frac{3\pi}{4}\right) R_{y0}\left(-\frac{\pi}{2}\right) \frac{1}{\sqrt{2}} (\ket{0}_1\ket{0}_0 + \ket{1}_1\ket{1}_0) \\
= & \frac{1}{2} \left[ (s+c)\ket{00} - (s-c)\ket{01} + (s-c)\ket{10} + (s+c)\ket{11} \right]
\end{align}
$$

$$
P^3_{00} = P^3_{11} = \frac{(s + c)^2}{4} \\
P^3_{01} = P^3_{10} = \frac{(s - c)^2}{4}
$$

となります。

それぞれの回路でビット0と1で同じ値が観測される確率$P^{i}_{00} + P^{i}_{11}$から異なる値が観測される確率$P^{i}_{01} + P^{i}_{10}$を引いた値を$C^{i}$と定義します。

$$
C^0 = c^2 - s^2 = \cos\left(\frac{\pi}{4}\right) = \frac{1}{\sqrt{2}} \\
C^1 = s^2 - c^2 = -\frac{1}{\sqrt{2}} \\
C^2 = 2sc = \sin\left(\frac{\pi}{4}\right) = \frac{1}{\sqrt{2}} \\
C^3 = 2sc = \frac{1}{\sqrt{2}}
$$

なので、これらの組み合わせ$S = C^0 - C^1 + C^2 + C^3$の値は$2\sqrt{2}$です。

実は、エンタングルメントが起こらない場合、この観測量$S$の値は2を超えられないことが知られています。例えば$R_y$ゲートをかける前の状態がベル状態ではなく、確率$\frac{1}{2}$で$\ket{00}$、確率$\frac{1}{2}$で$\ket{11}$という「混合状態」である場合、

$$
C^0 = \frac{1}{\sqrt{2}} \\
C^1 = -\frac{1}{\sqrt{2}} \\
C^2 = 0 \\
C^3 = 0
$$

となり、$S = \sqrt{2} < 2$です。これがCHSH不等式です。

それでは、IBMQの「量子コンピュータ」が実際にエンタングル状態を生成できるのか、上の四つの回路から$S$の値を計算して確認してみましょう。

## 回路を実機で実行する

まずは{ref}`トークン <qiskit_token>`を使ってIBMQに接続します。

```python
IBMQ.enable_account('__paste_your_token_here__')
```

認証が済んだら、利用する量子コンピュータ（「バックエンド」と呼びます）を選びます。

```python
# IBMQプロバイダ（実機へのアクセスを管理するオブジェクト）
provider = IBMQ.get_provider(hub='ibm-q', group='open', project='main')

# バックエンド（実機）のうち量子ビット数2個以上のもののリストをプロバイダから取得し、一番空いているものを選ぶ
backend_filter = lambda b: (not b.configuration().simulator) and (b.configuration().n_qubits >= 2) and b.status().operational
backend = least_busy(provider.backends(filters=backend_filter))

print('Jobs will run on', backend.name())
```

回路をバックエンドに送るには、`execute`関数を使います。引数`shots`で回路の実行と測定を何回繰り返すか指定します。バックエンドごとに一度のジョブでの最大ショット数が決められていますが、大半の場合8192 (=2<sup>13</sup>)回となっています。

一つのジョブで複数の回路を`shots`回ずつ実行できます。一度に送れる回路の数はバックエンドごとに決まっています。

```python
shots = 8192

job = execute(circuits, backend=backend, shots=shots)

job_monitor(job, interval=2)
```

これで回路がジョブとしてバックエンドに送られ、キューに入りました。ジョブの実行結果は`execute`関数の返り値であるジョブオブジェクトから参照します。

IBMQのバックエンドは世界中からたくさんのユーザーに利用されているため、場合によっては予約されているジョブが多数あってキューにかなりの待ち時間が生じることがあります。

バックエンドごとのキューの長さは[IBM Quantum Experience](https://quantum-computing.ibm.com/)のホーム画面の右側で確認できます。この右側のコラムでバックエンドを一つクリックすると詳細が表示され、一番下の Your access providers という欄でバックエンドの最大ショット数と最大ジョブ数を確認できます。

また、自分の投じたジョブのステータスは左のツールバーのうちのJobsボタンから確認できます。

Qiskitプログラム中からもジョブのステータスを確認できます。いくつか方法がありますが、シンプルに一つのジョブをテキストベースでモニターするだけなら上のように`job_monitor`を使います。

## 量子測定結果の解析

ジョブオブジェクトの`result()`というメソッドを呼ぶと、ジョブが完了して結果が帰ってくるまでコードの実行が止まります。実行結果はオブジェクトとして返され、それの`get_counts`というメソッドを使うと、各ビット列が何回観測されたかというヒストグラムデータがPythonのdictとして得られます。

```python
result = job.result()

counts = []
for circuit in circuits:
    c = result.get_counts(circuit)
    counts.append(c)

print(counts)
```

```python
# !! Dummy contents -- delete this cell
counts = [
    {'00': 3339, '01': 720, '10': 863, '11': 3270},
    {'00': 964, '01': 3332, '10': 3284, '11': 612},
    {'00': 3414, '01': 693, '10': 953, '11': 3132},
    {'00': 3661, '01': 725, '10': 768, '11': 3038}
]
```

````{tip}
ノートブックの接続が切れてしまったり、過去に走らせたジョブの結果を再び解析したくなったりした場合は、ジョブIDを使って`retrieve_job`というメソッドでジョブオブジェクトを再構成することができます。過去に走らせたジョブはIBM Quantum Experienceのホームページにリストされているので、そこにあるジョブID（603d8bef43659838aのような）をコピーし、

```{code-block} python
backend = provider.get_backend('backend_you_used')
job = backend.retrieve_job('job_id')
```

とすると、`execute`によって返されたのと同じようにジョブオブジェクトが生成されます。
````

Qiskitから提供されている`plot_histogram`関数を使って、この情報を可視化できます。プロットの縦軸は観測回数を全測定数で割って、観測確率に規格化してあります。

```python
for c in counts:
    ax = plt.figure().add_subplot()
    plot_histogram(c, ax=ax)
```

上で、$c^2 = (\sqrt{2} + 1) / 2\sqrt{2} \sim 0.85$, $s^2 = (\sqrt{2} - 1) / 2\sqrt{2} \sim 0.15$なので、得られた確率は当たらずとも遠からずというところでしょうか。

実は現在の量子コンピュータにはまだ様々なノイズやエラーがあり、計算結果は往々にして理論的な値から統計誤差の範囲を超えてずれます。特定のエラーに関しては多少の緩和法も存在しますが、全て防げるわけでは決してありません。現在の量子コンピュータを指して "*Noisy* intermediate-scale quantum (NISQ) device" と呼んだりしますが、このNoisyの部分はこのような簡単な実験でもすでに顕著に現れるわけです。

逆に、NISQデバイスを有効活用するには、ノイズやエラーがあっても意味のある結果が得られるようなロバストな回路が求められます。{doc}`vqe`で紹介する変分量子回路を用いた最適化などがその候補として注目されています。

さて、それでは最後にCHSH不等式の破れを確認してみましょう。$C^{0}, C^{1}, C^{2}, C^{3}$を計算して$S$を求めます。

```python
C = []
for c in counts:
    C.append((c['00'] + c['11'] - c['01'] - c['10']) / shots)

S = C[0] - C[1] + C[2] + C[3]

print('C:', C)
print('S =', S)
if S > 2.:
    print('Yes, we are using a quantum computer!')
```

無事、$S$が2を超えました。

## 参考文献

```{bibliography}
:filter: docname in docnames
```


## Teoría de perturbaciones

Consiste en resolver un sistema perturbado(se conoce la solución al no perturbado), y donde el interés es conocer la contribución de la parte perturbada $H'$ al nuevo sistema total.

$$ H = H^{0} + H'$$

Para sistemas no degenerados, la corrección a la energía a primer orden se calcula como

$$ E_{n}^{(1)} = \int\psi_{n}^{(0)*} H' \psi_{n}^{(0)}d\tau$$

** Tarea 1 : Programar esta ecuación si conoces $H^{0}$ y sus soluciones. **

```python
from sympy.physics.qho_1d import psi_n
from sympy.physics.qho_1d import E_n
from sympy import *
from sympy import init_printing; init_printing(use_latex = 'mathjax')

n, m, m_e, omega, hbar = symbols('n m m_e omega hbar', real = True, constant = True)
var('x')

m_e = 9.10938356e-31
n = Abs(sympify(input('Valor de la energia: ')))
omega = sympify(input('Frecuencia Angular: '))

# Funcion de onda del hamiltoniano H.O no perturbado
FunOnda = psi_n(n, x, m_e, omega)

#Energia del hamiltoniano no perturbado
E0 = E_n(n, omega)

#Se necesita definir el nuevo hamiltoniano agregando la perturbacion
H = ((-(hbar**2)/(2*m_e))*diff(FunOnda, x, 2) + FunOnda*(m_e*(omega*x)**2)/(2))+FunOnda*sympify(input('Perturbation: '))

#Producto interno
sandwich = conjugate(FunOnda)*H
E = integrate(sandwich, (x, -oo,oo))
Error = (((E-E0)*100)/E0)
E

```

    Valor de la energia: 1
    Frecuencia Angular: 1
    Perturbation: 0

$$\frac{0.75 \hbar^{2}}{\hbar} + 0.75 \hbar$$

Y la corrección a la función de onda, también a primer orden, se obtiene como:

 $$ \psi_{n}^{(1)} = \sum_{m\neq n} \frac{\langle\psi_{m}^{(0)} | H' | \psi_{n}^{(0)} \rangle}{E_{n}^{(0)} - E_{m}^{(0)}} \psi_{m}^{(0)}$$

**Tarea 2: Programar esta ecuación si conoces $H^{0}$ y sus soluciones. **

```python
### Solución
#Importar de sympy el hamiltoniano y eigenfunciones para el oscilador armónico cuántico
from sympy.physics.qho_1d import psi_n
from sympy.physics.qho_1d import E_n
from sympy import *
from sympy import init_printing; init_printing(use_latex = 'mathjax')

n, m, m_e, omega, hbar = symbols('n m m_e omega hbar', real = True, constant = True)
var('x')

m_e = 9.10938356e-31
#Nivel de energia, sobre el cual realizar la corrección
n = Abs(sympify(input('Nivel de energia para la correcion de la funcion de onda: ')))
i= Abs(sympify(input('Nivel mas alto de energia:')))
omega = sympify(input('Frecuencia Angular: '))
#Funcion de onda no perturbada
FunOnda = psi_n(n, x, m_e, omega)

#Energía previa a la perturbación
E0 = E_n(n, omega)

#Nuevo hamiltoniano, contiene el original y la perturbacion
H =  FunOnda*sympify(input('Perturbacion: '))

#Energia no perturbada del Hamiltoniano
E0 = E_n(n, omega)
psicorrec = 0

for m in range(i):
    if m !=n:
        psim= psi_n(m, x, m_e, omega)
        producto = conjugate(psim)*H
        sandwich = integrate(producto, (x,-oo,oo))
        Em = E_n(m, omega)
        correc = ((sandwich)/(E0-Em))*psim
        psicorrec = psicorrec + correc

    else:
        psicorrec = psicorrec

#Integral del producto interno
sandwich = conjugate(FunOnda)*H
E = E0 + integrate(sandwich, (x, -oo,oo))
Error = (((E-E0)*100)/E0)

psipert = FunOnda + psicorrec
psipert.evalf()
psiplot = conjugate(psipert)*psipert
plot(psiplot,(x,-0.1,0.1))

```

**Tarea 3: Investigue las soluciones a segundo orden y también programe las soluciones. **

```python
### Solución
#La forma para la corrección de la energía a segundo orden es parecida. Trabajamos con energía en vez de funciones de onda.
from sympy.physics.qho_1d import psi_n
from sympy.physics.qho_1d import E_n
from sympy import *
from sympy import init_printing; init_printing(use_latex = 'mathjax')

n, m, m_e, omega, hbar = symbols('n m m_e omega hbar', real = True, constant = True)
var('x')

m_e = 9.10938356e-31
#Nivel de energia sobre la cual trabajamos
n = Abs(sympify(input('Nivel de energia para la correcion de la funcion de onda: ')))
i= Abs(sympify(input('Nivel mas alto de energia:')))
omega = sympify(input('Frecuencia angular: '))
#Funcion de onda no perturbada
FunOnda = psi_n(n, x, m_e, omega)

#Energía antes de la perturbación
E0 = E_n(n, omega)

#Nuevo hamiltoniano, contiene el original y la perturbacion
H =  FunOnda*sympify(input('Perturbacion: '))
intepriorden = conjugate(FunOnda)*H
priorden = integrate (intepriorden, (x,-oo,oo))
#Energia del hamiltoniano no perturbado
E0 = E_n(n, omega)
Ecorrec = 0

#La primera parte de la corrección a 2º orden es la de primer orden
for m in range(i):
    if m !=n:
        psim= psi_n(m, x, m_e, omega)
        product = conjugate(psim)*H
        sandwich = integrate(product, (x,-oo,oo))
        Em = E_n(m, omega)
        corr = ((sandwich)**2/(E0-Em))
        Ecorrec = Ecorrec + corr

    else:
        Ecorrec = Ecorrec

E = E0 + priorden + Ecorrec

E

```

    Nivel de energia para la correcion de la funcion de onda: 1
    Nivel mas alto de energia:10
    Frecuencia angular: 1
    Perturbacion: x**3

$$- 1.17409001625997 \cdot 10^{91} \hbar^{2} - \frac{\hbar^{2}}{\pi} 1.883130520616 \cdot 10^{59} + \frac{3 \hbar}{2}$$

**Tarea 4.  Resolver el átomo de helio aplicando los programas anteriores.**

```python
from sympy.physics.hydrogen import E_nl, R_nl
var('r1, r2, q', positive=True, real=True)

def Helium(N1,N2,L1,L2):

    Eb=E_nl(N1,1)+E_nl(N2,1)
    Psi1=R_nl(N1, L1, r1, Z=1)
    Psi2=R_nl(N2, L2, r2, Z=1)
    Psi=Psi1*Psi2
    E_correction1 = integrate(integrate(r1**2*r2**2*conjugate(Psi1)*conjugate(Psi2)*q**2*Psi1*Psi2/abs(r1-r2), (r1,0,oo)), (r2,0,oo))
    E_correctionR1 = q**2*integrate(r2**2*conjugate(Psi2)*Psi2*(integrate(r1**2*conjugate(Psi1)*Psi1/r2, (r1,0,r2))+integrate(r1**2*conjugate(Psi1)*Psi1/r1, (r1,r2,oo))), (r2,0,oo))

    return Psi, Eb, E_correction1, E_correctionR1

    E_correction1, E_correctionR1

Helium (1,1,0,0)

```

$$\left ( \frac{4}{e^{r_{1}} e^{r_{2}}}, \quad -1, \quad 16 q^{2} \int_{0}^{\infty} \frac{r_{2}^{2}}{e^{2 r_{2}}} \int_{0}^{\infty} \frac{r_{1}^{2}}{e^{2 r_{1}} \left|{r_{1} - r_{2}}\right|}\, dr_{1}\, dr_{2}, \quad \frac{5 q^{2}}{8}\right )$$

**Tarea 5: Método variacional-perturbativo. **

Este método nos permite estimar de forma precisa $E^{(2)}$ y correcciones perturbativas de la energía de órdenes más elevados para el estado fundamental del sistema, sin evaluar sumas infinitas. Ver ecuación 9.38 del libro.

**Resolver el átomo de helio, considerando este método (sección 9.4), como mejor le parezca. **

**Tarea 6. Revisar sección 9.7. **

Inicialmente a mano, y sengunda instancia favor de intentar programar sección del problema, i.e. integral de Coulomb  e integral de intercambio.

## Siguiente: Segunda parte, Octubre
Simetrías moleculares y Hartree-Fock

```python

```


```python
%matplotlib inline
```

```python
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sn
sn.set_style("whitegrid")
import sympy as sym

import pycollocation
import pyam
```

```python
pyam.__version__
```

    '0.1.2a0'

## Defining inputs

Need to define some heterogenous factors of production...

```python
# define some workers skill
x, loc1, mu1, sigma1 = sym.var('x, loc1, mu1, sigma1')
skill_cdf = 0.5 + 0.5 * sym.erf((sym.log(x - loc1) - mu1) / sym.sqrt(2 * sigma1**2))
skill_params = {'loc1': 1e0, 'mu1': 0.0, 'sigma1': 1.0}

workers = pyam.Input(var=x,
                     cdf=skill_cdf,
                     params=skill_params,
                     bounds=(1.2, 1e1),  # guesses for the alpha and (1 - alpha) quantiles!
                     alpha=0.005,
                     measure=1.0
                     )

# define some firms
y, loc2, mu2, sigma2 = sym.var('y, loc2, mu2, sigma2')
productivity_cdf = 0.5 + 0.5 * sym.erf((sym.log(y - loc2) - mu2) / sym.sqrt(2 * sigma2**2))
productivity_params = {'loc2': 1e0, 'mu2': 0.0, 'sigma2': 1.0}

firms = pyam.Input(var=y,
                   cdf=productivity_cdf,
                   params=productivity_params,
                   bounds=(1.2, 1e1),  # guesses for the alpha and (1 - alpha) quantiles!
                   alpha=0.005,
                   measure=1.0
                   )
```

Note that we are shifting the distributions of worker skill and firm productivity to the right by 1.0 in order to try and avoid issues with having workers (firms) with near zero skill (productivity).

```python
xs = np.linspace(workers.lower, workers.upper, 1e4)
plt.plot(xs, workers.evaluate_pdf(xs))
plt.xlabel('Worker skill, $x$', fontsize=20)
plt.show()
```

## Defining a production process

Next need to define some production process...

```python
# define symbolic expression for CES between x and y
omega_A, sigma_A = sym.var('omega_A, sigma_A')
A = ((omega_A * x**((sigma_A - 1) / sigma_A) +
     (1 - omega_A) * y**((sigma_A - 1) / sigma_A))**(sigma_A / (sigma_A - 1)))

# define symbolic expression for CES between x and y
r, l, omega_B, sigma_B = sym.var('r, l, omega_B, sigma_B')
B = ((omega_B * r**((sigma_B - 1) / sigma_B) +
     (1 - omega_B) * l**((sigma_B - 1) / sigma_B))**(sigma_B / (sigma_B - 1)))

F = A * B
```

```python
# negative assortativity requires that sigma_A * sigma_B > 1
F_params = {'omega_A':0.25, 'omega_B':0.5, 'sigma_A':2.0, 'sigma_B':1.0 }
```

## Define a boundary value problem

```python
problem = pyam.AssortativeMatchingProblem(assortativity='negative',
                                          input1=workers,
                                          input2=firms,
                                          F=sym.limit(F, sigma_B, 1),
                                          F_params=F_params)
```

## Pick some collocation solver

```python
solver = pycollocation.OrthogonalPolynomialSolver(problem)
```

## Compute some decent initial guess
Currently I guess that $\mu(x)$ is has the form...

$$ \hat{\mu}(x) = \beta_0 + \beta_1 f(x) $$

(i.e., a linear translation) of some function $f$.  Using my $\hat{\mu}(x)$, I can then back out a guess for $\theta(x)$ implied by the model...

$$ \hat{\theta}(x) = \frac{H(x)}{\hat{\mu}'(x)} $$

```python
initial_guess = pyam.OrthogonalPolynomialInitialGuess(solver)
initial_polys = initial_guess.compute_initial_guess("Chebyshev",
                                                    degrees={'mu': 40, 'theta': 70},
                                                    f=lambda x, alpha: x**alpha,
                                                    alpha=1.0)
```

```python
# quickly plot the initial conditions
xs = np.linspace(workers.lower, workers.upper, 1000)
plt.plot(xs, initial_polys['mu'](xs))
plt.plot(xs, initial_polys['theta'](xs))
plt.grid('on')
```

## Solve the model!

```python
domain = [workers.lower, workers.upper]
initial_coefs = {'mu': initial_polys['mu'].coef,
                 'theta': initial_polys['theta'].coef}

solver.solve(kind="Chebyshev",
             coefs_dict=initial_coefs,
             domain=domain,
             method='hybr')
```

```python
solver.result.success
```

    False

## Plot some results

```python
viz = pyam.Visualizer(solver)
```

```python
viz.interpolation_knots = np.linspace(workers.lower, workers.upper, 1000)
viz.residuals.plot()
plt.show()
```

```python
viz.normalized_residuals[['mu', 'theta']].plot(logy=True)
plt.show()
```

```python
viz.solution.tail()
```

      F
      Fl
      Flr
      Fr
      Fx
      Fxl
      Fxr
      Fxy
      Fy
      Fyl
      Fyr
      factor_payment_1
      factor_payment_2
      mu
      theta

      12.508857
      18.206082
      5.150932
      2.575466
      9.103041
      0.398371
      0.112708
      0.199185
      0.040918
      0.934998
      0.264533
      0.467499
      5.150932
      9.103041
      14.142185
      1.767261

      12.520161
      18.206094
      5.153478
      2.576739
      9.103047
      0.397750
      0.112589
      0.198875
      0.040864
      0.935230
      0.264729
      0.467615
      5.153478
      9.103047
      14.142192
      1.766389

      12.531464
      18.206107
      5.156022
      2.578011
      9.103054
      0.397132
      0.112469
      0.198566
      0.040811
      0.935460
      0.264925
      0.467730
      5.156022
      9.103054
      14.142198
      1.765519

      12.542768
      18.206122
      5.158563
      2.579281
      9.103061
      0.396514
      0.112349
      0.198257
      0.040757
      0.935691
      0.265121
      0.467846
      5.158563
      9.103061
      14.142205
      1.764651

      12.554071
      18.206134
      5.161101
      2.580551
      9.103067
      0.395898
      0.112230
      0.197949
      0.040704
      0.935921
      0.265316
      0.467961
      5.161101
      9.103067
      14.142212
      1.763784

```python
viz.solution[['mu', 'theta']].plot(subplots=True)
plt.show()
```

```python
viz.solution[['Fxy', 'Fyl']].plot()
plt.show()
```

## Plot factor payments
Note the `factor_payment_1` is wages and `factor_payment_2` is profits...

```python
viz.solution[['factor_payment_1', 'factor_payment_2']].plot(subplots=True)
plt.show()
```

## Plot firm size against wages and profits

```python
fig, axes = plt.subplots(1, 2, sharey=True)
axes[0].scatter(viz.solution.factor_payment_1, viz.solution.theta, alpha=0.5,
               edgecolor='none')
axes[0].set_ylim(0, 1.05 * viz.solution.theta.max())
axes[0].set_xlabel('Wages, $w$')
axes[0].set_ylabel(r'Firm size, $\theta$')

axes[1].scatter(viz.solution.factor_payment_2, viz.solution.theta, alpha=0.5,
               edgecolor='none')
axes[1].set_xlabel(r'Profits, $\pi$')

plt.show()
```

```python
# to get correlation just use pandas!
viz.solution.corr()
```

      F
      Fl
      Flr
      Fr
      Fx
      Fxl
      Fxr
      Fxy
      Fy
      Fyl
      Fyr
      factor_payment_1
      factor_payment_2
      mu
      theta

      F
      1.000000
      0.999021
      0.999021
      1.000000
      0.220731
      -0.997585
      0.220731
      -0.972939
      0.992605
      -0.614955
      0.992605
      0.999021
      1.000000
      0.995928
      0.946557

      Fl
      0.999021
      1.000000
      1.000000
      0.999021
      0.235079
      -0.999269
      0.235079
      -0.976903
      0.996133
      -0.634499
      0.996133
      1.000000
      0.999021
      0.996189
      0.950319

      Flr
      0.999021
      1.000000
      1.000000
      0.999021
      0.235079
      -0.999269
      0.235079
      -0.976903
      0.996133
      -0.634499
      0.996133
      1.000000
      0.999021
      0.996189
      0.950319

      Fr
      1.000000
      0.999021
      0.999021
      1.000000
      0.220731
      -0.997585
      0.220731
      -0.972939
      0.992605
      -0.614955
      0.992605
      0.999021
      1.000000
      0.995928
      0.946557

      Fx
      0.220731
      0.235079
      0.235079
      0.220731
      1.000000
      -0.271075
      1.000000
      -0.434832
      0.309461
      -0.842048
      0.309461
      0.235079
      0.220731
      0.307638
      0.523476

      Fxl
      -0.997585
      -0.999269
      -0.999269
      -0.997585
      -0.271075
      1.000000
      -0.271075
      0.984286
      -0.998298
      0.660006
      -0.998298
      -0.999269
      -0.997585
      -0.998115
      -0.960981

      Fxr
      0.220731
      0.235079
      0.235079
      0.220731
      1.000000
      -0.271075
      1.000000
      -0.434832
      0.309461
      -0.842048
      0.309461
      0.235079
      0.220731
      0.307638
      0.523476

      Fxy
      -0.972939
      -0.976903
      -0.976903
      -0.972939
      -0.434832
      0.984286
      -0.434832
      1.000000
      -0.988764
      0.763266
      -0.988764
      -0.976903
      -0.972939
      -0.989281
      -0.993717

      Fy
      0.992605
      0.996133
      0.996133
      0.992605
      0.309461
      -0.998298
      0.309461
      -0.988764
      1.000000
      -0.699261
      1.000000
      0.996133
      0.992605
      0.996621
      0.969117

      Fyl
      -0.614955
      -0.634499
      -0.634499
      -0.614955
      -0.842048
      0.660006
      -0.842048
      0.763266
      -0.699261
      1.000000
      -0.699261
      -0.634499
      -0.614955
      -0.676317
      -0.814112

      Fyr
      0.992605
      0.996133
      0.996133
      0.992605
      0.309461
      -0.998298
      0.309461
      -0.988764
      1.000000
      -0.699261
      1.000000
      0.996133
      0.992605
      0.996621
      0.969117

      factor_payment_1
      0.999021
      1.000000
      1.000000
      0.999021
      0.235079
      -0.999269
      0.235079
      -0.976903
      0.996133
      -0.634499
      0.996133
      1.000000
      0.999021
      0.996189
      0.950319

      factor_payment_2
      1.000000
      0.999021
      0.999021
      1.000000
      0.220731
      -0.997585
      0.220731
      -0.972939
      0.992605
      -0.614955
      0.992605
      0.999021
      1.000000
      0.995928
      0.946557

      mu
      0.995928
      0.996189
      0.996189
      0.995928
      0.307638
      -0.998115
      0.307638
      -0.989281
      0.996621
      -0.676317
      0.996621
      0.996189
      0.995928
      1.000000
      0.971758

      theta
      0.946557
      0.950319
      0.950319
      0.946557
      0.523476
      -0.960981
      0.523476
      -0.993717
      0.969117
      -0.814112
      0.969117
      0.950319
      0.946557
      0.971758
      1.000000

```python
# or a subset
viz.solution[['theta', 'factor_payment_1']].corr()
```

      theta
      factor_payment_1

      theta
      1.000000
      0.950319

      factor_payment_1
      0.950319
      1.000000

```python
# or actual values!
viz.solution.corr().loc['theta']['factor_payment_1']
```

    0.95031918230948131

## Plot the density for firm size

As you can see, the theta function is hump-shaped. Nothing special, but when calculating the pdf some arrangements have to be done for this: sort the thetas preserving the order (so we can relate them to their xs) and then use carefully the right x for calculating the pdf.

The principle of Philipp's trick is:

$pdf_x(x_i)$ can be interpreted as *number of workers with ability x*. $\theta_i$ is the size of the firms that employs workers of kind $x_i$. As all firms that match with workers type $x_i$ choose the same firm size, $pdf_x(x_i)/\theta_i$ is the number of firms of size $\theta_i$.

Say there are 100 workers with ability $x_i$, and their associated firm size $\theta_i$ is 2. Then there are $100/2 = 50$ $ \theta_i$ firms

```python
fig, axes = plt.subplots(1, 3)
theta_pdf = viz.compute_pdf('theta', normalize=True)
theta_pdf.plot(ax=axes[0])
axes[0].set_xlabel(r'Firm size, $\theta$')
axes[0].set_title(r'pdf')

theta_cdf = viz.compute_cdf(theta_pdf)
theta_cdf.plot(ax=axes[1])
axes[1].set_title(r'cdf')
axes[1].set_xlabel(r'Firm size, $\theta$')

theta_sf = viz.compute_sf(theta_cdf)
theta_sf.plot(ax=axes[2])
axes[2].set_title(r'sf')
axes[2].set_xlabel(r'Firm size, $\theta$')

plt.tight_layout()
plt.show()
```

## Distributions of factor payments

Can plot the distributions of average factor payments...

```python
fig, axes = plt.subplots(1, 3)
factor_payment_1_pdf = viz.compute_pdf('factor_payment_1', normalize=True)
factor_payment_1_pdf.plot(ax=axes[0])
axes[0].set_title(r'pdf')

factor_payment_1_cdf = viz.compute_cdf(factor_payment_1_pdf)
factor_payment_1_cdf.plot(ax=axes[1])
axes[1].set_title(r'cdf')

factor_payment_1_sf = viz.compute_sf(factor_payment_1_cdf)
factor_payment_1_sf.plot(ax=axes[2])
axes[2].set_title(r'sf')

plt.tight_layout()
plt.show()
```

```python
fig, axes = plt.subplots(1, 3)
factor_payment_2_pdf = viz.compute_pdf('factor_payment_2', normalize=True)
factor_payment_2_pdf.plot(ax=axes[0])
axes[0].set_title(r'pdf')

factor_payment_2_cdf = viz.compute_cdf(factor_payment_2_pdf)
factor_payment_2_cdf.plot(ax=axes[1])
axes[1].set_title(r'cdf')

factor_payment_2_sf = viz.compute_sf(factor_payment_2_cdf)
factor_payment_2_sf.plot(ax=axes[2])
axes[2].set_title(r'sf')

plt.tight_layout()
plt.show()
```

## Widget

```python
from IPython.html import widgets
```

```python
def interactive_plot(viz, omega_A=0.25, omega_B=0.5, sigma_A=0.5, sigma_B=1.0,
                     loc1=1.0, mu1=0.0, sigma1=1.0, loc2=1.0, mu2=0.0, sigma2=1.0):
    # update new parameters as needed
    new_F_params = {'omega_A': omega_A, 'omega_B': omega_B,
                    'sigma_A': sigma_A, 'sigma_B': sigma_B}
    viz.solver.problem.F_params = new_F_params

    new_input1_params = {'loc1': loc1, 'mu1': mu1, 'sigma1': sigma1}
    viz.solver.problem.input1.params = new_input1_params

    new_input2_params = {'loc2': loc2, 'mu2': mu2, 'sigma2': sigma2}
    viz.solver.problem.input2.params = new_input2_params

    # solve the model using a hotstart initial guess
    domain = [viz.solver.problem.input1.lower, viz.solver.problem.input1.upper]
    initial_coefs = viz.solver._coefs_array_to_dict(viz.solver.result.x, viz.solver.degrees)
    viz.solver.solve(kind="Chebyshev",
                     coefs_dict=initial_coefs,
                     domain=domain,
                     method='hybr')

    if viz.solver.result.success:
        viz._Visualizer__solution = None  # should not need to access this!
        viz.interpolation_knots = np.linspace(domain[0], domain[1], 1000)
        viz.solution[['mu', 'theta']].plot(subplots=True)
        viz.normalized_residuals[['mu', 'theta']].plot(logy=True)
    else:
        print "Foobar!"
```

```python
viz_widget = widgets.fixed(viz)

# widgets for the model parameters
eps = 1e-2
omega_A_widget = widgets.FloatSlider(value=0.25, min=eps, max=1-eps, step=eps,
                                     description=r"$\omega_A$")
sigma_A_widget = widgets.FloatSlider(value=0.5, min=eps, max=1-eps, step=eps,
                                     description=r"$\sigma_A$")
omega_B_widget = widgets.FloatSlider(value=0.5, min=eps, max=1-eps, step=eps,
                                     description=r"$\omega_B$")
sigma_B_widget = widgets.fixed(1.0)

# widgets for input distributions
loc_widget = widgets.fixed(1.0)
mu_1_widget = widgets.FloatSlider(value=0.0, min=-1.0, max=1.0, step=eps,
                                  description=r"$\mu_1$")
mu_2_widget = widgets.FloatSlider(value=0.0, min=-1.0, max=1.0, step=eps,
                                  description=r"$\mu_2$")
sigma_1_widget = widgets.FloatSlider(value=1.0, min=eps, max=2-eps, step=eps,
                                     description=r"$\sigma_1$")
sigma_2_widget = widgets.FloatSlider(value=1.0, min=eps, max=2-eps, step=eps,
                                     description=r"$\sigma_2$")

```

```python
widgets.interact(interactive_plot, viz=viz_widget, omega_A=omega_A_widget,
                 sigma_A=sigma_A_widget, omega_B=omega_B_widget,
                 sigma_B=sigma_B_widget, sigma1=sigma_1_widget,
                 loc1=loc_widget, mu1 = mu_1_widget,
                 loc2=loc_widget, sigma2=sigma_2_widget, mu2 = mu_2_widget)
```

```python
# widget is changing the parameters of the underlying solver
solver.result.x
```

    array([  7.25026241e+01,   4.44958662e+01,  -2.32094502e+01,
             6.62025133e+00,   5.81461799e-01,  -1.55946410e+00,
             6.26485738e-01,   3.47032707e-02,  -1.34255798e-01,
             2.33827843e-02,   5.00922933e-02,  -5.15670685e-02,
             2.86571642e-02,  -1.41408791e-02,   1.11098441e-02,
            -1.18476805e-02,   1.15288324e-02,  -9.86307280e-03,
             8.03792622e-03,  -6.69413471e-03,   5.77789650e-03,
            -5.05085914e-03,   4.38977476e-03,  -3.78748392e-03,
             3.26426857e-03,  -2.82203708e-03,   2.44698900e-03,
            -2.12428421e-03,   1.84442215e-03,  -1.60187017e-03,
             1.39228307e-03,  -1.21137286e-03,   1.05506872e-03,
            -9.19854262e-04,   8.02819187e-04,  -7.01524100e-04,
             6.13866717e-04,  -5.38013838e-04,   4.72372899e-04,
            -4.15568976e-04,   3.66416864e-04,  -3.23892681e-04,
             2.87109830e-04,  -2.55299928e-04,   2.27797146e-04,
            -2.04024618e-04,   1.83482440e-04,  -1.65737213e-04,
             1.50413030e-04,  -1.37183732e-04,   1.25766216e-04,
            -1.15914624e-04,   1.07415296e-04,  -1.00082376e-04,
             9.37539977e-05,  -8.82889744e-05,   8.35639309e-05,
            -7.94707899e-05,   7.59145078e-05,  -7.28109983e-05,
             7.00853553e-05,  -6.76706220e-05,   6.55070496e-05,
            -6.35408793e-05,   6.17211507e-05,  -5.99950522e-05,
             5.83072166e-05,  -5.66099134e-05,   5.48781544e-05,
            -5.30977342e-05,   5.11925525e-05,  -4.89365611e-05,
             4.60308938e-05,  -4.25082051e-05,   3.92086051e-05,
            -1.87680507e-05,   6.10424640e+01,   3.86836905e+01,
            -2.21649811e+01,   5.26294133e+00,   2.01751068e+00,
            -2.41479138e+00,   8.91641452e-01,   3.96751691e-02,
            -1.67706401e-01,   1.85366413e-02,   7.15768521e-02,
            -6.46042588e-02,   2.92261437e-02,  -8.98904604e-03,
             5.76335767e-03,  -7.66261342e-03,   7.85910010e-03,
            -6.14814581e-03,   4.28490171e-03,  -3.12973675e-03,
             2.54158023e-03,  -2.15279042e-03,   1.78914710e-03,
            -1.45008533e-03,   1.17116479e-03,  -9.58354064e-04,
             7.94695948e-04,  -6.62679003e-04,   5.53059870e-04,
            -4.62188213e-04,   3.87674593e-04,  -3.26777656e-04,
             2.76738706e-04,  -2.35322435e-04,   2.00898051e-04,
            -1.72244304e-04,   1.48370419e-04,  -1.28442076e-04,
             1.11765505e-04,  -9.77761048e-05,   8.60169736e-05,
            -7.61151131e-05,   6.77628858e-05,  -6.07054673e-05,
             5.47317959e-05,  -4.96670756e-05,   4.53662817e-05,
            -4.17087266e-05,   3.85936833e-05,  -3.59368849e-05,
             3.36676811e-05,  -3.17266913e-05,   3.00638572e-05,
            -2.86368285e-05,   2.74096272e-05,  -2.63515534e-05,
             2.54362956e-05,  -2.46411808e-05,   2.39464638e-05,
            -2.33346025e-05,   2.27896763e-05,  -2.22973074e-05,
             2.18451093e-05,  -2.14225261e-05,   2.10180922e-05,
            -2.06143949e-05,   2.01871960e-05,  -1.97180633e-05,
             1.92152208e-05,  -1.87045927e-05,   1.81448944e-05,
            -1.73067164e-05,   1.58166339e-05,  -1.35683931e-05,
             1.12566297e-05,  -5.08591626e-06])

```python

```


### Requirements

#### Jupyter Nbextensions
- Python Markdown
- Load Tex Macros

#### Python & Libs

- Python version $\geq$ 3.4
- Numpy version $\geq$ 1.17
- Pandas version $\geq$ 1.0.3

```python
import string
import operator
import functools
import numpy as np
import pandas as pd

from collections import Counter
from IPython.display import display, Math, Latex, Markdown
```

```python
ref_1 = 'The cat sat on the mat.'
cand_1 = 'The cat is on the mat.'
ref_2 = 'There is a cat on the mat.'
cand_2 = 'The the the the the the the the.'

preprocess = lambda x: x.lower().translate(str.maketrans('', '', string.punctuation))

def extract(sentence):
    sentence = preprocess(sentence)
    uni_gram = sentence.split()
    bi_gram = [' '.join(words) for words in zip(uni_gram[::], uni_gram[1::])]
    tri_gram = [' '.join(words) for words in zip(uni_gram[::], uni_gram[1::], uni_gram[2::])]
    quad_gram = [' '.join(words) for words in zip(uni_gram[::], uni_gram[1::], uni_gram[2::], uni_gram[3::])]
    return uni_gram, bi_gram, tri_gram, quad_gram
```

## N-gram Evaluation

### Example

Reference

`{{ref_1}}`

$\xrightarrow[\text{}]{\text{ Preprocessing }}$ `{{preprocess(ref_1)}}`

$\xrightarrow[\text{}]{\text{Extract 1-gram}} $ `{{extract(ref_1)[0]}}`

$\xrightarrow[\text{}]{\text{Extract 2-gram}} $ `{{extract(ref_1)[1]}}`

$\xrightarrow[\text{}]{\text{Extract 3-gram}} $ `{{extract(ref_1)[2]}}`

Candidate

`{{cand_1}}`

$\xrightarrow[\text{}]{\text{ Preprocessing }}$ `{{preprocess(cand_1)}}`

$\xrightarrow[\text{}]{\text{Extract 1-gram}} $ `{{extract(cand_1)[0]}}`

$\xrightarrow[\text{}]{\text{Extract 2-gram}} $ `{{extract(cand_1)[1]}}`

$\xrightarrow[\text{}]{\text{Extract 3-gram}} $ `{{extract(cand_1)[2]}}`

## Considering Recall %

### Modified Precision - Clipping

### Example

Candidate

`{{cand_2}}`

$\xrightarrow[\text{}]{\text{ Preprocessing }}$ `{{preprocess(cand_2)}}`

$\xrightarrow[\text{}]{\text{Extract 1-gram}} $ `{{extract(cand_2)[0]}}`

$\xrightarrow[\text{}]{\text{Extract 2-gram}} $ `{{extract(cand_2)[1]}}`

$\xrightarrow[\text{}]{\text{Extract 3-gram}} $ `{{extract(cand_2)[2][:2] + ['...']}}`

## [BLEU - Bilingual Evaluation Understudy](https://www.aclweb.org/anthology/P02-1040.pdf)

### BLEU_n Formula

$
\begin{align}
    \quad
        BLEU = BP \cdot exp(\sum_{n=1}^{N} w_n\log_{}{P_n}) \cr
\end{align}
$

$
\begin{align}
    \quad
        BP \quad\,\ = \begin{cases}
                        1         &, \ c > r   \cr
                        exp(1-\frac{r}{c}) &, \ c \leq r   \cr
                      \end{cases}
\end{align}
$

```python
def BLEU_n(candidate, reference):
    candidate = extract(candidate)
    reference = extract(reference)
    BLEU = 0
    W_n = 1. / len(candidate)
    for cand, ref in zip(candidate, reference):
        BLEU += W_n * np.log(P_n(cand, ref))
    BLEU = np.exp(BLEU) * BP(candidate[0], reference[0])
    return BLEU

def P_n(cand, ref):
    count = 0
    for c in cand:
        if c in ref:
            count += 1
            ref.remove(c)
    return 1 if count == 0 else count / len(cand)

def BP(candidate, reference):
    c, r = len(candidate), len(reference)
    return 1 if c > r else np.exp(1 - r / c)

#BLEU_n(cand_1, ref_1)
```

## [ROUGE - Recall-Oriented Understudy for Gisting Evaluation](https://www.aclweb.org/anthology/W04-1013.pdf)

### Rouge-N Formula

$
\begin{align}
    \quad
        \textit{Rouge-N}\; = \frac{\sum\limits_{S \in \{\textit{ReferenceSummaries}\}}
                                 \sum\limits_{gram_n \in S} Count_{macth}(gram_n)}
                                {\sum\limits_{S \in \{\textit{ReferenceSummaries}\}}
                                 \sum\limits_{gram_n \in S} Count(gram_n)}
\end{align}
$

```python
ref_3 = 'The cat was under the bed.'
cand_3 = 'The cat was found under the bed.'

def Rouge_n(candidate, reference, n=1):
    cand, ref = extract(candidate)[n-1], extract(reference)[n-1]
    cand = list(map(lambda x: 1 if x in ref else 0, cand))
    return functools.reduce(operator.add, cand) / len(ref)

#Rouge_n(cand_3, ref_3)
```

### LCS(Longest Common Subsequence)

#### Example

Reference

`{{ref_1}}`

$\xrightarrow[\text{}]{\text{ Preprocessing }}$ `{{preprocess(ref_1)}}`

Candidate

`{{cand_1}}`

$\xrightarrow[\text{}]{\text{ Preprocessing }}$ `{{preprocess(cand_1)}}`

LCS

`the cat on the mat`

### Rouge-L Formula

$
\begin{align}
    \cr
    \quad
        R_{lcs} = \frac{LCS(\textit{Reference}, \textit{Candidate})}{m},
                        \; m \;\text{for }\textit{Reference} \text{ length} \cr
        P_{lcs} = \frac{LCS(\textit{Reference}, \textit{Candidate})}{n},
                        \; n \;\text{for }\textit{Candidate} \text{ length} \cr
\end{align}
$

$
\begin{align}
    \quad \;
        F_{lcs} = \frac{(1+\beta^2)R_{lcs}P_{lcs}}{R_{lcs} + \beta^2P_{lcs}}
\end{align}
$

```python
def Rouge_l(candidate, reference, beta=1.2):
    cand, ref = extract(candidate)[0], extract(reference)[0]
    lcs = LCS(cand, ref)
    r_lcs, p_lcs = lcs / len(ref), lcs / len(cand)
    return ((1 + beta**2)*r_lcs*p_lcs) / (r_lcs + beta**2*p_lcs)

def LCS(cand, ref):
    l_c, l_r = len(cand), len(ref)
    dp = np.zeros(shape=(l_c + 1, l_r + 1))
    for i in range(l_c):
        for j in range(l_r):
            if cand[i] == ref[j]:
                dp[i + 1][j + 1] = dp[i][j] + 1
            elif dp[i + 1][j] > dp[i][j + 1]:
                dp[i + 1][j + 1] = dp[i + 1][j]
            else:
                dp[i + 1][j + 1] = dp[i][j + 1]
    return int(dp[-1][-1])

#Rouge_l(cand_1, ref_1)
```

### Rouge-W

   - WLCS
       - Weighted LCS-based statistics that favors consecutive LCSes.

### Rouge-S

   - Skip-gram
       - Skip-bigram based co-occurrence statistics.
       - Skip-bigram is any pair of words in their sentence order.

### Rouge-SU

   - Skip-bigram plus unigram-based co-occurrence statistics.

## [CIDEr -  Consensus-based Image Description Evaluation](https://www.cv-foundation.org/openaccess/content_cvpr_2015/papers/Vedantam_CIDEr_Consensus-Based_Image_2015_CVPR_paper.pdf)

### TF-IDF

   - Term Frequency

       $
       \begin{align}
           \quad
           \textit{TF}(𝑥)
           \;\text{for count of term } x \;\text{ in the document}
           \cr
       \end{align}
       $

   - Inverse Document Frequency

       $
       \begin{align}
           \quad
           \textit{IDF}(𝑥) = \log \frac{N + 1}{N(x) + 1} +1
           ,\; N\;\text{for total document count and }N(x) \text{ for document which includes term } x
           \cr
       \end{align}
       $

   - TF-IDF

       $
       \begin{align}
           \quad
           \textit{TF-IDF}(x) = \textit{TF}(x)\;\times\;\textit{IDF}(x) \cr
       \end{align}
       $

### Cosine Similiraty

   - The cosine of two non-zero vectors can be derived by using the Euclidean dot product formula

       $
       \begin{align}
           \quad
           A \cdot B = \lVert A \rVert \lVert B \rVert \cos{\theta}
           \cr
       \end{align}
       $

   - Similiraty

       $
       \begin{align}
           \quad
           \textit{Similiraty } =
           \cos{(\theta)} =
           \frac{A \cdot B}
                {\lVert A \rVert \lVert B \rVert} =
           \frac{\sum\limits_{i=1}^{N}A_i B_i}
                {\sqrt{\sum\limits_{i=1}^{N}A_i^2} \sqrt{\sum\limits_{i=1}^{N}B_i^2}},
           \text{ where } A_i \text{ and } B_i \text{are components of vector } A \text{ and } B \text{ respectively.}
       \end{align}
       $

```python
@np.vectorize
def nonzero(x):
    return 1 if x > 0 else 0

@np.vectorize
def TFIDF(x, IDF):
    return x * IDF

def gen_matrix(documents):
    d = Counter()
    for doc in documents:
        d += Counter(doc)
    d = dict(d.most_common()).fromkeys(d, 0)
    matrix = []
    for doc in documents:
        dest = d.copy()
        dest.update(dict(Counter(doc)))
        matrix.append(list(dest.values()))
    return d, matrix

def gen_table(d, matrix, fn):
    columns = {}
    index = ['Candidate'] + ['Reference_{}'.format(i+1) for i in range(len(matrix) - 1)]
    if fn.__name__ == 'CIDEr':
        for idx, key in enumerate(list(d.keys()) +['CIDEr']):
            columns.update({idx:key})
        N = [len(matrix)]*len(matrix[0])
        N_x = np.matrix.sum(nonzero(np.matrix(matrix)), axis=0).tolist()[0]
        N = np.array(N) + np.array(1)
        N_x = np.array(N_x) + np.array(1)
        IDF = (np.log(N / N_x) / np.log(10) + (np.array(1)))
        matrix = TFIDF(matrix, IDF).tolist()
    else:
        for idx, key in enumerate(list(d.keys()) +['Cos_Sim']):
            columns.update({idx:key})
    temp = []
    for i in range(len(matrix)):
        temp.append(matrix[i] + [fn(matrix[0], matrix[i])])
    df = pd.DataFrame(temp).rename(columns=columns)
    df.reset_index(drop=True, inplace=True)
    df.index = index
    return Markdown(df.to_markdown())

def cosine_similiraty(cand, ref):
    fn = lambda x: (np.sqrt(np.sum(np.power(x, 2))))
    return np.dot(cand, ref) / (fn(cand) * fn(ref))
```

```python
# Cosine similiraty

table = []
for i in range(0, 4):
    table.append(gen_table(*gen_matrix([extract(cand_1)[i],
                        extract(ref_1)[i], extract(ref_2)[i]]), cosine_similiraty))
```

### Example

Candidate

`{{cand_1}}`

$\xrightarrow[\text{}]{\text{ Preprocessing }}$ `{{preprocess(cand_1)}}`
$\xrightarrow[\text{}]{\text{Extract 2-gram}} $ `{{extract(cand_1)[1]}}`

Reference_1

`{{ref_1}}`

$\xrightarrow[\text{}]{\text{ Preprocessing }}$ `{{preprocess(ref_1)}}`
$\xrightarrow[\text{}]{\text{Extract 2-gram}}$ `{{extract(ref_1)[1]}}`

Reference_2

`{{ref_2}}`

$\xrightarrow[\text{}]{\text{ Preprocessing }}$ `{{preprocess(ref_2)}}`
$\xrightarrow[\text{}]{\text{Extract 2-gram}}$ `{{extract(ref_2)[1]}}`

### Doc-Term Matrix with Cosine Similiraty

`{{table[0]}}`

`{{table[1]}}`

`{{table[2]}}`

### CIDEr_n Formula

$
\begin{align}
\cr \quad
\textit{CIDEr_n}(\textit{candidate},
                 \textit{references}) =
    \frac{1}{M}\sum\limits_{i=1}^{M}
    \frac{ g^n(\textit{candidate}) \cdot g^n(\textit{references}) }
         {\lVert g^n(\textit{candidate}) \rVert \times \lVert g^n(\textit{references}) \rVert},
\text{ where } g^n(x) \text{ is  } \textit{TF-IDF} \text{ weight of n-gram in sentence } x \text{.}
\end{align}
$

$
$

```python
def CIDEr(cand, ref):
    fn = lambda x: (np.sqrt(np.sum(np.power(x, 2))))
    return np.dot(cand, ref) / (fn(cand) * fn(ref))

table = []
for i in range(0, 3):
    table.append(gen_table(*gen_matrix([extract(cand_1)[i],
                        extract(ref_1)[i], extract(ref_2)[i]]), CIDEr))
```

### Doc-Term TF-IDF Matrix with CIDEr-n

`{{table[0]}}`

`{{table[1]}}`

`{{table[2]}}`


# Replication of Carneiro, Heckman, & Vytlacil's (2011) *Local Instrumental Variables* approach

In this notebook, I reproduce the semiparametric results from

> Carneiro, P., Heckman, J. J., & Vytlacil, E. J. (2011). [Estimating marginal returns to education.](https://pubs.aeaweb.org/doi/pdfplus/10.1257/aer.101.6.2754) *American Economic Review, 101*(6), 2754-81.

The authors analyze the returns to college for white males born between 1957 and 1963 using data from the National Longitudinal Survey of Youth 1979. The authors provide some [replication material]((https://pubs.aeaweb.org/doi/pdfplus/10.1257/aer.101.6.2754)) on their website but do not include geographic identifiers. Therefore, we make use of a mock data merging background characteristics and local data randomly.

In a future update, the semiparametric estimation method will be included in the open-source package *grmpy* for the simulation and estimation of the generalized Roy model in Python. Currently, *grmpy* is limited to the estimation of a parametric normal version of the generalized Roy model.
 For more, see the [online documentation](https://grmpy.readthedocs.io/en/develop/).

## 0) Imports

```python
import numpy as np

from tutorial_semipar_auxiliary import plot_semipar_mte

from grmpy.estimate.estimate import fit

import warnings
warnings.filterwarnings('ignore')

%load_ext autoreload
%autoreload 2
```

## 1) The LIV Framework

The method of Local Instrumental Variables (LIV) is based on the generalized Roy model, which is characterized by the following equations:

\begin{align*}
 &\textbf{Potential Outcomes} & & \textbf{Choice}  &\\
 & Y_1 = \beta_1 X + U_{1} &  & I = Z \gamma - V &\\
 & Y_0 = \beta_0 X + U_{0} &  & D_i = \left\{
\begin{array}{ll}
1 & if \ I > 0 \\
0 & if \ I \leq 0\\
\end{array}
\right.
&&&&\\
& \textbf{Observed Outcome} &&&\\
& Y = D Y_1 + (1-D) Y_0 &&&
\end{align*}

We work with the linear-in-the-parameters version of the generalized Roy model:

\begin{align}
E(Y|X = \overline{x}, P(Z) = p) =  \overline{x} \beta_0 + p \overline{x} (\beta_1 - \beta_0) + K(p),
\end{align}

where $K(p) = E(U_1 - U_0 | D = 1, P(Z) = p)$ is a nonlinear function of $p$ that captures heterogeneity along the unobservable resistance to treatment $u_D$.

In addition, assume that $(X, Z)$ is independent of $\{U_1, U_0, V\}$. Then, the MTE is

1) additively separable in $X$ and $U_D$, which means that the shape of the MTE is independent of $X$, and

2) identified over the common support of $P(Z)$, unconditional on $X$.

The common support, $P(Z)$, plays a crucial role for the identification of the MTE.
It denotes the probability of going to university ($D=1$). Common support is defined as the intersection of the support of $P(Z)$ given $D = 1$ and the support of $P(Z)$ given $D = 0$. i.e., those evaluations of $P(Z)$ for which we obtain positive frequencies in both subsamples. We will plot it below. The larger the common support, the larger the region over which the MTE is identified.

The LIV estimator, $\Delta^{LIV}$, is derived as follows (Heckman and Vytlacil [2001](https://www.aeaweb.org/articles?id=10.1257/aer.91.2.107), [2005](https://www.jstor.org/stable/3598865?seq=1#page_scan_tab_contents)):

\begin{equation}
\begin{split}
\Delta^{LIV} (\overline{x}, u_D) &= \frac{\partial E(Y|X = \overline{x}, P(Z) = p)}{\partial p}  \bigg\rvert_{p = u_D} \\
& \\
&= \overline{x}(\beta_1 - \beta_0) + E(U_1 - U_0 | U_D = u_D) \\
&\\
& = \underbrace{\overline{x}(\beta_1 - \beta_0)}_{\substack{observable \\ component}} + \underbrace{\frac{\partial K}{\partial p} \bigg\rvert_{p = u_D}}_{\substack{k(p): \ unobservable \\ component}} = MTE(\overline{x}, u_D)
\end{split}
%\frac{[E(U_1 - U_0 | U_D \leq p] p}{\partial p}  \bigg\rvert_{p = u_D}
%E(U_1 - U_0 | U_D = u_D)
\end{equation}

Since we do not make any assumption about the functional form of the unobservables, we estimate $k(p)$ non-parametrically. In particualr, $k(p)$ is the first derivative of a locally quadratic kernel regression.

## 3) The Initialization File

For the semiparametric estimation, we need information on the following sections:

* __ESTIMATION__: Specify the dependent (wage) and indicator variable (treatment dummy) of the input data frame.
For the estimation of the propensity score $P(Z)$, we choose a probability model, here logit. Furthermore, we select 30 bins to determine the common support in the treated and untreated subsamples. For the locally quadratic regression, we follow the specification of [Carneiro et al. (2011)](https://pubs.aeaweb.org/doi/pdfplus/10.1257/aer.101.6.2754) and choose a bandwidth of 0.322. The respective gridsize for the locally quadratic regression is set to 500. [Fan and Marron (1994)](https://www.tandfonline.com/doi/abs/10.1080/10618600.1994.10474629) find that a gridsize of 400 is a good default for graphical analysis. Since the data set is large (1785 observations) and the kernel regression function has a very low runtime, we increase the gridsize to 500. Setting it to the default or increasing it even more does not affect the final MTE.

Note that the MTE identified by LIV consists of two components: $\overline{x}(\beta_1 - \beta_0)$ (which does not depend on $P(Z) = p$) and $k(p)$ (which does depend on $p$). The latter is estimated nonparametrically. The key "p_range" in the initialization file specifies the interval over which $k(p)$ is estimated. After the data outside the overlapping support are trimmed, the locally quadratic kernel estimator uses the remaining data to predict $k(p)$ over the entire "p_range" specified by the user. If "p_range" is larger than the common support, *grmpy* extrapolates the values for the MTE outside this region. Technically speaking, interpretations of the MTE are only valid within the common support. Here, we set "p_range" to [0.005, 0.995].

The other parameters in this section are set by default and, normally, do not need to be changed.

* __TREATED, UNTREATED, CHOICE__: In this section, the variables of the outcome equations (treated, untreated) and the college decision (choice) are specified.

* __DIST__: The distribution of the unobservables is not of relevance in the semiparametric apporach and can be ignored.

```python
%%file files/tutorial_semipar.yml
---
ESTIMATION:
    file: data/aer-replication-mock.pkl
    dependent: wage
    indicator: state
    semipar: True
    show_output: True
    logit: True
    nbins: 30
    bandwidth: 0.322
    gridsize: 500
    trim_support: True
    reestimate_p: False
    rbandwidth: 0.05
    derivative: 1
    degree: 2
    ps_range: [0.005, 0.995]
TREATED:
    order:
    - exp
    - expsq
    - lwage5
    - lurate
    - cafqt
    - cafqtsq
    - mhgc
    - mhgcsq
    - numsibs
    - numsibssq
    - urban14
    - lavlocwage17
    - lavlocwage17sq
    - avurate
    - avuratesq
    - d57
    - d58
    - d59
    - d60
    - d61
    - d62
    - d63
UNTREATED:
    order:
    - exp
    - expsq
    - lwage5
    - lurate
    - cafqt
    - cafqtsq
    - mhgc
    - mhgcsq
    - numsibs
    - numsibssq
    - urban14
    - lavlocwage17
    - lavlocwage17sq
    - avurate
    - avuratesq
    - d57
    - d58
    - d59
    - d60
    - d61
    - d62
    - d63
CHOICE:
    params:
    - 1.0
    order:
    - const
    - cafqt
    - cafqtsq
    - mhgc
    - mhgcsq
    - numsibs
    - numsibssq
    - urban14
    - lavlocwage17
    - lavlocwage17sq
    - avurate
    - avuratesq
    - d57
    - d58
    - d59
    - d60
    - d61
    - d62
    - d63
    - lwage5_17numsibs
    - lwage5_17mhgc
    - lwage5_17cafqt
    - lwage5_17
    - lurate_17
    - lurate_17numsibs
    - lurate_17mhgc
    - lurate_17cafqt
    - tuit4c
    - tuit4cnumsibs
    - tuit4cmhgc
    - tuit4ccafqt
    - pub4
    - pub4numsibs
    - pub4mhgc
    - pub4cafqt
DIST:
    params:
    - 0.1
    - 0.0
    - 0.0
    - 0.1
    - 0.0
    - 1.0
```

    Overwriting files/tutorial_semipar.yml

Note that I do not include a constant in the __TREATED, UNTREATED__ section. The reason for this is that in the semiparametric setup, $\beta_1$ and $\beta_0$ are determined by running a Double Residual Regression without an intercept:
	$$ e_Y =e_X \beta_0 \ + \ e_{X \ \times \ p} (\beta_1 - \beta_0) \ + \ \epsilon $$

where $e_X$, $e_{X \ \times \ p}$, and $e_Y$ are the residuals of a local linear regression of $X$, $X$ x $p$, and $Y$ on $\widehat{P}(Z)$.

We now proceed to our replication.

## 3) Estimation

Conduct the estimation based on the initialization file.

```python
rslt = fit('files/tutorial_semipar.yml', semipar=True)
```

The rslt dictionary contains information on the estimated parameters and the final MTE.

```python
list(rslt)
```

    ['quantiles', 'mte', 'mte_x', 'mte_u', 'mte_min', 'mte_max', 'X', 'b0', 'b1']

Before plotting the MTE, let's see what else we can learn.
For instance, we can account for the variation in $X$.

Note that we divide the MTE by 4 to investigate the effect of one additional year of college education.

```python
np.min(rslt['mte_min']) / 4, np.max(rslt['mte_max']) / 4
```

    (-0.570309872543266, 0.6420576895028951)

Next we plot the MTE based on the estimation results. As shown in the figure below, the replicated MTE gets very close to the original, but its 90 percent confidence bands are wider. This is due to the use of a mock data set which merges basic and local variables randomly. The bootsrap method, which is used to estimate the confidence bands, is sensitive to the discrepancies in the data.

```python
mte, quantiles = plot_semipar_mte(rslt, 'files/tutorial_semipar.yml', nbootstraps=250)
```

People with the highest returns to education (those who have low unobserved resistance $u_D$
) are more likely to go to college. Note that the returns vary considerably with $u_D$
. Low $u_D$ students have returns of up to 40% per year of college, whereas high $u_D$
 people, who would loose from attending college, have returns of approximately -18%.

The magnitude of total heterogeneity is probably even higher, as the MTE depicts the average gain of
college attendance at the mean values of X, i.e. $\bar{x} (\beta_1 - \beta_0)$.
Accounting for variation in $X$, we observe returns as high as 64% and as low as -57%.


# Fitting experimental data to thermodynamic models

Besides calculating and visualizing phase equilibria using known models, **pycalphad** also enables you to fit experimental data to a parameterized thermodynamic model. While pycalphad handles construction of the energy functions and equilibrium calculation, a separate module handles the numerical details of fitting disparate sources of data to the thermodynamic functions.

Here we consider the case study of the **Al-Mg-Zn** system. First, we use a TDB file as the setup for our model parameterizations. Note that not all of the parameters have not been filled in. These are what we will fit.

A+BT+CTln(T)+DT2+E/T+FT3

**Model parametrization:** Liang, H., Chen, S. L., & Chang, Y. A. (1997). A thermodynamic description of the Al-Mg-Zn system. _Metallurgical and Materials Transactions A_, 28(9), 1725-1734.

```python
%matplotlib inline

TDB_STR = """
$=============================================================================
$
$  Al-Mg-Zn System, almgzn.tdb
$  Parameterized by R. Otis from an Al-Cu-Mg-Zn database, 04-13-2015
$  Last updated: 01-07-1997 by H. Liang
$
$  Based on Al-Mg-Zn[96Liang]
$
$=============================================================================
$
 ELEMENT /-   ELECTRON_GAS              0.0000E+00  0.0000E+00  0.0000E+00!
 ELEMENT VA   VACUUM                    0.0000E+00  0.0000E+00  0.0000E+00!
 ELEMENT AL   FCC_A1                    2.6982E+01  4.5773E+03  2.8322E+01!
 ELEMENT MG   HCP_A3                    2.4305E+01  4.9980E+03  3.2671E+01!
 ELEMENT ZN   HCP_A3                    6.5380E+01  5.6567E+03  4.1631E+01!

 FUNCTION UN_ASS     2.98140E+02 0.0; 3.00000E+02  N !
 FUNCTION GHSERAL    2.98130E+02  -7976.15+137.071542*T-24.3671976*T*LN(T)
     -.001884662*T**2-8.77664E-07*T**3+74092*T**(-1);  7.00000E+02  Y
      -11276.24+223.02695*T-38.5844296*T*LN(T)+.018531982*T**2
     -5.764227E-06*T**3+74092*T**(-1);  9.33600E+02  Y
      -11277.683+188.661987*T-31.748192*T*LN(T)-1.234264E+28*T**(-9);
     2.90000E+03  N !
 FUNCTION GHSERMG    2.98130E+02  -8367.34+143.677875*T-26.1849782*T*LN(T)
     +4.858E-04*T**2-1.393669E-06*T**3+78950*T**(-1);  9.23000E+02  Y
      -14130.185+204.718543*T-34.3088*T*LN(T)+1.038192E+28*T**(-9);
     3.00000E+03  N !
 FUNCTION GHSERZN    2.98140E+02  -7285.787+118.470069*T-23.701314*T*LN(T)
   -.001712034*T**2-1.264963E-06*T**3;  6.92680E+02  Y
   -11070.559+172.34566*T-31.38*T*LN(T)+4.70514E+26*T**(-9);
     1.70000E+03  N !
$
 FUNCTION GALLIQ     2.98130E+02  +11005.553-11.840873*T+7.9401E-20*T**7
     +GHSERAL#;  9.33600E+02  Y
      +10481.974-11.252014*T+1.234264E+28*T**(-9)+GHSERAL#;
     2.90000E+03  N !
 FUNCTION GMGLIQ     2.98130E+02  +8202.24-8.83693*T-8.01759E-20*T**7
     +GHSERMG#;  9.23000E+02  Y
      +8690.32-9.39216*T-1.03819E+28*T**(-9)+GHSERMG#;  3.00000E+03  N !
 FUNCTION GZNLIQ     2.70000E+02  -128.517+108.176926*T-23.701314*T*LN(T)
     -.001712034*T**2-1.264963E-06*T**3-3.58652E-19*T**7;  6.92730E+02  Y
      -3620.474+161.608677*T-31.38*T*LN(T);  2.90000E+03  N !
$
 FUNCTION GBCCAL  2.98150E+02  +10083-4.813*T+GHSERAL#; 6.00000E+03  N !
 FUNCTION GBCCZN  2.98150E+02  +2886.96-2.5104*T+GHSERZN#;
		  6.00000E+03  N !
 FUNCTION GFCCZN  2.98150E+02  +2969.82-1.56968*T+GHSERZN#;
		  6.00000E+03  N !
 FUNCTION GFCCMG  2.98150E+02  2600-0.9*T+GHSERMG#;
		  6.00000E+03 N !

 TYPE_DEFINITION % SEQ *!
 DEFINE_SYSTEM_DEFAULT SPECIE 2 !
 DEFAULT_COMMAND DEF_SYS_ELEMENT VA !

 PHASE LIQUID:L %  1  1.0  !
    CONSTITUENT LIQUID:L :AL,MG,ZN :  !

   PARAMETER G(LIQUID,AL;0)  2.98130E+02  +GALLIQ#;
			     3.00000E+03  N REF: 0 !
   PARAMETER G(LIQUID,MG;0)  2.98130E+02  +GMGLIQ#;
			     3.00000E+03  N REF: 0 !
   PARAMETER G(LIQUID,ZN;0)  2.98130E+02  +GZNLIQ#;
			     3.00000E+03  N REF: 0 !
   PARAMETER G(LIQUID,AL,MG;0)  2.98150E+02  LIQALMG0A+LIQALMG0B*T;
				6.00000E+03  N REF: 0 !
   PARAMETER G(LIQUID,AL,ZN;0)  2.98150E+02  LIQALZN0A+LIQALZN0B*T;
				6.00000E+03   N REF: 3 !
   PARAMETER G(LIQUID,AL,ZN;1)  2.98150E+02  LIQALZN1A+LIQALZN1B*T;
				6.00000E+03   N REF: 3 !
   PARAMETER G(LIQUID,MG,ZN;0) 298.15  LIQMGZN0A+LIQMGZN0B*T+LIQMGZN0C*T*LN(T);
			       6000.0   N REF: 4 !
   PARAMETER G(LIQUID,MG,ZN;1) 298.15  LIQMGZN1A+LIQMGZN1B*T;
			       6000.0 N REF: 4 !
   PARAMETER G(LIQUID,MG,ZN;2) 298.15  LIQMGZN2A;
			       6000.0   N REF:   4 !
$
   PARAMETER G(LIQUID,AL,MG,ZN;0) 298.15 LIQALMGZN0A;
				  6000.0  N REF:  0 !
   PARAMETER G(LIQUID,AL,MG,ZN;1) 298.15 LIQALMGZN1A;
				  6000.0 N REF:  0 !
   PARAMETER G(LIQUID,AL,MG,ZN;2) 298.15 LIQALMGZN2A;
				  6000.0  N REF:  0 !

$----------------------------------------------------------------------
 TYPE_DEFINITION ' GES A_P_D FCC_A1 MAGNETIC  -3.0    2.80000E-01 !

 PHASE FCC_A1  %'  2 1   1 !
    CONSTITUENT FCC_A1  :AL%,MG,ZN : VA% :  !

   PARAMETER G(FCC_A1,AL:VA;0)  2.98130E+02  +GHSERAL#;
				2.90000E+03  N REF:  0 !
   PARAMETER G(FCC_A1,MG:VA;0)  2.98130E+02  +2600-.9*T+GHSERMG#;
				3.00000E+03  N REF: 0 !
   PARAMETER G(FCC_A1,ZN:VA;0)  2.98150E+02  +2969.82-1.56968*T+GHSERZN#;
				6.00000E+03   N REF: 1 !
   PARAMETER G(FCC_A1,AL,MG:VA;0)  2.98150E+02  FCCALMG0A+FCCALMG0B*T;
				   6.00000E+03   N REF: 0 !
   PARAMETER G(FCC_A1,AL,MG:VA;1)  2.98150E+02  FCCALMG1A+FCCALMG1B*T;
				   6.00000E+03   N REF: 0 !
   PARAMETER G(FCC_A1,AL,ZN:VA;0)  2.98150E+02  FCCALZN0A+FCCALZN0B*T;
				   6.00000E+03   N REF: 3 !
   PARAMETER G(FCC_A1,AL,ZN:VA;1)  2.98150E+02  FCCALZN1A+FCCALZN1B*T;
				   6.00000E+03   N REF: 3 !
   PARAMETER G(FCC_A1,AL,ZN:VA;2)  2.98150E+02  FCCALZN2A+FCCALZN2B*T;
				   6.00000E+03   N REF: 3 !
   PARAMETER G(FCC_A1,MG,ZN:VA;0)  2.98150E+02  FCCMGZN0A;
				   6000.0        N REF: 4 !
   PARAMETER G(FCC_A1,AL,MG,ZN:VA;0) 298.15 FCCALMGZN0A;
				     6000.0  N REF:  0 !

$----------------------------------------------------------------------

 TYPE_DEFINITION ( GES A_P_D HCP_A3 MAGNETIC  -3.0    2.80000E-01 !

 PHASE HCP_A3  %(  2 1   .5 !
    CONSTITUENT HCP_A3  :AL,MG%,ZN : VA% :  !

   PARAMETER G(HCP_A3,AL:VA;0)  2.98130E+02  +5481-1.8*T+GHSERAL#;
				2.90000E+03  N REF: 0 !
   PARAMETER G(HCP_A3,MG:VA;0)  2.98130E+02  +GHSERMG#;
				3.00000E+03  N REF:  0 !
   PARAMETER G(HCP_A3,ZN:VA;0)  2.98150E+02  +GHSERZN#;
				6.00000E+03   N REF: 1 !
   PARAMETER G(HCP_A3,AL,MG:VA;0)  2.98150E+02  HCPALMG0A+HCPALMG0B*T;
				   6.00000E+03   N REF: 0 !
   PARAMETER G(HCP_A3,AL,MG:VA;1)  2.98150E+02  HCPALMG1A;
				   6.00000E+03   N  REF: 0 !
   PARAMETER G(HCP_A3,AL,ZN:VA;0)  2.98150E+02  HCPALZN0A;
				   6.00000E+03   N REF: 3 !
   PARAMETER G(HCP_A3,MG,ZN:VA;0) 298.15  HCPMGZN0A+HCPMGZN0B*T;
				  6000.0 N REF: 4 !
   PARAMETER G(HCP_A3,MG,ZN:VA;1) 298.15  HCPMGZN1A+HCPMGZN1B*T;
				  6000.0 N REF: 4 !

$-------------------------------------------------------------------

  PHASE SIGMA  %  2 .66667    .33333 !
    CONSTITUENT SIGMA  :AL,ZN : MG :  !

   PARAMETER G(SIGMA,AL:MG;0)  2.98150E+02  +20133.73+6.3946*T
			       +.66667*GALLIQ#+.33333*GMGLIQ#;
			       3.00000E+03  N REF: 0 !
   PARAMETER G(SIGMA,ZN:MG;0)  2.98150E+02  -19389.65+13.644*T
			       +.66667*GZNLIQ#+.33333*GMGLIQ#;
			       3.00000E+03  N REF: 0 !
   PARAMETER G(SIGMA,AL,ZN:MG;0) 2.98150E+02  SIGALZN0A;
				 3.00000E+03  N  REF: 0 !
   PARAMETER G(SIGMA,AL,ZN:MG;1) 2.98150E+02  SIGALZN1A;
				 3.00000E+03  N  REF: 0 !

$----------------------------------------------------------------

 PHASE T  %  2 .605   .395 !
    CONSTITUENT T  :AL,ZN : MG :  !

   PARAMETER G(T,AL:MG;0)  2.98150E+02  -10910.836+8.71*T
			   +.605*GALLIQ#+.395*GMGLIQ#;
			   3.00000E+03  N REF: 0 !
   PARAMETER G(T,ZN:MG;0)  2.98150E+02  -15733.501+12.6746*T
			   +.605*GZNLIQ#+.395*GMGLIQ#;
			   3.00000E+03  N REF: 0 !
   PARAMETER G(T,AL,ZN:MG;0)  2.98150E+02  TALZN0A+TALZN0B*T;
			      3.00000E+03  N  REF: 0 !
   PARAMETER G(T,AL,ZN:MG;1)  2.98150E+02  TALZN1A;
			      3.00000E+03  N REF:   0 !
$-----------------------------------------------------------------

 PHASE EPS  %  2 1   1 !
    CONSTITUENT EPS  :AL,MG,ZN : VA :  !

   PARAMETER G(EPS,AL:VA;0)  2.98150E+02   5481-1.8*T+GHSERAL#;
			     6.00000E+03 N REF: 0 !
   PARAMETER G(EPS,MG:VA;0)  2.98150E+02  +10+GFCCMG#;
			     6.00000E+03 N REF: 0 !
   PARAMETER G(EPS,ZN:VA;0)  2.98150E+02  +GFCCZN#;
			     6.00000E+03   N REF: 0 !
   PARAMETER G(EPS,AL,ZN:VA;0)  2.98150E+02   EPSALZN0A;
				6.00000E+03   N REF: 0 !

$-----------------------------------------------------------------

 TYPE_DEFINITION & GES A_P_D BCC_A2 MAGNETIC  -1.0    4.00000E-01 !

  PHASE BCC_A2  %  2 1   3 !
    CONSTITUENT BCC_A2  :AL,ZN : VA% :  !

   PARAMETER G(BCC_A2,AL:VA;0)  2.98150E+02  +10083-4.813*T+GHSERAL#;
				6.00000E+03   N REF: 1 !
   PARAMETER G(BCC_A2,ZN:VA;0)  2.98150E+02  +2886.96-2.5104*T+GHSERZN#;
				6.00000E+03   N REF: 1 !
   PARAMETER G(BCC_A2,AL,ZN:VA;0)  2.98150E+02   BCCALZN0A;
				   6.00000E+03  N REF: 0 !

$------------------------------------------------------------------

PHASE GAMMA  %  2 1   1 !
    CONSTITUENT GAMMA  :AL,ZN : VA :  !

   PARAMETER G(GAMMA,AL:VA;0)  2.98150E+02  +GHSERAL#+10.0;
			       6.00000E+03  N  REF: 0 !
   PARAMETER G(GAMMA,ZN:VA;0)  2.98150E+02  +GHSERZN#+10.0;
			       6.00000E+03  N  REF: 0 !
   PARAMETER G(GAMMA,AL,ZN:VA;0)  2.98150E+02  GAMALZN0A;
				  6.00000E+03  N REF: 0 !
$---------------------------------------------------------------------

 PHASE PHI  %  3  2  5  2 !
    CONSTITUENT PHI  :AL : MG : ZN :  !

  PARAMETER G(PHI,AL:MG:ZN;0)    298.15  -169985.46+136.8*T
	+2*GALLIQ#+5*GMGLIQ#+2*GZNLIQ#;  6000.0 N REF: 0 !

$======================================================================
$              Binary Intermetallic Phases
$======================================================================

$---------------------------------------------------------------------

 PHASE ALMG_BETA  %  2 .615   .385 !
    CONSTITUENT ALMG_BETA  :AL : MG :  !

   PARAMETER G(ALMG_BETA,AL:MG;0)  2.98150E+02  -1451.1-1.907*T
	      +.615*GHSERAL#+.385*GHSERMG#;   6.00000E+03   N REF: 0 !

$---------------------------------------------------------------------

 PHASE ALMG_EPSILON  %  2 .56   .44 !
    CONSTITUENT ALMG_EPSILON  :AL : MG :  !
   PARAMETER G(ALMG_EPSILON,AL:MG;0)  2.98150E+02  ALMG_EPSILONALMG0A+ALMG_EPSILONALMG0B*T
	       +.56*GHSERAL#+.44*GHSERMG#;   6.00000E+03   N REF: 0 !

$----------------------------------------------------------------------

 PHASE ALMG_GAMMA  %  3 .4483   .1379   .4138 !
    CONSTITUENT ALMG_GAMMA  :MG : AL,MG : AL,MG :  !

   PARAMETER G(ALMG_GAMMA,MG:AL:AL;0)  2.98150E+02  ALMG_GAMMAMGALAL0A+ALMG_GAMMAMGALAL0B*T
	   +.5517*GHSERAL#+.4483*GHSERMG#;  6.00000E+03   N REF: 0 !
   PARAMETER G(ALMG_GAMMA,MG:MG:AL;0)  2.98150E+02  ALMG_GAMMAMGMGAL0A+ALMG_GAMMAMGMGAL0B*T
	   +.4138*GHSERAL#+.5862*GHSERMG#;   6.00000E+03   N REF: 0 !
   PARAMETER G(ALMG_GAMMA,MG:AL:MG;0)  2.98150E+02  ALMG_GAMMAMGALMG0A+ALMG_GAMMAMGALMG0B*T
	   +.1379*GHSERAL#+.8621*GHSERMG#;   6.00000E+03   N REF: 0 !
   PARAMETER G(ALMG_GAMMA,MG:MG:MG;0)  2.98150E+02  ALMG_GAMMAMGMGMG0A+GHSERMG#;
	   6.00000E+03   N REF: 0 !

$----------------------------------------------------------------------
$
 PHASE ALMG_ZETA  %  2 .525   .475 !
    CONSTITUENT ALMG_ZETA  :AL : MG :  !

   PARAMETER G(ALMG_ZETA,AL:MG;0)  2.98150E+02  -837.8-3.163*T
	    +.525*GHSERAL#+.475*GHSERMG#;   6.00000E+03   N REF: 0 !

$----------------------------------------------------------------------
$
 PHASE MG2ZN11  %  2 .153846   .846154 !
    CONSTITUENT MG2ZN11  :MG : ZN :  !

  PARAMETER G(MG2ZN11,MG:ZN;0)  298.15  -5823.05+1.94323*T
  +0.153846154*GHSERMG#+0.846153846*GHSERZN#;   6000.0   N REF: 4 !
$
$-----------------------------------------------------------------------
$
 PHASE MG2ZN3  %  2 .4   .6 !
    CONSTITUENT MG2ZN3  :MG : ZN :  !

  PARAMETER G(MG2ZN3,MG:ZN;0)  298.15  -11014.5+3.67151*T+0.4*GHSERMG#
			       +0.6*GHSERZN#;   6000.0   N REF: 4 !
$
$-----------------------------------------------------------------------
$
 PHASE MG7ZN3  %  2 .71831   .28169 !
    CONSTITUENT MG7ZN3  :MG : ZN :  !

  PARAMETER G(MG7ZN3,MG:ZN;0)  298.15  -4814.11+T+0.71831*GHSERMG#
			       +0.28169*GHSERZN#;   6000.0   N REF: 4 !
$
$-----------------------------------------------------------------------
$
 PHASE MGZN  %  2 .48   .52 !
    CONSTITUENT MGZN  :MG : ZN :  !

  PARAMETER G(MGZN,MG:ZN;0)  298.15  -9590.44+3.19681*T+0.48*GHSERMG#
			     +0.52*GHSERZN#;   6000.0   N REF: 4 !
$

$===========================================================================
"""

from pycalphad import Database, Model, binplot
from pycalphad.eq.energy_surf import energy_surf
import pycalphad.variables as v
import matplotlib
matplotlib.style.use('ggplot')
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from sympy import Symbol
import numpy as np

dbf = Database(TDB_STR)
```

## Prior to fitting

First, we'll show what the phase diagram looks like without any binary interaction parameters. In this diagram only the pure component lattice stabilities of each phase relative to their standard states are considered.

```python
liang96_parameters = {
    'LIQALMG0A':   -11200,
    'LIQALMG0B':   9.578,
    'LIQALZN0A':   10288.0,
    'LIQALZN0B':   -3.035,
    'LIQALZN1A':   -810.0,
    'LIQALZN1B':   0.471,
    'LIQMGZN0A':   -81439.68,
    'LIQMGZN0B':   518.25,
    'LIQMGZN0C':   -64.7144,
    'LIQMGZN1A':   2627.54,
    'LIQMGZN1B':   2.93061,
    'LIQMGZN2A':   -1673.28,
    'LIQALMGZN0A': -4094.48,
    'LIQALMGZN1A': -39973.74,
    'LIQALMGZN2A': -11337.52,
    'FCCALMG0A':   4945.7,
    'FCCALMG0B':   -1.318,
    'FCCALMG1A':   1594.4,
    'FCCALMG1B':   -.973,
    'FCCALZN0A':   6656.0,
    'FCCALZN0B':   1.615,
    'FCCALZN1A':   6793.0,
    'FCCALZN1B':   -4.982,
    'FCCALZN2A':   -5352.0,
    'FCCALZN2B':   7.261,
    'FCCMGZN0A':   18000,
    'FCCALMGZN0A': -20000,
    'HCPALMG0A':   4063.4,
    'HCPALMG0B':   -3.243,
    'HCPALMG1A':   -1642.1,
    'HCPALZN0A':   14620,
    'HCPMGZN0A':   -1600.77,
    'HCPMGZN0B':   7.62441,
    'HCPMGZN1A':   -3823.03,
    'HCPMGZN1B':   8.02575,
    'ALMG_EPSILONALMG0A': -768.3,
    'ALMG_EPSILONALMG0B': -3.119,
    'ALMG_GAMMAMGALAL0A': -1270,
    'ALMG_GAMMAMGALAL0B': -1.75,
    'ALMG_GAMMAMGMGAL0A': -2441.4,
    'ALMG_GAMMAMGMGAL0B': .219,
    'ALMG_GAMMAMGALMG0A': 1279.6,
    'ALMG_GAMMAMGALMG0B': 1.1606,
    'ALMG_GAMMAMGMGMG0A': 5000,
    'SIGALZN0A':   -23927.13,
    'SIGALZN1A':   9335.47,
    'TALZN0A':     -25696.19,
    'TALZN0B':     25,
    'TALZN1A':     9153.84,
    'EPSALZN0A':   10000,
    'BCCALZN0A':   20000,
    'GAMALZN0A':   75000
    }

phases_alzn = ['LIQUID', 'FCC_A1', 'HCP_A3', 'EPS']
phases_mgzn = ['LIQUID', 'FCC_A1', 'HCP_A3', 'EPS', 'SIGMA', 'T', 'MG2ZN11', 'MG2ZN3', 'MG7ZN3', 'MGZN']
phases_almg = ['LIQUID', 'FCC_A1', 'HCP_A3', 'EPS', 'SIGMA', 'T', 'ALMG_BETA', 'ALMG_GAMMA',
               'ALMG_EPSILON', 'ALMG_ZETA']

alzn_models = {name: Model(dbf, ['AL', 'ZN', 'VA'], name, parameters=liang96_parameters) for name in phases_alzn}
mgzn_models = {name: Model(dbf, ['MG', 'ZN', 'VA'], name, parameters=liang96_parameters) for name in phases_mgzn}
almg_models = {name: Model(dbf, ['AL', 'MG', 'VA'], name, parameters=liang96_parameters) for name in phases_almg}
almg_enthalpies = energy_surf(dbf, ['AL', 'MG', 'VA'], ['FCC_A1', 'HCP_A3'], output='MIX_HM', T=300.0, model=almg_models)
almg_enthalpies.sort('X(MG)', inplace=True) # points need to be ordered on x axis for line plot to work
almg_enthalpies_by_phase = dict(list(almg_enthalpies.groupby('Phase')))
zhong_2005_hcp_sqs = np.array([[0.25, 525], [0.5, 1174], [0.75, 852]])
```

```python
fig = plt.figure(figsize=(16, 24))
gs = gridspec.GridSpec(3, 2, hspace=0.4, wspace=0.5)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1])
ax3 = fig.add_subplot(gs[1, 0])
ax4 = fig.add_subplot(gs[1, 1])
ax5 = fig.add_subplot(gs[2, 0])
ax6 = fig.add_subplot(gs[2, 1])
binplot(dbf, ['AL', 'ZN', 'VA'], phases_alzn, 'X(ZN)', 300, 1000, ax=ax1, model=alzn_models)
binplot(dbf, ['MG', 'ZN', 'VA'], phases_mgzn, 'X(MG)', 300, 1000, ax=ax2, model=mgzn_models)
binplot(dbf, ['AL', 'MG', 'VA'], phases_almg, 'X(MG)', 300, 1000, ax=ax3, model=almg_models)
ax4.plot(almg_enthalpies_by_phase['HCP_A3']['X(MG)'], almg_enthalpies_by_phase['HCP_A3']['MIX_HM'])
ax4.scatter(zhong_2005_hcp_sqs[:, 0], zhong_2005_hcp_sqs[:, 1], label='Zhong 2005 (SQS)')
ax4.set_xlim((0,1))
ax4.set_ylim(bottom=0)
ax4.set_title('Al-Mg HCP Mixing Enthalpy at 300 K', fontsize=20)
ax4.set_xlabel('X(MG)', fontsize=20)
ax4.set_ylabel('Enthalpy of Mixing (J/mol-atom)', fontsize=20)
ax4.tick_params(axis='both', which='major', labelsize=14)
ax4.legend()
ax5.plot(almg_enthalpies_by_phase['FCC_A1']['X(MG)'], almg_enthalpies_by_phase['FCC_A1']['MIX_HM'])
#ax5.scatter(zhong_2005_hcp_sqs[:, 0], zhong_2005_hcp_sqs[:, 1], label='Zhong 2005 (SQS)')
ax5.set_xlim((0,1))
ax5.set_ylim(bottom=0)
ax5.set_title('Al-Mg FCC Mixing Enthalpy at 300 K', fontsize=20)
ax5.set_xlabel('X(MG)', fontsize=20)
ax5.set_ylabel('Enthalpy of Mixing (J/mol-atom)', fontsize=20)
ax5.tick_params(axis='both', which='major', labelsize=14)
#ax5.legend()
plt.show()
```

## Performing a fit

We perform the fit using the **fit_model** routine in pycalphad.

```python
import pandas as pd
from pycalphad.residuals import fit_model

liang96_alzn_parameters = {
    'LIQALZN0A':   10288.0,
    'LIQALZN0B':   -3.035,
    'LIQALZN1A':   -810.0,
    'LIQALZN1B':   0.471,
    'FCCALZN0A':   6656.0,
    'FCCALZN0B':   1.615,
    'FCCALZN1A':   6793.0,
    'FCCALZN1B':   -4.982,
    'FCCALZN2A':   -5352.0,
    'FCCALZN2B':   7.261,
    'HCPALZN0A':   14620,
    }
liang96_almg_parameters = {
    'LIQALMG0A':   -11200,
    'LIQALMG0B':   9.578,
    'FCCALMG0A':   4945.7,
    'FCCALMG0B':   -1.318,
    'FCCALMG1A':   1594.4,
    'FCCALMG1B':   -.973,
    'HCPALMG0A':   4063.4,
    'HCPALMG0B':   -3.243,
    'HCPALMG1A':   -1642.1,
    'ALMG_EPSILONALMG0A': -768.3,
    'ALMG_EPSILONALMG0B': -3.119,
    'ALMG_GAMMAMGALAL0A': -1270,
    'ALMG_GAMMAMGALAL0B': -1.75,
    'ALMG_GAMMAMGMGAL0A': -2441.4,
    'ALMG_GAMMAMGMGAL0B': .219,
    'ALMG_GAMMAMGALMG0A': 1279.6,
    'ALMG_GAMMAMGALMG0B': 1.1606,
    'ALMG_GAMMAMGMGMG0A': 5000,
}
from pycalphad.log import debug_mode
import lmfit
#debug_mode()
phase_eq = pd.read_csv('almg_test_phase_eq.csv')
phases = ['LIQUID', 'FCC_A1', 'HCP_A3', 'ALMG_EPSILON', 'ALMG_BETA', 'ALMG_GAMMA']
comps = ['AL', 'MG', 'VA']
start_models = {name: Model(dbf, comps, name, parameters=liang96_almg_parameters) for name in phases}
%time result, mi = fit_model(liang96_almg_parameters, phase_eq, dbf, comps, phases, maxfev=50)
fit_models = {name: Model(dbf, comps, name, parameters=result) for name in phases}
```

```python
print(lmfit.report_fit(mi.params))
#print(lmfit.conf_interval(mi))
```

```python
from pycalphad.plot.utils import phase_legend
legend_handles, colors = phase_legend(phases)
almg_start_df = energy_surf(dbf, ['AL', 'MG', 'VA'], phases, T=550.0, model=start_models)
almg_fit_df = energy_surf(dbf, ['AL', 'MG', 'VA'], phases, T=550.0, model=fit_models)
fig = plt.figure(figsize=(18,8))
gs = gridspec.GridSpec(1, 2, wspace=0.3, hspace=0.5)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1])
#ax3 = fig.add_subplot(gs[1, 0])
#ax4 = fig.add_subplot(gs[1, 1])
#almg_enthalpies = energy_surf(dbf, ['AL', 'MG', 'VA'], phases, output='MIX_HM', T=300.0, model=fit_models)
#almg_enthalpies.sort('X(MG)', inplace=True) # points need to be ordered on x axis for line plot to work
#almg_enthalpies_by_phase = dict(list(almg_enthalpies.groupby('Phase')))
for phase in phases:
    phase_eq[phase_eq['Phase'] == phase].plot(kind='scatter', x='X(MG)', y='T', ax=ax1, edgecolor=colors[phase],
                                                    s=50, xlim=(0,1), marker='s', linewidth=2, facecolor='none',
                                                    legend=False)
    phase_eq[phase_eq['Phase'] == phase].plot(kind='scatter', x='X(MG)', y='T', ax=ax2, edgecolor=colors[phase],
                                                    s=50, xlim=(0,1), marker='s', linewidth=2, facecolor='none',
                                                    legend=False)
#    ax3 = almg_start_df[almg_start_df['Phase'] == phase].plot(kind='scatter', x='X(MG)', y='GM', ax=ax3, color=colors[phase], s=1, xlim=(0,1))
#    ax4 = almg_fit_df[almg_fit_df['Phase'] == phase].plot(kind='scatter', x='X(MG)', y='GM', ax=ax4, color=colors[phase], s=1, xlim=(0,1))

ax1 = binplot(dbf, comps, phases, 'X(MG)', 300, 1000, ax=ax1, model=start_models, pdens=5000)
ax1.set_title('Al-Mg (Liang 1996)', fontsize=20)
ax2 = binplot(dbf, comps, phases, 'X(MG)', 300, 1000, ax=ax2, model=fit_models, pdens=5000)
ax2.get_legend().set_visible(False)
ax2 = ax2.set_title('Al-Mg leastsq fit', fontsize=20)
```

## Quantitative comparison of assessment values with fitted values

The difference can be partially understood by the fact that Liang's assessment also relies on thermochemical data while here we only fit to points on the phase diagram.

```python
for param_name, param_value in sorted(result.items()):
    percent_change = abs(param_value - liang96_almg_parameters[param_name]) / abs(liang96_almg_parameters[param_name])
    print('{:<20}: {:>8.2%} difference from original'.format(param_name, percent_change))
```

    ALMG_EPSILONALMG0A  :    0.20% difference from original
    ALMG_EPSILONALMG0B  :    0.50% difference from original
    ALMG_GAMMAMGALAL0A  :    4.03% difference from original
    ALMG_GAMMAMGALAL0B  :    4.82% difference from original
    ALMG_GAMMAMGALMG0A  :   12.48% difference from original
    ALMG_GAMMAMGALMG0B  :   27.46% difference from original
    ALMG_GAMMAMGMGAL0A  :    0.44% difference from original
    ALMG_GAMMAMGMGAL0B  :   22.82% difference from original
    ALMG_GAMMAMGMGMG0A  :    1.55% difference from original
    FCCALMG0A           :    4.23% difference from original
    FCCALMG0B           :   21.95% difference from original
    FCCALMG1A           :   28.40% difference from original
    FCCALMG1B           :   64.31% difference from original
    HCPALMG0A           :    9.55% difference from original
    HCPALMG0B           :   16.86% difference from original
    HCPALMG1A           :   33.23% difference from original
    LIQALMG0A           :    1.39% difference from original
    LIQALMG0B           :    2.30% difference from original

```python
from pycalphad.residuals import residual_thermochemical
from pycalphad.eq.utils import make_callable, generate_dof
from pycalphad import Model
import itertools
import lmfit

def fit_model_thermochemical(guess, data, dbf, comps, phases, **kwargs):
    """
    Fit model parameters to input data based on an initial guess for parameters.

    Parameters
    ----------
    guess : dict
        Parameter names to fit with initial guesses.
    data : list of DataFrames
        Input data to fit.
    dbf : Database
        Thermodynamic database containing the relevant parameters.
    comps : list
        Names of components to consider in the calculation.
    phases : list
        Names of phases to consider in the calculation.

    Returns
    -------
    (Dictionary of fit key:values), (lmfit minimize result)

    Examples
    --------
    None yet.
    """
    if 'maxfev' not in kwargs:
        kwargs['maxfev'] = 100
    fit_params = lmfit.Parameters()
    for guess_name, guess_value in guess.items():
        fit_params.add(guess_name, value=guess_value)
    param_names = fit_params.valuesdict().keys()
    fit_models = {name: Model(dbf, comps, name) for name in phases}
    fit_variables = dict()
    for name, mod in fit_models.items():
        fit_variables[name], _ = generate_dof(dbf.phases[name], mod.components)
    callables = {name: make_callable(mod.ast, itertools.chain(param_names, [v.T],
                                                              fit_variables[name])) \
                    for name, mod in fit_models.items()}
    out = lmfit.minimize(residual_thermochemical, fit_params,
                         args=(data, dbf, comps, fit_models, callables))
    return fit_params.valuesdict(), out
```

    :0: FutureWarning: IPython widgets are experimental and may change in the future.

```python
comps = sorted(['Fe', 'Ni', 'Cr', 'Al', 'Mg', 'Zn'])
import itertools
print(list('-'.join(sorted(x)) for x in itertools.combinations(comps, 2)))
print(list('-'.join(sorted(x)) for x in itertools.combinations(comps, 3) if set(x).issuperset(set(['Fe', 'Al'])))+['Al-Mg-Zn', 'Cr-Fe-Ni'])
print(list('-'.join(sorted(x)) for x in itertools.combinations(comps, 3)))
```

    ['Al-Cr', 'Al-Fe', 'Al-Mg', 'Al-Ni', 'Al-Zn', 'Cr-Fe', 'Cr-Mg', 'Cr-Ni', 'Cr-Zn', 'Fe-Mg', 'Fe-Ni', 'Fe-Zn', 'Mg-Ni', 'Mg-Zn', 'Ni-Zn']
    ['Al-Cr-Fe', 'Al-Fe-Mg', 'Al-Fe-Ni', 'Al-Fe-Zn', 'Al-Mg-Zn', 'Cr-Fe-Ni']
    ['Al-Cr-Fe', 'Al-Cr-Mg', 'Al-Cr-Ni', 'Al-Cr-Zn', 'Al-Fe-Mg', 'Al-Fe-Ni', 'Al-Fe-Zn', 'Al-Mg-Ni', 'Al-Mg-Zn', 'Al-Ni-Zn', 'Cr-Fe-Mg', 'Cr-Fe-Ni', 'Cr-Fe-Zn', 'Cr-Mg-Ni', 'Cr-Mg-Zn', 'Cr-Ni-Zn', 'Fe-Mg-Ni', 'Fe-Mg-Zn', 'Fe-Ni-Zn', 'Mg-Ni-Zn']

```python

```