# 1 Introduction

### 1-1 Quantum Simulation
One of the leading uses for quantum computers will be to simulate quantum systems such as molecules or engineered materials. Actually executing a quantum simulation on a current quantum computer, however, can be difficult and error prone. Your objective for this Open Science Prize is to significantly improve the state-of-the-art for quantum simulation (details in Section 2).

The quantum system you will simulate is a quantum spin-1/2 model, where each qubit represents a quantum [spin-1/2](https://en.wikipedia.org/wiki/Spin-1/2) particle in a 1D chain. Quantum spin models have some amazing properties and uses. Computationally speaking, [certain optimization problems can be mapped](https://en.wikipedia.org/wiki/Spin-1/2) to spin models and hence minimized on a quantum computer. Physically speaking, spin models show a variety of quantum behavior such as large entangled states, quantum phases of matter, quantum many-body affects, and many other open questions in physics research.

There are many different spin models, but we will focus on the [$XXX$ Heisenberg spin model](https://en.wikipedia.org/wiki/Quantum_Heisenberg_model#XXX_model). Though it is not as well known as the [transverse-field Ising model](https://en.wikipedia.org/wiki/Transverse-field_Ising_model), the $XXX$ Heisenberg model is of practical and theoretical interest. It can also be difficult to simulate, which is where you come in.

The notebook is organized as follows:
1) Section 1 (labeled 1-X) provides you with brief introductions to the $XXX$ Heisenberg model, time evolving a Hamiltonian, and decomposing a time evolution operator into gates for a quantum computer to execute.
2) Section 2 (labeled 2-X) outlines the specific requirements and boundaries associated with completing and competing in the Open Science Prize. Continuing from Section 1, the notebook showns an example solution.

```python
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams.update({'font.size': 16})  # enlarge matplotlib fonts

# Import qubit states Zero (|0>) and One (|1>), and Pauli operators (X, Y, Z)
from qiskit.opflow import Zero, One, I, X, Y, Z

# Suppress warnings
import warnings
warnings.filterwarnings('ignore')
```

### 1-2 The $XXX$ Heisenberg Spin Model

To simulate a quantum system, we first need to define how the quantum particles that make up the system interact with each other. In quantum physics, this is done by defining a [Hamiltonian operator](https://en.wikipedia.org/wiki/Hamiltonian_(quantum_mechanics)). For this Open Science Prize, you will work with the $XXX$ Heisenberg Hamiltonian $H_{\text{Heis}}$ as defined below

$$
\begin{equation}
H_{\text{Heis}} = \sum_{\langle ij \rangle}^{N} J \left(\sigma_x^{(i)}\sigma_x^{(j)} + \sigma_y^{(i)}\sigma_y^{(j)} + \sigma_z^{(i)}\sigma_z^{(j)}\right).
\end{equation}
$$

$N$ is the number of spin-1/2 particles in model. The operators $\sigma_x$, $\sigma_y$, and $\sigma_z$ are [Pauli operators](https://en.wikipedia.org/wiki/Pauli_matrices) where the $i$ and $j$ superscripts label which qubit they act on. For example, $\sigma_x^{(1)}$ would be the $\sigma_x$ operator acting on only qubit 1 (which is the 2nd qubit since indexing starts at 0). This version of the general Heisenberg spin model is called $XXX$ because the same $J$ value multiplies each pair of Pauli operators. The sum notation $\langle ij \rangle$ means the sum is over nearest neighbors (only qubits next to each other interact), and $J$ is the interaction strength, which we will set $J=1$.

You will work with the explicit case of $N=3$ with the 3 spins arranged in a line. Written out fully, the Hamiltonian is
$$
H_{\text{Heis3}} = \sigma_x^{(0)}\sigma_x^{(1)} + \sigma_x^{(1)}\sigma_x^{(2)} + \sigma_y^{(0)}\sigma_y^{(1)} + \sigma_y^{(1)}\sigma_y^{(2)} + \sigma_z^{(0)}\sigma_z^{(1)} + \sigma_z^{(1)}\sigma_z^{(2)}.
$$
Now that we have a Hamiltonian ($H_{\text{Heis3}}$), we can use it to determine how the quantum system of 3 spin-1/2 particles changes in time.

*See the supplementary material for further details on $H_{\text{Heis}}$, $H_{\text{Heis3}}$ and its matrix repsentation.*

```python
# Returns the matrix representation of the XXX Heisenberg model for 3 spin-1/2 particles in a line
def H_heis3():
    # Interactions (I is the identity matrix; X, Y, and Z are Pauli matricies; ^ is a tensor product)
    XXs = (I^X^X) + (X^X^I)
    YYs = (I^Y^Y) + (Y^Y^I)
    ZZs = (I^Z^Z) + (Z^Z^I)

    # Sum interactions
    H = XXs + YYs + ZZs

    # Return Hamiltonian
    return H
```

### 1-3 Time Evolution

Knowing the Hamiltonian, we can determine how quantum states of that system evolve in time by solving the [Schrödinger equation](https://en.wikipedia.org/wiki/Schrödinger_equation)
$$
i\hbar \dfrac{d}{dt}|\psi(t)\rangle = H |\psi(t)\rangle
$$

For simplicity, let's set $\hbar = 1$. We know that the Hamiltonian $H_{\text{heis3}}$ does not change in time, so the solution to the Schrödinger equation is an exponential of the Hamiltonian operator
$$
\begin{align}
U_{\text{Heis3}}(t) &= e^{-it H_\text{Heis3}} = \exp\left(-it H_\text{Heis3}\right) \\
U_{\text{Heis3}}(t) &= \exp\left[-it \sum_{\langle ij \rangle}^{N=3} \left(\sigma_x^{(i)}\sigma_x^{(j)} + \sigma_y^{(i)}\sigma_y^{(j)} + \sigma_z^{(i)}\sigma_z^{(j)}\right) \right] \\
U_{\text{Heis3}}(t) &= \exp\left[-it \left(\sigma_x^{(0)}\sigma_x^{(1)} + \sigma_x^{(1)}\sigma_x^{(2)} + \sigma_y^{(0)}\sigma_y^{(1)} + \sigma_y^{(1)}\sigma_y^{(2)} + \sigma_z^{(0)}\sigma_z^{(1)} + \sigma_z^{(1)}\sigma_z^{(2)}\right) \right]
\end{align}
$$.

Now that we have the time evolution operator $U_{\text{Heis3}}(t)$, we can simulate changes in a state of the system ($|\psi(t)\rangle$) over time $|\psi(t)\rangle = U_{\text{Heis3}}(t)|\psi(t=0)\rangle$.

*See the supplementary material for more information on exponentiating an operator or matrix.*

```python
# Returns the matrix representation of U_heis3(t) for a given time t assuming an XXX Heisenberg Hamiltonian for 3 spins-1/2 particles in a line
def U_heis3(t):
    # Compute XXX Hamiltonian for 3 spins in a line
    H = H_heis3()

    # Return the exponential of -i multipled by time t multipled by the 3 spin XXX Heisenberg Hamilonian
    return (t * H).exp_i()
```

### 1-4 Classical Simulation of $H_{\text{Heis3}}$

Now that we have the operator $U_{\text{Heis3}}(t)$ which describes how any state of the quantum system changes in time, we can simulate the $XXX$ Heisenberg spin model! Just for fun, let's simulate the system using a classical computer first.

(Fun fact: Notice the size of $U_{\text{Heis3}}(t)$ in matrix form. It's representated by an $8$x$8$ matrix. This is because there are $2^3=8$ states in the $N=3$ system. If the simulation were of $50$ particles ($N=50$), $U_{\text{Heis}}(t)$ would be approximately $10^{15}$x$10^{15}$! Well beyond the capacity of today's computers.)

```python
# Define array of time points
ts = np.linspace(0, np.pi, 100)

# Define initial state |110>
initial_state = One^One^Zero

# Compute probability of remaining in |110> state over the array of time points
 # ~initial_state gives the bra of the initial state (<110|)
 # @ is short hand for matrix multiplication
 # U_heis3(t) is the unitary time evolution at time t
 # t needs to be wrapped with float(t) to avoid a bug
 # (...).eval() returns the inner product <110|U_heis3(t)|110>
 #  np.abs(...)**2 is the modulus squared of the innner product which is the expectation value, or probability, of remaining in |110>
probs_110 = [np.abs((~initial_state @ U_heis3(float(t)) @ initial_state).eval())**2 for t in ts]

# Plot evolution of |110>
plt.plot(ts, probs_110)
plt.xlabel('time')
plt.ylabel(r'probability of state $|110\rangle$')
plt.title(r'Evolution of state $|110\rangle$ under $H_{Heis3}$')
plt.grid()
plt.show()
```

### 1-5 Decomposition of $U_{\text{Heis3}}(t)$ Into Quantum Gates

To execute $U_{\text{Heis3}}(t)$ on a [circuit model](https://qiskit.org/documentation/apidoc/circuit.html) quantum computer, we must decompose $U_{\text{Heis3}}(t)$ into a product of single and two-qubit gates that are native to that quantum computer. There are a [variety of techniques](https://en.wikipedia.org/wiki/Hamiltonian_simulation) to do this. For this Open Science Prize, you will only use [Trotterization](https://en.wikipedia.org/wiki/Hamiltonian_simulation#Product_Formulas) (which is sometimes refered to as Suzuki-Trotterization decomposition, Trotter formulas, product formulas, and other names).

Below, we show an example Trotterization as outlined in \[1-2\].

Since the [Pauli operators do not commute](https://en.wikipedia.org/wiki/Pauli_matrices#Commutation_relations) with each other, the exponential $U_{\text{Heis3}}(t)$ cannot be split into a product of simpler exponentials. However, we can approximate $U_{\text{Heis3}}(t)$ as a product of simpler exponentials through Trotterization. Consider a subsystem of 2 spin-1/2 particles within the larger 3 spin system. The Hamiltonian on spins $i$ and $j$ ($i,j \in \{0,1,2\}$) would be $H^{(i,j)}_{\text{Heis2}} = \sigma_x^{(i)}\sigma_x^{(j)} + \sigma_y^{(i)}\sigma_y^{(j)} + \sigma_z^{(i)}\sigma_z^{(j)}$. Rewritting $U_{\text{Heis3}}(t)$ in terms of the two possible subsystems within the total $N=3$ system you will simulate,

$$
U_{\text{Heis3}}(t) = \exp\left[-i t \left(H^{(0,1)}_{\text{Heis2}} + H^{(1,2)}_{\text{Heis2}} \right)\right].
$$

$H^{(0,1)}_{\text{Heis2}}$ and $H^{(1,2)}_{\text{Heis2}}$ do not commute, so $U_{\text{Heis3}}(t) \neq \exp\left(-i t H^{(0,1)}_{\text{Heis2}}\right) \exp\left(-i t H^{(1,2)}_{\text{Heis2}} \right)$. But, this product decomposition can be approximated with Trotterization which says $U_{\text{Heis3}}(t)$ is approximately a short evolution of $H^{(0,1)}_{\text{Heis2}}$ (time = $t/n$) and followed by a short evolution of $H^{(1,2)}_{\text{Heis2}}$ (time = $t/n$) repeated $n$ times

$$
\begin{align}
U_{\text{Heis3}}(t) &= \exp\left[-i t \left(H^{(0,1)}_{\text{Heis2}} + H^{(1,2)}_{\text{Heis2}} \right)\right] \\
U_{\text{Heis3}}(t) &\approx \left[\exp\left(\dfrac{-it}{n}H^{(0,1)}_{\text{Heis2}}\right) \exp\left(\dfrac{-it}{n}H^{(1,2)}_{\text{Heis2}} \right)\right]^n.
\end{align}
$$

$n$ is the number of Trotter steps, and as $n$ increases, the approximation becomes more accurate. (Note that how a untiary is split up into subsystems for Trotterization is not necessarily unique.) The decomposition goes further. Within each 2 spin subsystems, the Pauli operator pairs ($\sigma_x^{(i)}\sigma_x^{(j)}$, $\sigma_y^{(i)}\sigma_y^{(j)}$, and $\sigma_z^{(i)}\sigma_z^{(j)}$) commute. This means we can decompose the exponential of a subsystem Hamiltonian ($H^{(i,j)}_{\text{Heis2}}$) into a product of even simpler exponentials getting us closer to a gate implementation of $U_{\text{Heis3}}(t)$

$$
\begin{align}
U_{\text{Heis3}}(t) &\approx \left[\exp\left(\dfrac{-it}{n}H^{(0,1)}_{\text{Heis2}}\right) \exp\left(\dfrac{-it}{n}H^{(1,2)}_{\text{Heis2}} \right)\right]^n \\
U_{\text{Heis3}}(t) &\approx \left[\exp\left(\dfrac{-it}{n}\left(\sigma_x^{(0)}\sigma_x^{(1)} + \sigma_y^{(0)}\sigma_y^{(1)} + \sigma_z^{(0)}\sigma_z^{(1)}\right)\right) \exp\left(\dfrac{-it}{n}\left(\sigma_x^{(1)}\sigma_x^{(2)} + \sigma_y^{(1)}\sigma_y^{(2)} + \sigma_z^{(1)}\sigma_z^{(2)}\right)\right)\right]^{n} \\
U_{\text{Heis3}}(t) &\approx \left[\exp\left(\dfrac{-it}{n}\sigma_x^{(0)}\sigma_x^{(1)}\right) \exp\left(\dfrac{-it}{n}\sigma_y^{(0)}\sigma_y^{(1)}\right) \exp\left(\dfrac{-it}{n}\sigma_z^{(0)}\sigma_z^{(1)}\right) \exp\left(\dfrac{-it}{n}\sigma_x^{(1)}\sigma_x^{(2)}\right) \exp\left(\dfrac{-it}{n}\sigma_y^{(1)}\sigma_y^{(2)}\right) \exp\left(\dfrac{-it}{n}\sigma_z^{(1)}\sigma_z^{(2)}\right)\right]^{n}
\end{align}
$$

For simplicity, and to use more common notation, let's rename the products with $XX(2t) = \exp\left(-it \sigma_x\sigma_x\right)$, $YY(2t) = \exp\left(-it \sigma_y\sigma_y\right)$, and $ZZ(2t) = \exp\left(-it \sigma_z\sigma_z\right)$ and rewrite the Trotterized $U_{\text{Heis3}}(t)$
$$
U_{\text{Heis3}}(t) \approx \left[XX\left(\frac{2t}{n}\right)^{(0,1)} YY\left(\frac{2t}{n}\right)^{(0,1)} ZZ\left(\frac{2t}{n}\right)^{(0,1)} XX\left(\frac{2t}{n}\right)^{(1,2)} YY\left(\frac{2t}{n}\right)^{(1,2)} ZZ\left(\frac{2t}{n}\right)^{(1,2)} \right]^{n}
$$
And that's it! We have now approximately decomposed $U_{\text{Heis3}}(t)$ into two-qubit gates $XX(t)$, $YY(t)$, and $ZZ(t)$. These gates are not native to superconducting qubits, but in Section 2, they will be further decomposed into native single and two-qubit gates with *further details found in the supplementary material.*

\[1\] Y. Salathe, et al., *Digital Quantum Simulation of Spin Models with Circuit Quantum Electrodynamics*, [Phys. Rev. X **5**, 021027 (2015)](https://link.aps.org/doi/10.1103/PhysRevX.5.021027)

\[2\] F. Tacchino, et al., *Quantum Computers as Universal Quantum Simulators: State-of-the-Art and Perspectives*, [Adv. Quantum Technol. *3* 3 (2020)](https://doi.org/10.1002/qute.201900052) \[[free arxiv version](https://arxiv.org/abs/1907.03505)\]

# 2 The Open Science Prize
### 2-1 Contest Details
The Open Science Prize requirements and judging criteria can be found in the terms and conditions when registering on the [website](https://ibmquantumawards.bemyapp.com/#/event). Below is an outline of those requirements.

You are to time evolve the state $|110\rangle$ to time $t=\pi$ under the $XXX$ Heisenberg model Hamiltonian $H_{\text{heis3}}$ as defined in Section 1. This will be done on the ibmq_jakarta quantum computer. From left to right, the qubits in $|110\rangle$ correspond to ibmq_jakarta's qubits 5, 3, and 1. The evolution must be done using Trotterization with at least 4 steps ($n\geq4$). You will be judged on the clarity, originiality, and creativity of your solution, as well as the fidelity of the evolved state at time $t=\pi$ using [state tomography](https://qiskit.org/documentation/tutorials/noise/8_tomography.html). State tomography is a way of determining the exact quantum state--even phase information--at the end of a quantum circuit by running the circuit multiple times and measuring in different bases. The closer your final quantum state to the expected state, the higher the fidelity. Ibmq_jakarta's qubits 0, 2, 4, and 6 will not be evaluated by the state tomography calculation, so you may use them as you wish. You do not need to follow the same decomposition given in the example above, but your solution must be a Trotterization of $U_{\text{heis3}}(t)$ with at least 4 steps ($n\geq4$). You may find it useful to implement techniques that reduce errors and improve qubit coherence.

### 2-2 Import Qiskit

Feel free to import packages as needed. However, only free packages and those obtained through ```pip install``` or ```conda install``` are allowed.

```python
# Importing standard Qiskit modules
from qiskit import QuantumCircuit, QuantumRegister, IBMQ, execute, transpile
from qiskit.providers.aer import QasmSimulator
from qiskit.tools.monitor import job_monitor
from qiskit.circuit import Parameter

# Import state tomography modules
from qiskit.ignis.verification.tomography import state_tomography_circuits, StateTomographyFitter
from qiskit.quantum_info import state_fidelity

# suppress warnings
import warnings
warnings.filterwarnings('ignore')
```

### 2-3 Quantum Devices
Connect to IBM provider and connect to a real or simulated backend. Final submissions must be run on a real backend, but simulated devices are faster for debugging and testing.

```python
# load IBMQ Account data

IBMQ.save_account("TOKEN")  # replace TOKEN with your API token string (https://quantum-computing.ibm.com/lab/docs/iql/manage/account/ibmq)
provider = IBMQ.load_account()
```

```python
# Get backend for experiment
provider = IBMQ.get_provider(hub='ibm-q-community', group='ibmquantumawards', project='open-science-22')
jakarta = provider.get_backend('ibmq_jakarta')
# properties = jakarta.properties()

# Simulated backend based on ibmq_jakarta's device noise profile
sim_noisy_jakarta = QasmSimulator.from_backend(provider.get_backend('ibmq_jakarta'))

# Noiseless simulated backend
sim = QasmSimulator()
```

### 2-4 Decomposition of $U_{\text{Heis3}}(t)$ into Quantum Gates (Example)

The following circuit code is written based on the example given in Section 1. This is where you write your solution.

YOUR TROTTERIZATION GOES HERE -- START (beginning of example)

```python
# Parameterize variable t to be evaluated at t=pi later
t = Parameter('t')
```

```python
# Build a subcircuit for XX(t) two-qubit gate
XX_qr = QuantumRegister(2)
XX_qc = QuantumCircuit(XX_qr, name='XX')

XX_qc.ry(np.pi/2,[0,1])
XX_qc.cnot(0,1)
XX_qc.rz(2 * t, 1)
XX_qc.cnot(0,1)
XX_qc.ry(-np.pi/2,[0,1])

# Convert custom quantum circuit into a gate
XX = XX_qc.to_instruction()
```

```python
# Build a subcircuit for YY(t) two-qubit gate
YY_qr = QuantumRegister(2)
YY_qc = QuantumCircuit(YY_qr, name='YY')

YY_qc.rx(np.pi/2,[0,1])
YY_qc.cnot(0,1)
YY_qc.rz(2 * t, 1)
YY_qc.cnot(0,1)
YY_qc.rx(-np.pi/2,[0,1])

# Convert custom quantum circuit into a gate
YY = YY_qc.to_instruction()
```

```python
# Build a subcircuit for ZZ(t) two-qubit gate
ZZ_qr = QuantumRegister(2)
ZZ_qc = QuantumCircuit(ZZ_qr, name='ZZ')

ZZ_qc.cnot(0,1)
ZZ_qc.rz(2 * t, 1)
ZZ_qc.cnot(0,1)

# Convert custom quantum circuit into a gate
ZZ = ZZ_qc.to_instruction()
```

Combine subcircuits into single gate representing one ($n=1$) trotter step.

```python
# Combine subcircuits into a single multiqubit gate representing a single trotter step
num_qubits = 3

Trot_qr = QuantumRegister(num_qubits)
Trot_qc = QuantumCircuit(Trot_qr, name='Trot')

for i in range(0, num_qubits - 1):
    Trot_qc.append(ZZ, [Trot_qr[i], Trot_qr[i+1]])
    Trot_qc.append(YY, [Trot_qr[i], Trot_qr[i+1]])
    Trot_qc.append(XX, [Trot_qr[i], Trot_qr[i+1]])

# Convert custom quantum circuit into a gate
Trot_gate = Trot_qc.to_instruction()
```

YOUR TROTTERIZATION GOES HERE -- FINISH (end of example)

### 2-5 Trotterized Time Evolution

Time evolve the state $|110\rangle$ from $\theta=0$ to $\theta=\pi$ under $H_{\text{heis3}}$. Although jakarta has 7 qubits, only the qubits 1, 3, and 5 will be used for the state $|110\rangle$. From left to right, the qubits in $|110\rangle$ corresponds to the qubits 5, 3, and 1 on jakarta. State tomography will evaluate how well the quantum simulation matches to the expected state at $\theta= \pi$. (Note qubits 0, 2, 4, and 6 are not part of the state tomography calculation.)

You choose how many trotter steps (```trotter_steps```) to take in the simulation, but it must be at least 4. The code below is written to be generic enough for you may add to it if necessary for your solution.

```python
# The final time of the state evolution
target_time = np.pi

# Number of trotter steps
trotter_steps = 4  ### CAN BE >= 4

# Initialize quantum circuit for 3 qubits
qr = QuantumRegister(7)
qc = QuantumCircuit(qr)

# Prepare initial state (remember we are only evolving 3 of the 7 qubits on jakarta qubits (q_5, q_3, q_1) corresponding to the state |110>)
qc.x([3,5])  # DO NOT MODIFY (|q_5,q_3,q_1> = |110>)

# Simulate time evolution under H_heis3 Hamiltonian
for _ in range(trotter_steps):
    qc.append(Trot_gate, [qr[1], qr[3], qr[5]])

# Evaluate simulation at target_time (t=pi) meaning each trotter step evolves pi/trotter_steps in time
qc = qc.bind_parameters({t: target_time/trotter_steps})

# Generate state tomography circuits to evaluate fidelity of simulation
st_qcs = state_tomography_circuits(qc, [qr[1], qr[3], qr[5]])

# Display circuit for confirmation
# st_qcs[-1].decompose().draw()  # view decomposition of trotter gates
st_qcs[-1].draw()  # only view trotter gates
```

<pre style="word-wrap: normal;white-space: pre;background: #fff0;line-height: 1.1;font-family: "Courier New",Courier,monospace">                                                                    ░
q4_0: ──────────────────────────────────────────────────────────────░──────────
           ┌────────────┐┌────────────┐┌────────────┐┌────────────┐ ░ ┌─┐
q4_1: ─────┤0           ├┤0           ├┤0           ├┤0           ├─░─┤M├──────
           │            ││            ││            ││            │ ░ └╥┘
q4_2: ─────┤            ├┤            ├┤            ├┤            ├─░──╫───────
      ┌───┐│            ││            ││            ││            │ ░  ║ ┌─┐
q4_3: ┤ X ├┤1 Trot(π/4) ├┤1 Trot(π/4) ├┤1 Trot(π/4) ├┤1 Trot(π/4) ├─░──╫─┤M├───
      └───┘│            ││            ││            ││            │ ░  ║ └╥┘
q4_4: ─────┤            ├┤            ├┤            ├┤            ├─░──╫──╫────
      ┌───┐│            ││            ││            ││            │ ░  ║  ║ ┌─┐
q4_5: ┤ X ├┤2           ├┤2           ├┤2           ├┤2           ├─░──╫──╫─┤M├
      └───┘└────────────┘└────────────┘└────────────┘└────────────┘ ░  ║  ║ └╥┘
q4_6: ──────────────────────────────────────────────────────────────░──╫──╫──╫─
                                                                    ░  ║  ║  ║
c0: 3/═════════════════════════════════════════════════════════════════╩══╩══╩═
                                                                       0  1  2 </pre>

### 2-6 Execute
For your final submission, you will need to execute your solution on a real backend with 8 repetitions. For faster debugging, considering using a simulated backend and/or 1 repetition.

```python
shots = 8192
reps = 8
backend = sim_noisy_jakarta
# reps = 8
# backend = jakarta

jobs = []
for _ in range(reps):
    # execute
    job = execute(st_qcs, backend, shots=shots)
    print('Job ID', job.job_id())
    jobs.append(job)
```

    Job ID dbfdd4b2-92ee-41aa-9bae-d7c4b3b8023a
    Job ID bad4c9c5-7537-4162-9889-004670db8372
    Job ID 0816b842-8851-4e13-81db-dcc44c7d3956
    Job ID eebe6ade-2778-4ea3-89bf-1c473bc50c32
    Job ID 69b248a3-1401-45b6-b3ca-25a57fd4f0dc
    Job ID cde57a56-2510-4007-800a-7724b566df6c
    Job ID 0692fb4f-76ac-44ed-9987-e0434780756f
    Job ID 1e3327ce-06ca-4056-852d-c8ea2cdbe072

We can monitor the status of the jobs using Qiskit's job monitoring tools.

```python
for job in jobs:
    job_monitor(job)
    try:
        if job.error_message() is not None:
            print(job.error_message())
    except:
        pass
```

    Job Status: job has successfully run
    Job Status: job has successfully run
    Job Status: job has successfully run
    Job Status: job has successfully run
    Job Status: job has successfully run
    Job Status: job has successfully run
    Job Status: job has successfully run
    Job Status: job has successfully run

### 2-7 Results Analysis
Extract the results for the completed jobs and compute the state tomography fidelity for each repetition. You may choose to include other post-processing analyses here as well.

```python
# Compute the state tomography based on the st_qcs quantum circuits and the results from those ciricuits
def state_tomo(result, st_qcs):
    # The expected final state; necessary to determine state tomography fidelity
    target_state = (One^One^Zero).to_matrix()  # DO NOT MODIFY (|q_5,q_3,q_1> = |110>)
    print (result.get_counts())
    # Fit state tomography results
    tomo_fitter = StateTomographyFitter(result, st_qcs)
    rho_fit = tomo_fitter.fit(method='lstsq')
    # Compute fidelity
    fid = state_fidelity(rho_fit, target_state)
    return fid

# Compute tomography fidelities for each repetition
fids = []
for job in jobs:
    fid = state_tomo(job.result(), st_qcs)
    fids.append(fid)

print('state tomography fidelity = {:.4f} \u00B1 {:.4f}'.format(np.mean(fids), np.std(fids)))
```

    [{'001': 966, '100': 950, '110': 1083, '010': 1033, '101': 966, '000': 1005, '111': 1120, '011': 1069}, {'101': 924, '100': 925, '110': 982, '010': 1102, '001': 1050, '011': 1106, '000': 1029, '111': 1074}, {'101': 428, '010': 1707, '001': 1552, '110': 441, '100': 384, '000': 1515, '111': 470, '011': 1695}, {'010': 986, '110': 1006, '100': 977, '111': 1125, '000': 985, '011': 1048, '101': 1027, '001': 1038}, {'101': 1023, '001': 1033, '010': 998, '000': 1047, '011': 1089, '111': 1058, '110': 985, '100': 959}, {'101': 470, '010': 1667, '110': 459, '100': 392, '000': 1536, '011': 1634, '111': 462, '001': 1572}, {'010': 1633, '100': 419, '110': 1579, '111': 1671, '011': 1584, '000': 488, '001': 427, '101': 391}, {'001': 472, '101': 405, '000': 490, '011': 1647, '111': 1542, '010': 1655, '110': 1532, '100': 449}, {'001': 602, '101': 264, '111': 650, '000': 679, '011': 2580, '010': 2573, '110': 587, '100': 257}, {'100': 986, '110': 1099, '010': 1064, '111': 1063, '000': 990, '011': 1048, '001': 1047, '101': 895}, {'010': 1108, '001': 980, '101': 918, '100': 955, '110': 1069, '011': 1064, '000': 1049, '111': 1049}, {'101': 398, '001': 1529, '010': 1666, '111': 483, '000': 1513, '011': 1703, '110': 463, '100': 437}, {'001': 975, '010': 1014, '111': 1049, '011': 1027, '000': 1062, '110': 1036, '100': 1063, '101': 966}, {'101': 1001, '001': 1018, '010': 1037, '111': 1050, '000': 1053, '011': 1045, '110': 1018, '100': 970}, {'010': 1651, '100': 442, '110': 487, '001': 1505, '101': 433, '111': 442, '000': 1591, '011': 1641}, {'101': 404, '010': 1578, '110': 1670, '100': 479, '001': 435, '000': 468, '111': 1572, '011': 1586}, {'101': 454, '100': 454, '110': 1514, '010': 1615, '000': 473, '111': 1608, '011': 1630, '001': 444}, {'100': 237, '110': 621, '010': 2639, '000': 649, '011': 2564, '111': 618, '001': 608, '101': 256}, {'010': 483, '001': 1570, '101': 1506, '100': 433, '110': 479, '011': 1635, '111': 1662, '000': 424}, {'010': 475, '101': 1456, '001': 1586, '000': 470, '111': 1566, '011': 1738, '110': 438, '100': 463}, {'101': 595, '010': 618, '100': 250, '110': 253, '001': 2520, '000': 635, '011': 2678, '111': 643}, {'010': 456, '101': 1573, '001': 1575, '111': 1641, '011': 1600, '000': 463, '100': 442, '110': 442}, {'101': 1435, '001': 1629, '000': 461, '111': 1671, '011': 1642, '110': 465, '100': 438, '010': 451}, {'110': 256, '100': 238, '001': 2463, '101': 617, '010': 721, '011': 2616, '000': 614, '111': 667}, {'101': 569, '000': 254, '011': 2538, '111': 2612, '010': 687, '001': 632, '110': 667, '100': 233}, {'101': 609, '001': 648, '000': 260, '111': 2377, '011': 2727, '010': 680, '110': 640, '100': 251}, {'101': 358, '001': 885, '010': 969, '100': 140, '110': 388, '000': 349, '111': 887, '011': 4216}]
    [{'010': 1045, '001': 1040, '101': 993, '110': 1077, '100': 993, '111': 1039, '000': 1013, '011': 992}, {'101': 959, '001': 1025, '100': 936, '110': 1081, '010': 1072, '000': 991, '111': 996, '011': 1132}, {'010': 1672, '100': 413, '110': 455, '001': 1517, '101': 429, '111': 484, '000': 1574, '011': 1648}, {'010': 1013, '001': 986, '101': 1031, '100': 982, '110': 1044, '111': 1062, '000': 1021, '011': 1053}, {'001': 1027, '110': 1072, '100': 918, '010': 1107, '000': 1068, '111': 1015, '011': 1044, '101': 941}, {'101': 469, '001': 1618, '011': 1587, '111': 522, '000': 1642, '010': 1522, '110': 399, '100': 433}, {'001': 425, '010': 1597, '100': 416, '110': 1557, '101': 406, '000': 490, '111': 1714, '011': 1587}, {'101': 431, '000': 439, '011': 1682, '111': 1584, '100': 448, '110': 1522, '010': 1644, '001': 442}, {'101': 252, '010': 2556, '100': 257, '110': 630, '001': 605, '000': 627, '011': 2623, '111': 642}, {'011': 1011, '111': 1096, '000': 987, '010': 1084, '110': 1063, '100': 958, '001': 980, '101': 1013}, {'001': 971, '010': 1183, '011': 1060, '000': 1061, '111': 1005, '110': 1027, '100': 941, '101': 944}, {'101': 417, '010': 1570, '100': 431, '110': 506, '001': 1506, '000': 1571, '011': 1716, '111': 475}, {'101': 986, '001': 1005, '100': 1061, '110': 1079, '010': 1054, '111': 1030, '000': 1028, '011': 949}, {'010': 1120, '100': 1023, '110': 986, '111': 996, '000': 1065, '011': 1034, '001': 1017, '101': 951}, {'001': 1477, '101': 459, '010': 1623, '100': 440, '110': 492, '111': 460, '011': 1645, '000': 1596}, {'101': 421, '010': 1610, '110': 1628, '100': 454, '000': 487, '111': 1590, '011': 1557, '001': 445}, {'101': 412, '100': 393, '110': 1627, '010': 1734, '001': 396, '000': 447, '111': 1545, '011': 1638}, {'101': 252, '010': 2608, '111': 599, '000': 627, '011': 2552, '110': 647, '100': 260, '001': 647}, {'010': 494, '110': 463, '100': 427, '101': 1483, '001': 1498, '111': 1698, '011': 1643, '000': 486}, {'000': 473, '011': 1705, '111': 1599, '001': 1537, '101': 1511, '010': 487, '100': 416, '110': 464}, {'010': 679, '001': 2391, '101': 592, '111': 677, '011': 2729, '000': 612, '100': 255, '110': 257}, {'101': 1587, '001': 1559, '000': 496, '011': 1559, '111': 1628, '010': 467, '110': 442, '100': 454}, {'001': 1632, '101': 1548, '111': 1589, '011': 1615, '000': 470, '100': 430, '110': 429, '010': 479}, {'101': 636, '001': 2479, '110': 253, '100': 255, '011': 2594, '111': 608, '000': 660, '010': 707}, {'010': 698, '000': 259, '111': 2566, '011': 2528, '101': 602, '001': 645, '100': 246, '110': 648}, {'100': 238, '110': 649, '010': 670, '000': 248, '111': 2487, '011': 2579, '001': 649, '101': 672}, {'001': 842, '101': 340, '010': 988, '100': 134, '110': 411, '000': 325, '011': 4193, '111': 959}]
    [{'010': 979, '001': 994, '101': 1040, '000': 1008, '111': 1071, '011': 1110, '110': 1027, '100': 963}, {'101': 941, '010': 1059, '000': 1001, '111': 1085, '011': 1147, '110': 1000, '100': 958, '001': 1001}, {'100': 407, '110': 409, '010': 1654, '111': 492, '000': 1557, '011': 1705, '001': 1544, '101': 424}, {'101': 1103, '001': 1034, '010': 1047, '100': 916, '110': 1011, '000': 1056, '011': 1013, '111': 1012}, {'101': 1001, '001': 1059, '100': 976, '110': 1004, '010': 1068, '011': 1033, '000': 1000, '111': 1051}, {'010': 1567, '001': 1558, '101': 433, '110': 420, '100': 458, '000': 1640, '111': 479, '011': 1637}, {'101': 399, '010': 1558, '110': 1646, '100': 446, '001': 480, '000': 443, '111': 1603, '011': 1617}, {'001': 421, '010': 1635, '100': 426, '110': 1547, '000': 497, '011': 1714, '111': 1532, '101': 420}, {'001': 633, '010': 2586, '110': 612, '100': 242, '101': 274, '111': 678, '000': 643, '011': 2524}, {'001': 965, '011': 995, '111': 1090, '000': 957, '010': 1058, '110': 1089, '100': 1088, '101': 950}, {'010': 1155, '110': 1063, '100': 999, '001': 968, '101': 898, '111': 1051, '000': 1032, '011': 1026}, {'010': 1742, '111': 449, '011': 1589, '000': 1625, '001': 1484, '101': 375, '110': 487, '100': 441}, {'001': 990, '101': 963, '010': 1064, '110': 1049, '100': 1019, '011': 1044, '000': 1021, '111': 1042}, {'110': 1051, '100': 948, '101': 962, '001': 1060, '010': 1097, '011': 980, '000': 1056, '111': 1038}, {'101': 401, '010': 1635, '001': 1568, '000': 1596, '011': 1644, '111': 460, '110': 446, '100': 442}, {'001': 452, '101': 416, '100': 410, '110': 1601, '010': 1623, '111': 1631, '000': 463, '011': 1596}, {'001': 447, '101': 431, '010': 1714, '111': 1536, '000': 448, '011': 1559, '110': 1633, '100': 424}, {'101': 271, '010': 2622, '100': 279, '110': 593, '001': 587, '000': 633, '111': 662, '011': 2545}, {'010': 500, '000': 443, '011': 1630, '111': 1648, '101': 1516, '001': 1548, '110': 471, '100': 436}, {'010': 482, '111': 1641, '000': 450, '011': 1669, '001': 1573, '101': 1475, '110': 471, '100': 431}, {'010': 636, '001': 2420, '101': 613, '000': 630, '011': 2662, '111': 692, '100': 272, '110': 267}, {'101': 1617, '001': 1545, '000': 466, '011': 1562, '111': 1625, '010': 486, '110': 449, '100': 442}, {'100': 428, '110': 451, '001': 1655, '101': 1506, '010': 462, '111': 1561, '011': 1644, '000': 485}, {'100': 256, '110': 281, '101': 620, '001': 2492, '010': 680, '000': 671, '011': 2554, '111': 638}, {'000': 251, '111': 2575, '011': 2529, '010': 683, '100': 221, '110': 675, '101': 625, '001': 633}, {'100': 252, '110': 683, '010': 670, '000': 259, '011': 2619, '111': 2495, '101': 590, '001': 624}, {'101': 375, '001': 826, '100': 144, '110': 369, '010': 938, '111': 913, '000': 350, '011': 4277}]
    [{'101': 990, '001': 979, '110': 1041, '100': 985, '010': 1024, '011': 1068, '111': 1107, '000': 998}, {'101': 989, '010': 1053, '100': 942, '110': 1014, '001': 1027, '000': 1003, '111': 1048, '011': 1116}, {'010': 1631, '001': 1586, '101': 453, '110': 430, '100': 409, '000': 1524, '011': 1677, '111': 482}, {'010': 995, '001': 1006, '101': 1015, '111': 1086, '011': 1064, '000': 1029, '110': 1044, '100': 953}, {'001': 1071, '101': 1010, '100': 1030, '110': 983, '010': 1014, '111': 1016, '000': 994, '011': 1074}, {'101': 404, '100': 426, '110': 459, '010': 1599, '001': 1545, '011': 1628, '000': 1614, '111': 517}, {'101': 391, '010': 1584, '001': 450, '000': 482, '011': 1566, '111': 1668, '110': 1630, '100': 421}, {'001': 438, '000': 482, '011': 1618, '111': 1631, '010': 1590, '110': 1597, '100': 418, '101': 418}, {'101': 252, '001': 563, '010': 2617, '011': 2608, '000': 642, '111': 651, '110': 614, '100': 245}, {'010': 1055, '111': 1060, '011': 1049, '000': 1013, '110': 1084, '100': 936, '101': 1017, '001': 978}, {'001': 998, '100': 958, '110': 1074, '010': 1095, '101': 954, '000': 969, '011': 1087, '111': 1057}, {'010': 1639, '111': 475, '011': 1639, '000': 1608, '100': 414, '110': 495, '101': 416, '001': 1506}, {'000': 1025, '011': 1040, '111': 994, '100': 1061, '110': 1052, '010': 1067, '001': 984, '101': 969}, {'010': 1055, '100': 953, '110': 1045, '001': 1026, '101': 989, '000': 1070, '011': 1052, '111': 1002}, {'101': 436, '110': 435, '100': 428, '010': 1702, '001': 1573, '000': 1581, '111': 436, '011': 1601}, {'100': 450, '110': 1633, '010': 1612, '001': 457, '101': 380, '000': 484, '011': 1543, '111': 1633}, {'001': 390, '101': 447, '010': 1736, '000': 484, '111': 1597, '011': 1564, '110': 1586, '100': 388}, {'001': 602, '110': 669, '100': 243, '010': 2656, '101': 243, '000': 626, '111': 622, '011': 2531}, {'110': 465, '100': 444, '101': 1510, '001': 1555, '010': 429, '111': 1672, '000': 461, '011': 1656}, {'010': 487, '100': 431, '110': 469, '001': 1456, '101': 1500, '000': 464, '011': 1756, '111': 1629}, {'010': 677, '001': 2465, '101': 598, '100': 240, '110': 256, '111': 671, '011': 2663, '000': 622}, {'101': 1518, '001': 1572, '010': 486, '100': 424, '110': 489, '000': 444, '011': 1625, '111': 1634}, {'010': 450, '001': 1598, '101': 1508, '000': 489, '011': 1657, '111': 1556, '110': 480, '100': 454}, {'010': 684, '001': 2504, '101': 578, '000': 610, '011': 2602, '111': 656, '110': 275, '100': 283}, {'101': 633, '000': 252, '011': 2535, '111': 2612, '010': 663, '110': 589, '100': 241, '001': 667}, {'001': 641, '010': 651, '011': 2544, '111': 2527, '000': 282, '101': 625, '110': 666, '100': 256}, {'100': 121, '110': 370, '001': 877, '101': 385, '010': 915, '011': 4294, '000': 355, '111': 875}]
    [{'010': 1028, '100': 929, '110': 1057, '000': 1049, '011': 1061, '111': 1102, '001': 969, '101': 997}, {'001': 1043, '010': 1112, '110': 1082, '100': 888, '011': 1162, '000': 936, '111': 1027, '101': 942}, {'100': 402, '110': 452, '010': 1678, '111': 475, '000': 1554, '011': 1667, '001': 1560, '101': 404}, {'010': 1030, '001': 1035, '101': 1004, '110': 1024, '100': 946, '011': 1073, '000': 987, '111': 1093}, {'001': 1046, '010': 1049, '111': 1009, '000': 1045, '011': 1008, '110': 1073, '100': 977, '101': 985}, {'010': 1663, '100': 423, '110': 442, '111': 485, '000': 1554, '011': 1597, '001': 1573, '101': 455}, {'101': 410, '001': 470, '010': 1527, '110': 1566, '100': 460, '000': 456, '011': 1622, '111': 1681}, {'010': 1615, '101': 439, '001': 441, '110': 1609, '100': 426, '000': 455, '111': 1617, '011': 1590}, {'101': 248, '010': 2563, '111': 696, '000': 656, '011': 2583, '110': 594, '100': 268, '001': 584}, {'101': 941, '001': 962, '110': 1104, '100': 964, '010': 1125, '011': 1051, '111': 1048, '000': 997}, {'010': 1104, '001': 1008, '101': 934, '110': 1088, '100': 945, '000': 1043, '011': 1098, '111': 972}, {'101': 432, '010': 1710, '100': 383, '110': 498, '111': 446, '000': 1590, '011': 1635, '001': 1498}, {'101': 961, '010': 1052, '100': 1023, '110': 1024, '011': 1065, '000': 1060, '111': 995, '001': 1012}, {'110': 1044, '100': 1012, '001': 1023, '101': 953, '010': 1111, '000': 1065, '011': 979, '111': 1005}, {'100': 437, '110': 434, '010': 1638, '101': 443, '001': 1539, '111': 399, '000': 1627, '011': 1675}, {'010': 1560, '100': 450, '110': 1633, '000': 433, '111': 1607, '011': 1600, '101': 430, '001': 479}, {'101': 402, '001': 458, '010': 1715, '100': 430, '110': 1605, '111': 1530, '000': 478, '011': 1574}, {'101': 255, '010': 2572, '100': 238, '110': 664, '001': 635, '000': 640, '011': 2580, '111': 608}, {'101': 1501, '001': 1460, '000': 440, '111': 1698, '011': 1674, '010': 490, '100': 432, '110': 497}, {'010': 486, '001': 1607, '101': 1449, '011': 1715, '111': 1597, '000': 456, '100': 444, '110': 438}, {'101': 589, '111': 657, '011': 2652, '000': 640, '010': 632, '001': 2493, '110': 276, '100': 253}, {'001': 1548, '010': 489, '101': 1598, '110': 501, '100': 437, '111': 1593, '000': 421, '011': 1605}, {'010': 435, '101': 1526, '001': 1682, '000': 486, '011': 1655, '111': 1563, '100': 399, '110': 446}, {'101': 611, '001': 2485, '010': 630, '100': 255, '110': 289, '000': 636, '011': 2660, '111': 626}, {'101': 589, '001': 646, '010': 686, '111': 2544, '000': 286, '011': 2527, '110': 664, '100': 250}, {'000': 269, '011': 2576, '111': 2517, '010': 714, '101': 627, '001': 602, '110': 639, '100': 248}, {'101': 337, '010': 925, '100': 108, '110': 375, '001': 902, '000': 396, '011': 4277, '111': 872}]
    [{'101': 993, '001': 962, '111': 1147, '011': 1054, '000': 1022, '010': 1066, '100': 922, '110': 1026}, {'101': 956, '010': 1105, '011': 1076, '000': 1031, '111': 978, '001': 1069, '110': 980, '100': 997}, {'101': 421, '010': 1623, '001': 1560, '111': 469, '000': 1583, '011': 1686, '110': 465, '100': 385}, {'010': 984, '101': 986, '001': 980, '110': 1057, '100': 1012, '000': 1049, '011': 1030, '111': 1094}, {'101': 922, '001': 1049, '100': 983, '110': 1032, '010': 1041, '111': 1023, '000': 1070, '011': 1072}, {'100': 419, '110': 401, '010': 1588, '101': 437, '001': 1621, '000': 1639, '011': 1653, '111': 434}, {'101': 394, '001': 450, '010': 1541, '110': 1657, '100': 438, '000': 448, '011': 1613, '111': 1651}, {'001': 418, '000': 421, '011': 1700, '111': 1602, '010': 1641, '110': 1549, '100': 454, '101': 407}, {'101': 254, '010': 2593, '110': 614, '100': 229, '001': 593, '000': 630, '111': 678, '011': 2601}, {'010': 1090, '100': 1005, '110': 1087, '001': 974, '101': 956, '000': 1019, '111': 1072, '011': 989}, {'001': 937, '100': 923, '110': 1117, '010': 1097, '111': 1067, '000': 1055, '011': 1072, '101': 924}, {'110': 478, '100': 426, '010': 1669, '111': 450, '000': 1537, '011': 1678, '101': 440, '001': 1514}, {'101': 968, '010': 982, '001': 1006, '110': 1045, '100': 1082, '000': 1022, '111': 1048, '011': 1039}, {'001': 1012, '101': 924, '110': 1044, '100': 1013, '000': 1070, '111': 1016, '011': 1027, '010': 1086}, {'110': 470, '100': 441, '001': 1550, '101': 452, '010': 1638, '011': 1584, '000': 1627, '111': 430}, {'101': 424, '001': 441, '110': 1629, '100': 420, '010': 1634, '111': 1658, '000': 422, '011': 1564}, {'101': 426, '001': 425, '010': 1648, '111': 1594, '011': 1601, '000': 461, '100': 414, '110': 1623}, {'001': 658, '101': 268, '010': 2619, '111': 625, '011': 2527, '000': 619, '100': 238, '110': 638}, {'101': 1528, '001': 1490, '010': 440, '100': 422, '110': 455, '011': 1621, '000': 480, '111': 1756}, {'100': 435, '110': 449, '010': 496, '111': 1594, '011': 1701, '000': 466, '101': 1525, '001': 1526}, {'001': 2434, '101': 571, '110': 251, '100': 272, '010': 653, '000': 645, '011': 2657, '111': 709}, {'010': 494, '111': 1581, '011': 1607, '000': 509, '101': 1549, '001': 1544, '110': 453, '100': 455}, {'101': 1400, '001': 1671, '010': 507, '100': 431, '110': 468, '000': 472, '011': 1626, '111': 1617}, {'100': 263, '110': 234, '001': 2554, '101': 607, '010': 678, '111': 654, '000': 647, '011': 2555}, {'000': 282, '111': 2596, '011': 2497, '101': 613, '001': 672, '010': 659, '100': 232, '110': 641}, {'001': 617, '000': 276, '011': 2507, '111': 2543, '101': 603, '100': 267, '110': 665, '010': 714}, {'001': 886, '100': 143, '110': 358, '010': 948, '000': 340, '011': 4275, '111': 880, '101': 362}]
    [{'001': 980, '100': 923, '110': 1014, '010': 1048, '000': 1023, '111': 1076, '011': 1127, '101': 1001}, {'010': 1067, '111': 1011, '011': 1086, '000': 983, '100': 1018, '110': 994, '101': 968, '001': 1065}, {'001': 1520, '101': 460, '110': 435, '100': 435, '111': 479, '011': 1694, '000': 1507, '010': 1662}, {'001': 995, '000': 1037, '011': 1019, '111': 1068, '010': 997, '100': 976, '110': 1052, '101': 1048}, {'001': 1093, '010': 1107, '110': 994, '100': 914, '101': 988, '111': 1035, '000': 1037, '011': 1024}, {'010': 1561, '100': 446, '110': 405, '011': 1676, '000': 1548, '111': 499, '001': 1598, '101': 459}, {'001': 458, '101': 427, '010': 1596, '100': 451, '110': 1593, '000': 428, '111': 1631, '011': 1608}, {'101': 380, '010': 1620, '111': 1594, '000': 409, '011': 1615, '001': 479, '110': 1627, '100': 468}, {'101': 269, '010': 2519, '011': 2649, '111': 672, '000': 611, '100': 265, '110': 626, '001': 581}, {'010': 1063, '111': 1066, '000': 1093, '011': 1012, '001': 944, '101': 927, '110': 1060, '100': 1027}, {'010': 1101, '011': 1065, '000': 978, '111': 1069, '001': 994, '101': 956, '110': 1059, '100': 970}, {'101': 424, '001': 1493, '010': 1707, '111': 429, '011': 1691, '000': 1524, '110': 478, '100': 446}, {'001': 1043, '101': 1015, '110': 1016, '100': 1003, '010': 1056, '011': 1010, '111': 1049, '000': 1000}, {'001': 1063, '101': 975, '000': 1044, '011': 1049, '111': 998, '010': 1127, '110': 977, '100': 959}, {'100': 466, '110': 452, '010': 1634, '001': 1569, '101': 439, '000': 1579, '011': 1613, '111': 440}, {'001': 436, '010': 1601, '000': 467, '111': 1660, '011': 1587, '110': 1629, '100': 398, '101': 414}, {'101': 425, '010': 1662, '100': 435, '110': 1603, '011': 1548, '000': 461, '111': 1613, '001': 445}, {'101': 284, '010': 2606, '111': 610, '000': 680, '011': 2491, '001': 603, '110': 647, '100': 271}, {'010': 443, '001': 1550, '101': 1492, '000': 466, '011': 1703, '111': 1672, '110': 472, '100': 394}, {'010': 469, '100': 434, '110': 434, '001': 1558, '101': 1485, '011': 1698, '000': 470, '111': 1644}, {'101': 625, '001': 2410, '000': 663, '011': 2601, '111': 701, '010': 658, '100': 266, '110': 268}, {'001': 1530, '101': 1536, '100': 416, '110': 444, '010': 490, '111': 1664, '000': 474, '011': 1638}, {'001': 1558, '100': 438, '110': 456, '010': 452, '101': 1492, '000': 465, '111': 1656, '011': 1675}, {'101': 597, '010': 637, '111': 646, '000': 680, '011': 2603, '001': 2497, '110': 268, '100': 264}, {'010': 665, '100': 209, '110': 670, '000': 314, '111': 2551, '011': 2528, '101': 592, '001': 663}, {'100': 216, '110': 594, '101': 627, '001': 651, '111': 2518, '011': 2618, '000': 275, '010': 693}, {'001': 860, '101': 351, '010': 955, '100': 139, '110': 396, '000': 363, '011': 4263, '111': 865}]
    [{'101': 974, '010': 1062, '111': 1137, '011': 1027, '000': 989, '001': 980, '110': 1047, '100': 976}, {'101': 982, '001': 1030, '010': 1050, '110': 952, '100': 964, '111': 1069, '000': 980, '011': 1165}, {'101': 434, '111': 449, '011': 1667, '000': 1519, '010': 1646, '001': 1572, '110': 465, '100': 440}, {'001': 993, '101': 1019, '010': 1043, '110': 1048, '100': 975, '111': 1065, '000': 1014, '011': 1035}, {'110': 964, '100': 961, '101': 984, '001': 1041, '010': 1043, '000': 1066, '011': 1109, '111': 1024}, {'101': 442, '010': 1706, '111': 444, '000': 1601, '011': 1577, '110': 434, '100': 449, '001': 1539}, {'001': 449, '101': 412, '010': 1559, '000': 447, '111': 1682, '011': 1638, '110': 1567, '100': 438}, {'101': 413, '100': 385, '110': 1562, '010': 1683, '001': 451, '111': 1596, '000': 477, '011': 1625}, {'101': 250, '001': 565, '010': 2662, '111': 588, '011': 2631, '000': 633, '110': 588, '100': 275}, {'010': 1080, '011': 1041, '000': 993, '111': 1041, '110': 1073, '100': 1028, '101': 942, '001': 994}, {'001': 1023, '101': 1010, '010': 1092, '100': 945, '110': 1004, '111': 1017, '000': 1018, '011': 1083}, {'101': 428, '110': 456, '100': 476, '010': 1649, '001': 1527, '011': 1614, '000': 1591, '111': 451}, {'101': 971, '000': 1014, '011': 1046, '111': 1054, '010': 1000, '001': 1001, '110': 1070, '100': 1036}, {'001': 1021, '101': 969, '111': 996, '011': 943, '000': 1079, '100': 1008, '110': 1076, '010': 1100}, {'100': 409, '110': 492, '001': 1538, '101': 420, '010': 1639, '000': 1589, '011': 1645, '111': 460}, {'001': 428, '000': 446, '011': 1628, '111': 1582, '010': 1575, '100': 411, '110': 1671, '101': 451}, {'001': 443, '010': 1744, '110': 1561, '100': 435, '000': 423, '111': 1582, '011': 1613, '101': 391}, {'101': 257, '001': 600, '010': 2610, '110': 635, '100': 290, '111': 646, '000': 642, '011': 2512}, {'110': 439, '100': 457, '001': 1508, '101': 1499, '111': 1740, '011': 1670, '000': 437, '010': 442}, {'100': 428, '110': 437, '101': 1444, '001': 1573, '010': 488, '111': 1633, '000': 450, '011': 1739}, {'010': 615, '101': 637, '001': 2368, '100': 247, '110': 284, '111': 638, '000': 671, '011': 2732}, {'110': 440, '100': 428, '010': 512, '111': 1608, '000': 422, '011': 1612, '001': 1580, '101': 1590}, {'100': 395, '110': 464, '010': 469, '101': 1481, '001': 1613, '000': 453, '111': 1605, '011': 1712}, {'100': 262, '110': 248, '001': 2537, '101': 647, '010': 683, '011': 2577, '000': 627, '111': 611}, {'100': 229, '110': 623, '010': 635, '000': 274, '111': 2650, '011': 2574, '001': 644, '101': 563}, {'010': 682, '100': 242, '110': 642, '000': 255, '111': 2411, '011': 2684, '101': 641, '001': 635}, {'101': 343, '000': 351, '011': 4258, '111': 941, '001': 848, '010': 945, '110': 374, '100': 132}]
    state tomography fidelity = 0.0464 ± 0.0014

```python
import qiskit.tools.jupyter
%qiskit_version_table
```

<h3>Version Information</h3><table><tr><th>Qiskit Software</th><th>Version</th></tr><tr><td><code>qiskit-terra</code></td><td>0.18.3</td></tr><tr><td><code>qiskit-aer</code></td><td>0.9.1</td></tr><tr><td><code>qiskit-ignis</code></td><td>0.6.0</td></tr><tr><td><code>qiskit-ibmq-provider</code></td><td>0.18.1</td></tr><tr><td><code>qiskit-aqua</code></td><td>0.9.5</td></tr><tr><td><code>qiskit</code></td><td>0.32.1</td></tr><tr><td><code>qiskit-nature</code></td><td>0.1.3</td></tr><tr><th>System information</th></tr><tr><td>Python</td><td>3.8.8 (default, Apr 13 2021, 12:59:45)
[Clang 10.0.0 ]</td></tr><tr><td>OS</td><td>Darwin</td></tr><tr><td>CPUs</td><td>8</td></tr><tr><td>Memory (Gb)</td><td>64.0</td></tr><tr><td colspan='2'>Mon Nov 29 09:12:47 2021 EST</td></tr></table>


# Surface Water-Mass Transformation

In this notebook, we compute surface water-mass transformation rates (both in the net and partitioned into contributions from heat and salt fluxes) for the southern ocean, south of 60$^\circ$S.

Here we are looking specifically at ACCESS-OM2-01 output (0.1$^\circ$ resolution, latest spinup), as this configuration has demonstrated considerable success when simulating high latitude dense water formation processes.

## 1. Defining surface water-mass transformation

The surface water-mass transformation framework described here follows [Newsom *et al* (2016)](https://journals.ametsoc.org/doi/full/10.1175/JCLI-D-15-0513.1) and [Abernathey *et al* (2016)](https://www.nature.com/articles/ngeo2749). Surface water-mass transformation may be defined as the volume flux into a given density class ($\sigma$) from lighter density classes ($\sigma'<\sigma$) due to surface buoyancy forcing. Integrated over a region of the ocean surface, this volume flux can be expressed as,

\begin{equation}\Omega(\sigma,t)=\frac{\partial}{\partial\sigma}\iint_{\sigma'<\sigma}\Big(\frac{\partial\sigma}{\partial\theta}\theta + \frac{\partial\sigma}{\partial S}S\Big)\,dx\,dy\end{equation}

where $t$ is time, and the terms in the integrand are the potential temperature ($\theta$) flux and salinity ($S$) flux components of the surface buoyancy flux. The linearity of this expression means we can extract the relative contributions of heat ($\Omega_\text{heat}$) and salinity ($\Omega_\text{fw}$) fluxes to surface water-mass transformation, highlighting driving mechanisms.

## 2. Requirements

**The cosima-cookbook**: The `conda/analysis3-19.04` (or later) module on the VDI (or your own up-to-date cookbook installation).

**Model diagnostics**: for this example I use some of the 9091 RYF spinup output, if you want to use do surface water-mass transformation analysis on a different experiment you'll need to check you have saved the required diagnostics.

#### Warning: if you are using a surface heat flux diagnostic computed online by an ACCESS-OM2 run, make sure that this diagnostic is computed correctly! Some issues were found in the online computation of net_sfc_heating (see this [git issue](https://github.com/COSIMA/access-om2/issues/139) from March 2019). In this example we compute net surface heat fluxes offline from component fluxes to avoid these problems - if in doubt use the component fluxes as done here. If you dont have these variables saved and can't re-run...net_sfc_heating isn't really so bad for near Antarctic surface water-mass transformation calculations, since transformation rates are strongly dominated by freshwater fluxes, so the errors in heat fluxes confer only a small error to transformation rates.

You'll need at least monthly resolution (in diag table language...)

'surface_temp'

'surface_salt'

'pme_river'

'sfc_hflux_from_runoff'

'sfc_hflux_coupler'

'sfc_hflux_pme'

'frazil_3d_int_z' (this is not strictly a surface flux, instead frazil heat fluxes are vrtically integrated over the column. BUT this variable is highly surface intensified and, in most cases, conseputally relevant to what we want to know from SWMT analysis)

plus some basic gridding information.

## 3. Computing surface water-mass transformation

First, import some required modules and functions.

```python
import xarray as xr
import numpy as np
import dask.array
import cosima_cookbook as cc
from gsw import alpha, SA_from_SP, p_from_z, CT_from_pt, beta, sigma1
# note these gsw numpy functions mean some care is needed to avoid memory issues in very high-res analysis

## plotting
import matplotlib.pyplot as plt
from matplotlib import gridspec
import cmocean.cm as cmo
import matplotlib.colors as col
import cartopy.crs as ccrs
import matplotlib.path as mpath
from matplotlib import rc
rc('text', usetex=True)
rc('xtick', labelsize=25)
rc('ytick', labelsize=25)
rc('axes', labelsize=25)
```

You should also set up a dask client. The lines below assume that you have set up a scheduler and worker system in your terminal already. Open a terminal tab and run

`dask-scheduler`

and in another terminal tab run

`\dask-worker tcp://localhost:8786 --memory-limit 3e9 --nprocs 6 --nthreads 1 --local-directory /local/$PROJECT/$USER`

for a basic setup (this is just how I do it, you may have your pet method).

```python
## once you've set up a dask-worker, connect to it, click the dashboard link to check worker status
from dask.distributed import Client
client = Client('tcp://localhost:8786')
client
```

Client

Scheduler: tcp://localhost:8786

Dashboard: http://localhost:8787/status

Cluster

Workers: 6

Cores: 6

Memory: 18.00 GB

I'll be using output from the ACCESS-OM2-01 9091 RYF spinup, which is still running as I'm writing this example. For ease of updating, I've create my own database to organise this output (as in `Tutorials/Make_Your_Own_Database.ipynb`). Where suitable, replace any database and experiment strings with your own database (or the default database) and experiment name. It will also be necessary for you to choose a directory to save output to.

```python
db = '/g/data/v45/rm2389/databases/ryf_9091_spinup.db' # database path
session = cc.database.create_session(db)
dir_list=['/g/data3/hh5/tmp/cosima/access-om2-01/01deg_jra55v13_ryf9091']
cc.database.build_index(dir_list,session,update=True)
```

    Indexing experiment: 01deg_jra55v13_ryf9091

    0it [00:00, ?it/s]

    0

looks like I'm up to date

```python
session = cc.database.create_session(db)
expt = '01deg_jra55v13_ryf9091'
```

#### The function below will compute  surface water-mass transformation rates for the southern ocean, averaged over an integer number of years of monthly resolution output. Below I have flagged some possible modifications you may want/need to employ:

- If you are not computing net surface heat fluxes from its components (here we define `net_surface_heating = sfc_hflux_from_runoff + sfc_hflux_coupler + sfc_hflux_pme + frazil_3d_int_z`), the function below will need to be modified to account for this.
- If you are not looking at the southern ocean, alter the `yt_ocean` slicing regime.
- You may want to use a different density binning array (`isopycnal_bins`)  if the phenomena you're interested in occurs in a shifted density space.
- I use $\sigma_1$ here because I'm interested in dense waters that subduct to the Antarctic continental shelf <1000 m depth. You may want to use $\sigma_0$ $\sigma_2$ or something else. If so, alter the gsw function used, and define a more appropriate `isopycnal_bins` array.
- If you're looking at a non-interval number of years, the time averaging scheme will need to be modified.
- If you're online computed temperatures (SST) are potential temp (as opposed to conservative temp), use the gsw function `CT_from_pt` before the `sigma1` function as `sigma1` takes conservative temperature input (more info on the TEOS-10 gsw functions [here](http://www.teos-10.org/pubs/gsw/v3_04/html/gsw_contents.html)). This is the case for older ACCESS-OM2-01 runs (e.g. spinup6).
- On this dask setup and with the memory constraints of the VDI I don't think you would be able to do much more than a 5 year average of 0.1$^\circ$ data using this function. You are memory limited by the numpy TEOS-10 gsw functions. If you have a need to average over more years, you mey have to do the TEOS-10 gsw translations separately and save these prior to computing transformation rates.

When analysing 0.1$^\circ$ output I tend to compute + save and reload surface water-mass transformation diagnostics in separate steps. In part this is because the computations take a while (~13min for 5 years of data below), but I also find that there's some instability (my workers are always killed) if I instead `return net_transformation, heat_transformation, salt_transformation` and then manipulate these variables straight away (a mystery for another mind). Saving and reloading works fine however.

```python
test = cc.querying.getvar(expt,'surface_temp',session)
test.time ## want to see what the latest years look like
```

    <xarray.DataArray 'time' (time: 279)>
    array([cftime.DatetimeNoLeap(1900, 1, 16, 12, 0, 0, 0, 4, 16),
           cftime.DatetimeNoLeap(1900, 2, 15, 0, 0, 0, 0, 6, 46),
           cftime.DatetimeNoLeap(1900, 3, 16, 12, 0, 0, 0, 0, 75), ...,
           cftime.DatetimeNoLeap(1923, 1, 16, 12, 0, 0, 0, 6, 16),
           cftime.DatetimeNoLeap(1923, 2, 15, 0, 0, 0, 0, 1, 46),
           cftime.DatetimeNoLeap(1923, 3, 16, 12, 0, 0, 0, 2, 75)], dtype=object)
    Coordinates:
      * time     (time) object 1900-01-16 12:00:00 ... 1923-03-16 12:00:00

Spinup is up to year 23, I'll compute surface water-mass transformation averaged over the last 5 years.

```python
def save_SWMT(expt, session, start_time, end_time, outpath, lat_north = -59, n = None):
    '''
    Computes southern ocean surface water-mass transformation rates (partitioned into transformation from heat
    and freshwater) referenced to 1000 db from monthly ACCESS-OM2 output.
    Suitable for analysis of high-resolution (0.1 degree) output (the scattered .load()'s allowed this)

    expt - text string indicating the name of the experiment
    session - a database session created by cc.database.create_session()
    start_time - text string designating the start month of the analysis ('YYYY-MM', e.g. '1905-01')
    end_time - text string indicating the end month of the analysis ('YYY-MM', e.g. '1905-12')
    outpath - text string indicating directory where output databases are to be saved (3 xarray databases, can
    modify to combine these if memory permits)
    lat_north - function computed processes between lat = -90 and lat = lat_north
    n - designate if a subset of output files is to be considered (see cc.querying.getvar)

    NOTE: this function assumes you are averaging over an integer number of years (though the start month
    need not be january, e.g. can have start_time = '1905-05', end_time = '1907-04' etc), modify if otherwise.

    NOTE: assumes surface_temp and surface_salt variables are in potential temperature (K) and practical
    salinity (PSU), simplifications may be made if conservative temperature (C) and absolute salinity (g/kg)
    are computed online

    required modules:
    xarray as xr
    numpy as np
    cosima_cookbook as cc
    from gsw import alpha, SA_from_SP, p_from_z, CT_from_pt, beta, sigma1
    '''
    ## getvar all required variables
    SST = cc.querying.getvar(expt,'surface_temp',session) - 273.15 # SST - conservative temperature in K (sheck this is the case for your run)
    SSS_PSU = cc.querying.getvar(expt,'surface_salt',session) # SSS - practical salinity (not absolute)
    pme_river = cc.querying.getvar(expt,'pme_river',session) # mass flux of precip - evap + river
    ## getvar the components of the net surface heat fux instead of the net_surface_heating variable
    sfc_hflux_from_runoff = cc.querying.getvar(expt,'sfc_hflux_from_runoff',session) # W/m2
    sfc_hflux_coupler = cc.querying.getvar(expt,'sfc_hflux_coupler',session) # W/m2
    sfc_hflux_pme = cc.querying.getvar(expt,'sfc_hflux_pme',session) # W/m2
    frazil_3d_int_z = cc.querying.getvar(expt,'frazil_3d_int_z',session) # W/m2
    geolon_t = cc.querying.getvar(expt,'geolon_t',session, n=1)
    geolat_t = cc.querying.getvar(expt,'geolat_t',session, n=1)
    ## slice for time and latitudinal constraints
    time_slice = slice(start_time,end_time)
    lat_slice = slice(-90,lat_north)
    SST = SST.sel(time=time_slice, yt_ocean=lat_slice)
    SSS_PSU = SSS_PSU.sel(time=time_slice, yt_ocean=lat_slice)
    pme_river = pme_river.sel(time=time_slice, yt_ocean=lat_slice)
    sfc_hflux_from_runoff = sfc_hflux_from_runoff.sel(time=time_slice, yt_ocean=lat_slice)
    sfc_hflux_coupler = sfc_hflux_coupler.sel(time=time_slice, yt_ocean=lat_slice)
    sfc_hflux_pme = sfc_hflux_pme.sel(time=time_slice, yt_ocean=lat_slice)
    frazil_3d_int_z = frazil_3d_int_z.sel(time=time_slice, yt_ocean=lat_slice)
    lon_t = geolon_t.sel(yt_ocean=lat_slice)
    lat_t = geolat_t.sel(yt_ocean=lat_slice)
    ## extract coordinate arrays
    yt_ocean = SST.yt_ocean.values
    xt_ocean = SST.xt_ocean.values
    time_monthly = SST.time.values
    ## construct an xarray of days per month (check this is relevant to your run), simple modification if non integer number of years analysed
    start_month = int(start_time[5:7])
    end_month = int(end_time[5:7])
    n_years = int(len(SST.time)/12)
    months_standard_noleap = np.array([31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31])
    if start_month != 1:
        months_offset_noleap = np.append(months_standard_noleap[(start_month-1):],months_standard_noleap[:(start_month-1)])
    else:
        months_offset_noleap = months_standard_noleap
    days_per_month = xr.DataArray(np.tile(months_offset_noleap, n_years), coords = [time_monthly], dims = ['time'], name = 'days per month')
    ## compute net surface heat flux from its component terms
    net_surface_heating = sfc_hflux_from_runoff+ sfc_hflux_coupler+ sfc_hflux_pme+ frazil_3d_int_z # W/m2
    ## now I use some TEOS-10 gsw functions to compute absolute salinity, then potential density fields
    ## these are numpy functions, if you have memory errors this is a good step to check (though I have found
    ## this works on the VDI for 0.1 degree data, might be issues for very long time periods)
    depth = -0.541281 # st_ocean value of the uppermost cell
    depth_tile = (lat_t*0+1)*depth
    pressure = xr.DataArray(p_from_z(depth_tile,lat_t), coords = [yt_ocean, xt_ocean], dims = ['yt_ocean', 'xt_ocean'], name = 'pressure', attrs = {'units':'dbar'})
    # convert units to absolute salinity
    SSS = xr.DataArray(SA_from_SP(SSS_PSU,pressure,lon_t,lat_t), coords = [time_monthly, yt_ocean, xt_ocean], dims = ['time','yt_ocean', 'xt_ocean'], name = 'sea surface salinity', attrs = {'units':'Absolute Salinity (g/kg)'})
    ## SST is already saved as conservative temperature in this run, if you are working with an older run with potential
    ## temperature saved, conversion will be required (make sure you work with C not K)
    # SST = xr.DataArray(CT_from_pt(SSS_AS,SST_PT), coords = [time_monthly, yt_ocean, xt_ocean], dims = ['time','yt_ocean', 'xt_ocean'], name = 'sea surface temperature', attrs = {'units':'Conservative Temperature (C)'})
    # compute potential density referenced to 1000dbar (or referenced otherwise, depending on your purpose)
    pot_rho_1 = xr.DataArray(sigma1(SSS, SST), coords = [time_monthly, yt_ocean, xt_ocean], dims = ['time','yt_ocean', 'xt_ocean'], name = 'potential density ref 1000dbar', attrs = {'units':'kg/m^3 (-1000 kg/m^3)'})
    pot_rho_1 = pot_rho_1.load()
    # Compute salt transformation (no density binning)
    haline_contraction = xr.DataArray(beta(SSS, SST, pressure), coords = [time_monthly, yt_ocean, xt_ocean], dims = ['time','yt_ocean', 'xt_ocean'], name = 'saline contraction coefficient (constant conservative temp)', attrs = {'units':'kg/g'})
    salt_transformation = haline_contraction*SSS*pme_river*days_per_month #! before was PSU, why?
    salt_transformation = salt_transformation.load()
    # Compute heat transformation (no density binning)
    thermal_expansion = xr.DataArray(alpha(SSS, SST, pressure), coords = [time_monthly, yt_ocean, xt_ocean], dims = ['time','yt_ocean', 'xt_ocean'], name = 'thermal expansion coefficient (constant conservative temp)', attrs = {'units':'1/K'})
    heat_transformation =  thermal_expansion*net_surface_heating*days_per_month
    heat_transformation = heat_transformation.load()
    # Record the time bounds before summing through time (just to make sure it's consistent with requested years)
    time_bounds =  str(salt_transformation.coords['time.year'][0].values)+'_'+str(salt_transformation.coords['time.month'][0].values)+'-'+str(salt_transformation.coords['time.year'][-1].values)+'_'+str(salt_transformation.coords['time.month'][-1].values)

    # Next section does a few things. It cycles through isopycnal bins, determines which cells are
    # within the given bin for each month, finds the transformation values for those cells for each month,
    # and sums these through time. You are left with an array of shape (isopyncal bins * lats * lons)
    # where the array associated with a given isopycnal bin is NaN everywhere except where pot_rho_1
    # was within the bin, there it has a time summed transformation value.

    isopycnal_bins = np.arange(31,33.5,.02) ## alter if this density range doesn't capture surface processes in your study region, or if a different density field (not sigma1) is used

    bin_bottoms = isopycnal_bins[:-1]
    binned_salt_transformation = xr.DataArray(np.zeros((len(bin_bottoms), len(yt_ocean), len(xt_ocean))), coords = [bin_bottoms, yt_ocean,xt_ocean], dims = ['isopycnal_bins', 'yt_ocean', 'xt_ocean'], name = 'salt transformation in isopycnal bins summed over time')
    binned_salt_transformation.chunk({'isopycnal_bins':1})
    for i in range(len(isopycnal_bins)-1):
        bin_mask = pot_rho_1.where(pot_rho_1 <= isopycnal_bins[i+1]).where(pot_rho_1 > isopycnal_bins[i]) * 0 + 1
        masked_transform = (salt_transformation * bin_mask).sum(dim = 'time')
        masked_transform = masked_transform.where(masked_transform != 0)
        masked_transform = masked_transform.load()
        binned_salt_transformation[i,:,:] = masked_transform
    print('salt_transformation binning done')

    binned_heat_transformation = xr.DataArray(np.zeros((len(bin_bottoms), len(yt_ocean), len(xt_ocean))), coords = [bin_bottoms, yt_ocean,xt_ocean], dims = ['isopycnal_bins', 'yt_ocean', 'xt_ocean'], name = 'heat transformation in isopycnal bins summed over time')
    binned_heat_transformation.chunk({'isopycnal_bins':1})

    for i in range(len(isopycnal_bins)-1):
        bin_mask = pot_rho_1.where(pot_rho_1 <= isopycnal_bins[i+1]).where(pot_rho_1 > isopycnal_bins[i]) * 0 + 1
        masked_transform = (heat_transformation * bin_mask).sum(dim = 'time')
        masked_transform = masked_transform.where(masked_transform != 0)
        masked_transform = masked_transform.load()
        binned_heat_transformation[i,:,:] = masked_transform
    print('heat_transformation binning done')

    ndays = days_per_month.sum().values
    salt_transformation = binned_salt_transformation/ndays
    c_p = 3992.1
    heat_transformation = binned_heat_transformation/c_p/ndays

    isopycnal_bin_diff = np.diff(isopycnal_bins)
    salt_transformation = salt_transformation/isopycnal_bin_diff[:,np.newaxis,np.newaxis]
    heat_transformation = heat_transformation/isopycnal_bin_diff[:,np.newaxis,np.newaxis]
    isopycnal_bin_mid = (isopycnal_bins[1:] + isopycnal_bins[:-1])/2

    # this procedure defines fluxes from lighter to denser classes as negative, I want the opposite
    salt_transformation = salt_transformation *-1
    heat_transformation = heat_transformation *-1

    # Convert the binned (and summed through time) salt and heat transformation DataArrays to Datasets (to save metadata) and save to netCDF
    ds = xr.Dataset({'binned_salt_transformation': salt_transformation, 'time_bounds': time_bounds})
    ds.to_netcdf(outpath+'/ST_'+time_bounds+'.nc')
    ds = xr.Dataset({'binned_heat_transformation': heat_transformation, 'time_bounds': time_bounds})
    ds.to_netcdf(outpath+'/HT_'+time_bounds+'.nc')
    #############################################################
    net_transformation = heat_transformation + salt_transformation
    #############################################################
    del(heat_transformation, salt_transformation) ## unecessary for lower res or smaller time
    # wanted to rename the isopycnal bin (bottom edge) coord with the isopycnal bin midpoints...
    net_transformation.coords['isopycnal_bins'] = isopycnal_bin_mid
    ds = xr.Dataset({'surface_water_mass_transformation': net_transformation, 'time_bounds': time_bounds})
    ds.to_netcdf(outpath+'/SWMT_'+time_bounds+'.nc')
    del(net_transformation)
    return outpath, time_bounds ## helpful for re-loading
```

```python
%%time
outpath = '/g/data/v45/rm2389/SWMT/01deg_jra55v13_ryf9091' # use a path where you have write permission
outpath, time_bounds = save_SWMT(expt, session, '1918-04', '1923-03', outpath)
```

    salt_transformation binning done
    heat_transformation binning done
    CPU times: user 6min 10s, sys: 5min 12s, total: 11min 23s
    Wall time: 12min 29s

## 4. Reload computed surface water-mass transformation

```python
def get_SWMT(outpath, time_bounds):
    net_transformation = xr.open_dataset(outpath+'/SWMT_'+time_bounds+'.nc',chunks={'isopycnal_bins':1})
    net_transformation = net_transformation.surface_water_mass_transformation
    heat_transformation = xr.open_dataset(outpath+'/HT_'+time_bounds+'.nc',chunks={'isopycnal_bins':1})
    heat_transformation = heat_transformation.binned_heat_transformation
    salt_transformation = xr.open_dataset(outpath+'/ST_'+time_bounds+'.nc',chunks={'isopycnal_bins':1})
    salt_transformation = salt_transformation.binned_salt_transformation
    return  net_transformation, heat_transformation, salt_transformation
```

## 5. Plotting options: summed over southern ocean, south of 59S

One useful way of visualising the surface water-mass transformation metric is to sum the transformation rates (in density bins) over the analysis region, which here is the entire southern ocean south of 59S.

```python
area_t = cc.querying.getvar(expt,'area_t',session, n=1) # needed for the plots I'll provide.
swmt, heat, salt = get_SWMT(outpath, time_bounds)
## sum over region, convert to Sv
isopycnal_bin_mid = swmt.isopycnal_bins
swmt_sum = (swmt * area_t/1e6).sum(['xt_ocean', 'yt_ocean']).values
heat_sum = (heat * area_t/1e6).sum(['xt_ocean', 'yt_ocean']).values
salt_sum = (salt * area_t/1e6).sum(['xt_ocean', 'yt_ocean']).values
```

```python
figure = plt.figure(num = 1, figsize = (10,10))
plt.plot(swmt_sum,isopycnal_bin_mid,color = 'k',label='net')
plt.plot(heat_sum,isopycnal_bin_mid,color = 'r',label='heat')
plt.plot(salt_sum,isopycnal_bin_mid,color = 'b',label='salt')
plt.plot([0,0],[31,33.2],'k',linewidth=0.5)
plt.ylim((33.2,31))
plt.ylabel(r'$\sigma_{1}$ (kg m$^{-3}$)', fontsize = 30)
plt.xlabel('Surface water-mass transformation (Sv)', fontsize = 30)
plt.legend(loc=3, fontsize = 25)
plt.show()
```

Here the positive peak in the high density classes indicates a rate of subduction due to surface densification processes (predominantly freshwater fluxes associated with sea-ice processes in this case). The negative peak indicates upwelling rates.

## 6. Plotting options: Antarctic shelf dense water formation

You, like me, might be interested in dense water formation on the Anarctic continental shelf. Below I've outlined a procedure whereby you can identify the density of subducting waters on the continental shelf, and map the locations where this sudbuction occurs.

You will need a way of masking for the continental shelf. You might just use a simple depth criterion, but here I define the shelf region using a mask that selects cells poleward of a continuous approximation of the 1000 m isobath surrounding Antartcica.

```python
def shelf_mask_isobath(var):
    '''
    Masks ACCESS-OM2-01 variables by the region polewards of the 1000m isobath as computed using
    a script contributed by Adele Morrison.
    Only to be used with ACCESS-OM2-0.1 output!
    '''
    contour_file = np.load('/g/data/v45/rm2389/Isobath_Transport/Antarctic_slope_contour_1000m.npz')
    shelf_mask = contour_file['contour_masked_above']
    yt_ocean = contour_file['yt_ocean']
    xt_ocean = contour_file['xt_ocean']
    # in this file the points along the isobath are given a positive value, the points outside (northwards)
    # of the isobath are given a value of -100 and all the points on the continental shelf have a value of 0
    # so we mask for the 0 values
    shelf_mask[np.where(shelf_mask!=0)] = np.nan
    shelf_mask = shelf_mask+1
    shelf_map = np.nan_to_num(shelf_mask)
    shelf_mask  = xr.DataArray(shelf_mask,coords = [('yt_ocean', yt_ocean), ('xt_ocean', xt_ocean)] )
    shelf_map  = xr.DataArray(shelf_map,coords = [('yt_ocean', yt_ocean), ('xt_ocean', xt_ocean)] )
    # then we want to multiply the variable with the mask so we need to account for the shape of the mask.
    # The mask uses a northern cutoff of 59S.
    masked_var = var.sel(yt_ocean = slice(-90,-59.03)) * shelf_mask
    return masked_var, shelf_map
```

Below I've plotted the positioning of this isobath boundary.

```python
ht = cc.querying.getvar(expt,'ht',session, n=1)
ht = ht.sel(yt_ocean = slice(-90,-59))
land_mask = (ht*0).fillna(1)
yt_ocean =ht.yt_ocean.values
xt_ocean =ht.xt_ocean.values
ht_shelf , shelf_mask = shelf_mask_isobath(ht)
```

```python
fig = plt.figure(num=1,figsize=(10,10))
ax = plt.subplot(projection=ccrs.SouthPolarStereo())

ax.contour(xt_ocean, yt_ocean,land_mask,[0,1], colors = 'k', alpha = 0.4, transform=ccrs.PlateCarree())
ax.contour(xt_ocean, yt_ocean, shelf_mask.values, [0,1], colors = 'r', transform=ccrs.PlateCarree())

theta = np.linspace(0, 2*np.pi, 100)
center, radius = [0.5, 0.5], 0.5
verts = np.vstack([np.sin(theta), np.cos(theta)]).T
circle = mpath.Path(verts * radius + center)
ax.set_boundary(circle, transform=ax.transAxes)
ax.set_extent([-180, 180, -90, -59], ccrs.PlateCarree())
ax.outline_patch.set_visible(False)
plt.show()
```

Now I'll repoduce the previous surface water-mass transformation plot but for the continental shelf region only.

```python
swmt_shelf, shelf_mask = shelf_mask_isobath(swmt)
heat_shelf, shelf_mask = shelf_mask_isobath(heat)
salt_shelf, shelf_mask = shelf_mask_isobath(salt)
area_t_shelf, shelf_mask = shelf_mask_isobath(area_t)
swmt_shelf_sum = (swmt_shelf * area_t_shelf/1e6).sum(['xt_ocean','yt_ocean']).values
heat_shelf_sum = (heat_shelf * area_t_shelf/1e6).sum(['xt_ocean','yt_ocean']).values
salt_shelf_sum = (salt_shelf * area_t_shelf/1e6).sum(['xt_ocean','yt_ocean']).values
```

```python
figure = plt.figure(num = 1, figsize = (10,10))
plt.plot(swmt_shelf_sum,isopycnal_bin_mid,color = 'k',label='net')
plt.plot(heat_shelf_sum,isopycnal_bin_mid,color = 'r',label='heat')
plt.plot(salt_shelf_sum,isopycnal_bin_mid,color = 'b',label='salt')
plt.plot([0,0],[31,33.2],'k',linewidth=0.5)
plt.ylim((33.2,31))
plt.ylabel(r'$\sigma_{1}$ (kg m$^{-3}$)', fontsize = 30)
plt.xlabel('Surface water-mass transformation (Sv)', fontsize = 30)
plt.legend(loc=1, fontsize = 25)
plt.show()
```

This shows us that contienntal shelf surface waters are made denser (almost entirely by sea-ice freshwater fluxes) and subduct away from the surface at a rate of over 10 Sv! Where does this happen? If we map the surface water-mass transformation rate across the density class experiences the peak positive transformation rate when summed over the shelf, we will be able to see where waters are subducting.

```python
max_transformation_index = np.argmax(swmt_shelf_sum)
max_transformation_density = isopycnal_bin_mid[max_transformation_index]
print('Shelf subduction density:',max_transformation_density.values)
shelf_subduction_plot = swmt_shelf.isel(isopycnal_bins = max_transformation_index)*1e5
swmt_xt = shelf_subduction_plot.xt_ocean
swmt_yt = shelf_subduction_plot.yt_ocean
```

    Shelf subduction density: 32.44999999999997

```python
fig  = plt.figure(1, figsize = (20,10))
gs = gridspec.GridSpec(1,2, width_ratios = [3,2])
gs.update(wspace = 0.05)

ax, ax1= plt.subplot(gs[0], projection=ccrs.SouthPolarStereo()), plt.subplot(gs[1])

ax.contour(xt_ocean, yt_ocean,land_mask,[0,1], colors = 'k', alpha = 0.3, transform = ccrs.PlateCarree() )
ax.contour(shelf_mask.xt_ocean, shelf_mask.yt_ocean,shelf_mask,[0,1], colors = 'k', alpha =0.8, transform = ccrs.PlateCarree())
norm = col.Normalize(vmin=0,vmax=2.5)
plot_swmt = ax.pcolormesh(swmt_xt,swmt_yt,shelf_subduction_plot,norm=norm,cmap= cmo.matter,transform=ccrs.PlateCarree())

theta = np.linspace(0, 2*np.pi, 100)
center, radius = [0.5, 0.5], 0.5
verts = np.vstack([np.sin(theta), np.cos(theta)]).T
circle = mpath.Path(verts * radius + center)
ax.set_boundary(circle, transform=ax.transAxes)
ax.set_extent([-180, 180, -90, -59], ccrs.PlateCarree())
ax.outline_patch.set_visible(False)

cax = fig.add_axes([0.27, 0.13, 0.2, 0.04])
cbar=plt.colorbar(plot_swmt,cax = cax,orientation='horizontal',shrink = 0.5, ticks = [0, 0.5,1, 1.5,2,2.5,3])
cbar.set_label(r'Surface water-mass transformation ($\times 10^{-5}$m s$^{-1}$)', fontsize = 25)

ax1.plot(swmt_shelf_sum,isopycnal_bin_mid,color = 'k',label='net')
ax1.plot(heat_shelf_sum,isopycnal_bin_mid,color = 'r',label='heat')
ax1.plot(salt_shelf_sum,isopycnal_bin_mid,color = 'b',label='salt')
ax1.plot([0,0],[31,33.2],'k',linewidth=0.5)
ax1.plot([-5,15],[max_transformation_density,max_transformation_density],'k--',linewidth=1)
ax1.set_ylim((33.2,31))
ax1.set_xlim((-1.5,12.1))
ax1.set_ylabel(r'$\sigma_{1}$ (kg m$^{-3}$)', fontsize = 30)
ax1.set_xlabel('Surface water-mass transformation (Sv)', fontsize = 30)
ax1.legend(loc=1, fontsize = 25)

plt.show()
```

The map in the left panel shows the spatial distribution of surface water-mass transformation across the density class indicated with the dashed line in the right panel.

```python

```


# QAOA 求解最大割问题

 Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.

## 概览

在[量子近似优化算法教程](./QAOA_CN.ipynb)中，我们介绍了如何将经典的组合优化问题编码为量子优化问题，并用量子近似优化算法 [1]（quantum approximate optimization algorithm, QAOA）求解。在本教程中，我们将以最大割问题为例来进一步阐述 QAOA。

### 最大割问题

最大割问题（Max-Cut Problem）是图论中常见的一个组合优化问题，在统计物理学和电路设计中都有重要应用。最大割问题是一个 NP 困难问题，因此目前并不存在一个高效的算法能完美地解决该问题。

在图论中，一个图是由一对集合 $G=(V, E)$ 表示，其中集合 $V$ 中的元素为该图的顶点，集合 $E$ 中的每个元素是一对顶点，表示连接这两个顶点的一条边。例如下方图片中的图可以由 $V=\{0,1,2,3\}$ 和 $E=\{(0,1),(1,2),(2,3),(3,0)\}$ 表示。

图 1：一个有四个顶点和四条边的图

一个图上的割（cut）是指将该图的顶点集 $V$ 分割成两个互不相交的集合的一种划分，每个割都对应一个边的集合，这些边的两个顶点被划分在不同的集合中。于是我们可以将这个割的大小定义为这个边的集合的大小，即被割开的边的条数。最大割问题就是要找到一个割使得被割开的边的条数最多。图 2 展示了图 1 中图的一个最大割，该最大割的大小为 $4$，即割开了图中所有的边。

图 2：图 1 中图的一个最大割

假设输入的图 $G=(V, E)$ 有 $n=|V|$ 个顶点和 $m=|E|$ 条边，那么我们可以将最大割问题描述为 $n$ 个比特和 $m$ 个子句的组合优化问题。每个比特对应图 $G$ 中的一个顶点 $v$，其取值 $z_v$ 为 $0$ 或 $1$，分别对应该顶点属于集合 $S_{0}$ 或 $S_{1}$，因此这 $n$ 个比特的每种取值 $z$ 都对应一个割。每个子句则对应图 $G$ 中的一条边 $(u,v)$，一个子句要求其对应的边连接的两个顶点的取值不同，即 $z_u\neq z_v$，表示该条边被割开。也就是说，当该条边连接这的两个顶点被割划分到不同的集合上时，我们说该子句被满足。因此，对于图 $G$ 中的每条边 $(u,v)$，我们有

$$
C_{(u,v)}(z) = z_u+z_v-2z_uz_v,
\tag{1}
$$

其中 $C_{(u,v)}(z) = 1$ 当且仅当该条边被割开。否则，该函数等于 $0$。整个组合优化问题的目标函数是

$$
C(z) = \sum_{(u,v)\in E}C_{(u,v)}(z) = \sum_{(u,v)\in E}z_u+z_v-2z_uz_v.
\tag{2}
$$

因此，解决最大割问题就是要找到一个取值 $z$ 使得公式（2）中的目标函数最大。

### 编码最大割问题

为了将最大割问题转化为一个量子问题，我们要用到 $n$ 个量子比特，每个量子比特对应图 $G$ 中的一个顶点。一个量子比特处于量子态 $|0\rangle$ 或 $|1\rangle$，表示其对应的顶点属于集合 $S_{0}$ 或 $S_{1}$。值得注意的是，$|0\rangle$ 和 $|1\rangle$ 是 Pauli $Z$ 门的两个本征态，并且它们的本征值分别为 $1$ 和 $-1$，即

$$
\begin{align}
Z|0\rangle&=|0\rangle,\tag{3}\\
Z|1\rangle&=-|1\rangle.\tag{4}
\end{align}
$$

因此我们可以使用 Pauli $Z$ 门来构建该最大割问题的哈密顿量 $H_C$。因为通过映射 $f(x):x\to(x+1)/2$ 可以将 $-1$ 映射到 $0$ 上 并且仍将 $1$ 映射到 $1$ 上，所以我们可以将式（2）中的 $z$ 替换为 $(Z+I)/2$（$I$ 是单位矩阵），得到原问题目标函数对应的哈密顿量

$$
\begin{align}
H_C &= \sum_{(u,v)\in E} \frac{Z_u+I}{2} + \frac{Z_v+I}{2} - 2\cdot\frac{Z_u+I}{2}\frac{Z_v+I}{2}\tag{5}\\
&= \sum_{(u,v)\in E} \frac{Z_u+Z_v+2I - (Z_uZ_v+Z_u+Z_v+I)}{2}\tag{6}\\
&= \sum_{(u,v)\in E} \frac{I - Z_uZ_v}{2}.\tag{7}
\end{align}
$$

该哈密顿量关于一个量子态 $|\psi\rangle$ 的期望值为

$$
\begin{align}
\langle\psi|H_C|\psi\rangle &= \langle\psi|\sum_{(u,v)\in E} \frac{I - Z_uZ_v}{2}|\psi\rangle\tag{8}\\
&= \langle\psi|\sum_{(u,v)\in E} \frac{I}{2}|\psi\rangle - \langle\psi|\sum_{(u,v)\in E} \frac{Z_uZ_v}{2}|\psi\rangle\tag{9}\\
&= \frac{|E|}{2} - \frac{1}{2}\langle\psi|\sum_{(u,v)\in E} Z_uZ_v|\psi\rangle.\tag{10}
\end{align}
$$

如果我们记

$$
H_D = -\sum_{(u,v)\in E} Z_uZ_v,
\tag{11}
$$

那么找到量子态 $|\psi\rangle$ 使得 $\langle\psi|H_C|\psi\rangle$ 最大等价于找到量子态 $|\psi\rangle$ 使得 $\langle\psi|H_D|\psi\rangle$ 最大。

## Paddle Quantum 实现

接下来，我们用量桨实现 QAOA 来求解最大割问题。有许多方法可以找到参数 $\vec{\gamma},\vec{\beta}$，我们这里使用经典机器学习中的梯度下降方法。

要在量桨上实现 QAOA，首先要做的便是加载需要用到的包。其中 `networkx` 包可以帮助我们方便地处理图。

```python
from IPython.core.display import HTML
display(HTML("<style>pre { white-space: pre !important; }</style>"))
```

```python
# 加载量桨、飞桨的相关模块
import paddle
from paddle_quantum.circuit import UAnsatz
from paddle_quantum.utils import pauli_str_to_matrix

# 加载额外需要用到的包
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
```

接下来，我们生成该最大割问题中的图 $G$。为了运算方便，这里的顶点从 $0$ 开始计数。

```python
# n 是图 G 的顶点数，同时也是量子比特的个数
n = 4
G = nx.Graph()
V = range(n)
G.add_nodes_from(V)
E = [(0, 1), (1, 2), (2, 3), (3, 0)]
G.add_edges_from(E)

# 将生成的图 G 打印出来
pos = nx.circular_layout(G)
options = {
    "with_labels": True,
    "font_size": 20,
    "font_weight": "bold",
    "font_color": "white",
    "node_size": 2000,
    "width": 2
}
nx.draw_networkx(G, pos, **options)
ax = plt.gca()
ax.margins(0.20)
plt.axis("off")
plt.show()
```

### 编码哈密顿量

量桨中，哈密顿量可以以 `list` 的形式输入。这里我们构建式（11）中的哈密顿量 $H_D$。

```python
# 以 list 的形式构建哈密顿量 H_D
H_D_list = []
for (u, v) in E:
    H_D_list.append([-1.0, 'z'+str(u) + ',z' + str(v)])
print(H_D_list)
```

    [[-1.0, 'z0,z1'], [-1.0, 'z1,z2'], [-1.0, 'z2,z3'], [-1.0, 'z3,z0']]

可以看到，在这个例子中，哈密顿量 $H_D$ 为

$$
H_D = -Z_0Z_1 - Z_1Z_2 - Z_2Z_3 - Z_3Z_0.
\tag{12}
$$

我们可以查看哈密顿量 $H_D$ 的矩阵形式，并且获取它的本征值信息：

```python
# 将哈密顿量 H_D 从 list 形式转为矩阵形式
H_D_matrix = pauli_str_to_matrix(H_D_list, n)
# 取出 H_D 对角线上的元素
H_D_diag = np.diag(H_D_matrix).real
# 获取 H_D 的最大本征值
H_max = np.max(H_D_diag)

print(H_D_diag)
print('H_max:', H_max)
```

    [-4.  0.  0.  0.  0.  4.  0.  0.  0.  0.  4.  0.  0.  0.  0. -4.]
    H_max: 4.0

### 搭建 QAOA 电路

前面我们介绍了 QAOA 需要将两个酉变换 $U_C(\gamma)$ 和 $U_B(\beta)$ 交替地作用在初始态 $|s\rangle = |+\rangle^{\otimes n}$ 上。在这里，我们使用量桨中提供的量子门和量子电路模板搭建出一个量子电路来实现这一步骤。要注意的是，在最大割问题中，我们将最大化哈密顿量 $H_C$ 的期望值的问题简化为了最大化哈密顿量 $H_D$ 的期望值的问题，因此要用到的酉变换也就变成了 $U_D(\gamma)$ 和 $U_B(\beta)$。通过交替地摆放两个参数可调的电路模块，我们得以搭建QAOA电路

$$
U_B(\beta_p)U_D(\gamma_p)\cdots U_B(\beta_1)U_D(\gamma_1),
\tag{13}
$$

其中，$U_D(\gamma) = e^{-i\gamma H_D}$ 可以由下图中的电路搭建实现。另一个酉变换 $U_B(\beta)$ 则等价于在每个量子比特上作用一个 $R_x$ 门。

图 3：酉变换 $e^{i\gamma Z\otimes Z}$ 的量子电路实现

因此，实现一层酉变换 $U_B(\beta)U_D(\gamma)$ 的量子电路如图 4 所示。

图 4：酉变换 $U_B(\beta)U_D(\gamma)$ 的量子电路实现

量桨中，电路运行前每个量子比特默认的初始状态为 $|0\rangle$（可以通过输入参数来自定义初始状态），我们可以通过添加一层 Hadamard 门使每个量子比特的状态由 $|0\rangle$ 变为 $|+\rangle$，由此得到 QAOA 要求的初始态 $|s\rangle = |+\rangle^{\otimes n}$。在量桨中，可以通过调用 `superposition_layer()` 在量子电路中添加一层 Hadamard 门。

```python
def circuit_QAOA(p, gamma, beta):
    # 初始化 n 个量子比特的量子电路
    cir = UAnsatz(n)
    # 制备量子态 |s>
    cir.superposition_layer()
    # 搭建 p 层电路
    for layer in range(p):
        # 搭建 U_D 的电路
        for (u, v) in E:
            cir.cnot([u, v])
            cir.rz(gamma[layer], v)
            cir.cnot([u, v])

        # 搭建 U_B 的电路，即添加一层 R_x 门
        for v in V:
            cir.rx(beta[layer], v)

    return cir
```

搭建 QAOA 量子电路的工作完成后，如果运行该量子电路，得到的输出态为

$$
|\vec{\gamma},\vec{\beta}\rangle = U_B(\beta_p)U_D(\gamma_p)\cdots U_B(\beta_1)U_D(\gamma_1)|s\rangle.
\tag{14}
$$

### 计算损失函数

由上一步搭建的电路的输出态，我们可以计算最大割问题的目标函数

$$
F_p(\vec{\gamma},\vec{\beta}) = \langle\vec{\gamma},\vec{\beta}|H_D|\vec{\gamma},\vec{\beta}\rangle.
\tag{15}
$$

要最大化该目标函数等价于最小化 $-F_p$。因此我们定义 $L(\vec{\gamma},\vec{\beta}) = -F_p(\vec{\gamma},\vec{\beta})$ 为损失函数，即要最小化的函数，然后利用经典的优化算法寻找最优参数 $\vec{\gamma},\vec{\beta}$。下面的代码给出了通过量桨和飞桨搭建的完整 QAOA 网络：

```python
class Net(paddle.nn.Layer):
    def __init__(
        self,
        p,
        dtype="float64",
    ):
        super(Net, self).__init__()

        self.p = p
        self.gamma = self.create_parameter(shape=[self.p],
                                           default_initializer=paddle.nn.initializer.Uniform(low=0.0, high=2*np.pi),
                                           dtype=dtype, is_bias=False)
        self.beta = self.create_parameter(shape=[self.p],
                                          default_initializer=paddle.nn.initializer.Uniform(low=0.0, high=2*np.pi),
                                          dtype=dtype, is_bias=False)

    def forward(self):
        # 定义 QAOA 的量子电路
        cir = circuit_QAOA(self.p, self.gamma, self.beta)
        # 运行该量子电路
        cir.run_state_vector()
        # 计算损失函数
        loss = -cir.expecval(H_D_list)

        return loss, cir
```

### 训练量子神经网络
定义好了用于 QAOA 的量子神经网络后，我们使用梯度下降的方法来更新其中的参数，使得式（15）的期望值最大。

```python
p = 4      # 量子电路的层数
ITR = 120  # 训练迭代的次数
LR = 0.1   # 基于梯度下降的优化方法的学习率
SEED = 1024 #设置全局随机数种子
```

这里，我们在飞桨中优化上面定义的网络。

```python
paddle.seed(SEED)

net = Net(p)
# 使用 Adam 优化器
opt = paddle.optimizer.Adam(learning_rate=LR, parameters=net.parameters())
# 梯度下降循环
for itr in range(1, ITR + 1):
    # 运行上面定义的网络
    loss, cir = net()
    # 计算梯度并优化
    loss.backward()
    opt.minimize(loss)
    opt.clear_grad()
    if itr % 10 == 0:
        print("iter:", itr, "  loss:", "%.4f" % loss.numpy())
    if itr % ITR == 0:
        print("\n训练后的电路：")
        print(cir)

gamma_opt = net.gamma.numpy()
print("优化后的参数 gamma:\n", gamma_opt)
beta_opt = net.beta.numpy()
print("优化后的参数 beta:\n", beta_opt)
```

    iter: 10   loss: -3.8886
    iter: 20   loss: -3.9134
    iter: 30   loss: -3.9659
    iter: 40   loss: -3.9906
    iter: 50   loss: -3.9979
    iter: 60   loss: -3.9993
    iter: 70   loss: -3.9998
    iter: 80   loss: -3.9999
    iter: 90   loss: -4.0000
    iter: 100   loss: -4.0000
    iter: 110   loss: -4.0000
    iter: 120   loss: -4.0000

    训练后的电路：
    --H----*-----------------*--------------------------------------------------X----Rz(3.140)----X----Rx(0.824)----*-----------------*--------------------------------------------------X----Rz(0.737)----X----Rx(2.506)----*-----------------*--------------------------------------------------X----Rz(4.999)----X----Rx(4.854)----*-----------------*--------------------------------------------------X----Rz(0.465)----X----Rx(1.900)--
           |                 |                                                  |                 |                 |                 |                                                  |                 |                 |                 |                                                  |                 |                 |                 |                                                  |                 |
    --H----X----Rz(3.140)----X----*-----------------*---------------------------|-----------------|----Rx(0.824)----X----Rz(0.737)----X----*-----------------*---------------------------|-----------------|----Rx(2.506)----X----Rz(4.999)----X----*-----------------*---------------------------|-----------------|----Rx(4.854)----X----Rz(0.465)----X----*-----------------*---------------------------|-----------------|----Rx(1.900)--
                                  |                 |                           |                 |                                        |                 |                           |                 |                                        |                 |                           |                 |                                        |                 |                           |                 |
    --H---------------------------X----Rz(3.140)----X----*-----------------*----|-----------------|----Rx(0.824)---------------------------X----Rz(0.737)----X----*-----------------*----|-----------------|----Rx(2.506)---------------------------X----Rz(4.999)----X----*-----------------*----|-----------------|----Rx(4.854)---------------------------X----Rz(0.465)----X----*-----------------*----|-----------------|----Rx(1.900)--
                                                         |                 |    |                 |                                                               |                 |    |                 |                                                               |                 |    |                 |                                                               |                 |    |                 |
    --H--------------------------------------------------X----Rz(3.140)----X----*-----------------*----Rx(0.824)--------------------------------------------------X----Rz(0.737)----X----*-----------------*----Rx(2.506)--------------------------------------------------X----Rz(4.999)----X----*-----------------*----Rx(4.854)--------------------------------------------------X----Rz(0.465)----X----*-----------------*----Rx(1.900)--

    优化后的参数 gamma:
     [3.14046713 0.73681226 4.99897226 0.46481489]
    优化后的参数 beta:
     [0.82379898 2.50618308 4.85422542 1.90024859]

### 解码量子答案
当求得损失函数的最小值以及相对应的一组参数 $\vec{\gamma}^*,\vec{\beta}^*$ 后，我们的任务还没有完成。为了进一步求得 Max-Cut 问题的近似解，需要从 QAOA 输出的量子态 $|\vec{\gamma}^*,\vec{\beta}^*\rangle$ 中解码出经典优化问题的答案。物理上，解码量子态需要对量子态进行测量，然后统计测量结果的概率分布：

$$
p(z)=|\langle z|\vec{\gamma}^*,\vec{\beta}^*\rangle|^2.
\tag{16}
$$

通常情况下，某个比特串出现的概率越大，意味着其对应 Max-Cut 问题最优解的可能性越大。

Paddle Quantum 提供了查看 QAOA 量子电路输出状态的测量结果概率分布的函数：

```python
# 模拟重复测量电路输出态 1024 次
prob_measure = cir.measure(plot=True)
```

通过测量，找到出现几率最高的比特串。 此时，记其在该比特串中对应的比特取值为 $0$ 的顶点属于集合 $S_0$ 以及对应比特取值为 $1$ 的顶点属于集合 $S_1$，这两个顶点集合之间存在的边就是该图的一个可能的最大割方案。

下面的代码选取测量结果中出现几率最大的比特串，然后将其映射回经典解，并且画出对应的最大割方案：
- 红色顶点属于集合 $S_0$，
- 蓝色顶点属于集合 $S_1$，
- 虚线表示被割的边。

```python
# 找到测量结果中出现几率最大的比特串
cut_bitstring = max(prob_measure, key=prob_measure.get)
print("找到的割的比特串形式：", cut_bitstring)

# 在图上画出上面得到的比特串对应的割
node_cut = ["blue" if cut_bitstring[v] == "1" else "red" for v in V]

edge_cut = [
    "solid" if cut_bitstring[u] == cut_bitstring[v] else "dashed"
    for (u, v) in E
    ]
nx.draw(
        G,
        pos,
        node_color=node_cut,
        style=edge_cut,
        **options
)
ax = plt.gca()
ax.margins(0.20)
plt.axis("off")
plt.show()
```

可以看到，在这个例子中 QAOA 找到了图上的一个最大割。

_______

## 参考文献

[1] Farhi, E., Goldstone, J. & Gutmann, S. A Quantum Approximate Optimization Algorithm. [arXiv:1411.4028 (2014).](https://arxiv.org/abs/1411.4028)


# 11. Gyakorlat - Rezgésgerjesztő
2021.04.19.

## Feladat:

A mellékelt ábrán az $m_1$, $m_2$ és $m_3$ tömegekből álló 3 szabadságfokú rendszer látható. A testeket egy-egy rugó köti össze melyeknek merevsége $k_1$ és $k_2$. A testek elmozdulásait az $x_1$, $x_2$ és $x_3$ általános koordináták írják le.

### Adatok:
|||
|-------------------------------------|-------------------------------------|
| $m_1$ = 2 kg                        | $k_1$ = 200 N/m                     |
| $m_2$ = 4 kg                        | $k_2$ = 500 N/m                     |
| $m_3$ = 5 kg                        |

### Részfeladatok:

1. Írja fel a mozgásegyenleteket!
2. Számítsa ki a többszabdságfokú rendszer sajátkörfrekveciáit és a hozzá tartozó lengésképvektorokat!

## Megoldás:

## 1. Feladat:
Kis elmozdulások esetén a lineáris mozgásegyenlet mátrix együtthatós alakja a következő egyenlettel adható meg:

$$\mathbf{M}\mathbf{\ddot{q}}+\mathbf{C\dot{q}}+\mathbf{Kq} = \mathbf{Q^*},$$

ahol $\mathbf{q}$ az általános koordináták vektora, $\mathbf{M}$ a tömegmátrix, $\mathbf{C}$ a csillapítási mátrix, $\mathbf{K}$ a merevségi mátrix, $\mathbf{Q^*}$ pedig az általános erők vektora.

```python
import sympy as sp
from IPython.display import display, Math

sp.init_printing()
```

```python
## Függvények, szimbólumok definiálása

m1, m2, m3, k1, k2 = sp.symbols("m1, m2, m3, k1, k2", real=True)

# Készítsünk behelyettesítési listát az adatok alapján, SI-ben
adatok = [(m1, 2), (m2, 4), (m3, 5), (k1, 200), (k2, 500)]

# általános koordináták
t = sp.symbols("t", real=True, positive=True)
x1 = sp.Function('x1')(t)
x2 = sp.Function('x2')(t)
x3 = sp.Function('x3')(t)
```

```python
## A kinetikus energia
T = sp.Rational(1,2)*m1*x1.diff(t)**2 + sp.Rational(1,2)*m2*x2.diff(t)**2 + sp.Rational(1,2)*m3*x3.diff(t)**2

display(Math('T = {}'.format(sp.latex(T))))

## Potenciális energia
U = sp.Rational(1,2)*k1*(x2-x1)**2 + sp.Rational(1,2)*k2*(x3-x2)**2

display(Math('U = {}'.format(sp.latex(U))))

## Disszipatív energia most nincs!
## Külső erő nincs!
```

$\displaystyle T = \frac{m_{1} \left(\frac{d}{d t} \operatorname{x_{1}}{\left(t \right)}\right)^{2}}{2} + \frac{m_{2} \left(\frac{d}{d t} \operatorname{x_{2}}{\left(t \right)}\right)^{2}}{2} + \frac{m_{3} \left(\frac{d}{d t} \operatorname{x_{3}}{\left(t \right)}\right)^{2}}{2}$

$\displaystyle U = \frac{k_{1} \left(- \operatorname{x_{1}}{\left(t \right)} + \operatorname{x_{2}}{\left(t \right)}\right)^{2}}{2} + \frac{k_{2} \left(- \operatorname{x_{2}}{\left(t \right)} + \operatorname{x_{3}}{\left(t \right)}\right)^{2}}{2}$

```python
### Mátrix együtthatók legenerálása
""" A mátrix együtthatók egyes elemeit a megfelelő általános koordináta szerinti
parciális deriválással lehet előállítani. Ehhez először célszerű egy listába rendezni az általános koordinátákat.
"""
DoF = [x1, x2, x3]

## Tömegmátrix (kinetikus energiából)
# nullmátrix létrehozása a tömegmátrixnak
M = sp.zeros(3,3)
# nullmátrix feltöltése a megfelelő parciális derivált értékekkel
for i in range(3):
    for j in range(3):
        M[i,j] = T.diff((DoF[i]).diff(t)).diff((DoF[j]).diff(t))

display(Math('M = {}'.format(sp.latex(M))))

## Merevségi mátrix (potenciális energiából)
# nullmátrix létrehozása a merevségi mátrixnak
K = sp.zeros(3,3)
# nullmátrix feltöltése a megfelelő parciális derivált értékekkel
for i in range(3):
    for j in range(3):
        K[i,j] = U.diff(DoF[i]).diff(DoF[j])

display(Math('K = {}'.format(sp.latex(K))))

# Külső erő és disszipatív energia most nincs, tehát az általános erővektor és a csillapítási mátrix is nulla elemű.
```

$\displaystyle M = \left[\begin{matrix}m_{1} & 0 & 0\\0 & m_{2} & 0\\0 & 0 & m_{3}\end{matrix}\right]$

$\displaystyle K = \left[\begin{matrix}k_{1} & - k_{1} & 0\\- k_{1} & k_{1} + k_{2} & - k_{2}\\0 & - k_{2} & k_{2}\end{matrix}\right]$

Így a mozgásegyenlet
$$\mathbf{M}\mathbf{\ddot{q}}+\mathbf{Kq} = \mathbf{0}.$$
## 2. Feladat
A mozgásegyenlet egy homogén lineáris közönséges differenciál egyenlet, melynek megoldásást exponenciális próbafüggvénnyel keressük

$$
\mathbf{q}(t) = \mathbf{A}e^{i\omega_nt},
$$

ahol $\mathbf{A}$ a lengésképvektor. Visszahelyettesítve a próbafüggvényt és annak deriváltját a mozgásegyenletbe,

$$
(-\omega_n^2\mathbf{M}+\mathbf{K})\mathbf{A}e^{i\omega_nt} = \mathbf{0}
$$

adódik.
A fenti egyenletnek csak akkor van nem triviális megoldása, ha a következő teljesül:

$$
\det(-\omega_n^2\mathbf{M}+\mathbf{K}) = 0.
$$

Ez az úgyenvezett frekvencia egyenlet, amiből a rendszer sajátkörfrekvenciái kaphatók. A lengéskép vektorokat úgy lehet megkapni, hogy a frekvencia egyenlet megolásait beírjuk az alábbi egyenletbe:

$$
(-\omega_{ni}^2\mathbf{M}+\mathbf{K})\mathbf{A}_{i} = 0.
$$

Mivel ennek az egyenletnek végtelen megoldása van, ezért az $\mathbf{A}_{i}$ lengésképvektor egyik koordinátáját, szokás szerint az elsőt tetszőlegesen 1-nek választhatjuk.

```python
## A frekvencia egynelet
ω_n2, ω_n = sp.symbols("ω_n2, ω_n")
# oldjuk meg az egyenletet ω_n^2-re, majd vonjunk gyököt
ω_n2_val = sp.solve((-ω_n2*M+K).subs(adatok).det())
ω_n = [(sp.sqrt(i)) for i in ω_n2_val]

display(Math('ω_{{n,1}} = {}\\ \\text{{rad/s}}'.format(sp.latex(ω_n[0]))))
display(Math('ω_{{n,2}} = {}\\ \\text{{rad/s}}'.format(sp.latex(ω_n[1]))))
display(Math('ω_{{n,3}} = {}\\ \\text{{rad/s}}'.format(sp.latex(ω_n[2].evalf(5)))))
```

$\displaystyle ω_{n,1} = 0\ \text{rad/s}$

$\displaystyle ω_{n,2} = 10\ \text{rad/s}$

$\displaystyle ω_{n,3} = 16.583\ \text{rad/s}$

```python
## lengéskép vektorok meghatározása
# Hozzunk létre a lengésképvektoroknak egy üres listát, majd töltsük fel 3 lengésképvektorral, melyek első eleme 1
A = []
A2, A3 = sp.symbols("A2, A3")
for i in range(3):
    A.append(sp.Matrix([[1],[A2],[A3]]))

    # oldjuk meg az egyenletet a lengésképekre és írjuk be a megoldásokat a lengésképvektorba (2. és 3. koordináta)
    """A solver most a két ismeretlen változóra számított megoldást egy dictionary-be rendezi,
    melyben az egyes elemekre a hozzá rendelt névvel, jelen esetben a szimbólumok neveivel (A2, A3) lehet hivatkozni."""
    A[i][1] = sp.solve((((-ω_n[i]**2*M+K)*A[i]).subs(adatok)))[A2]
    A[i][2] = sp.solve((((-ω_n[i]**2*M+K)*A[i]).subs(adatok)))[A3]

display(Math('A_{{1}} = {}\\ \\text{{m}}'.format(sp.latex(A[0]))))
display(Math('A_{{2}} = {}\\ \\text{{m}}'.format(sp.latex(A[1].evalf(4)))))
display(Math('A_{{3}} = {}\\ \\text{{m}}'.format(sp.latex(A[2].evalf(4)))))
```

$\displaystyle A_{1} = \left[\begin{matrix}1\\1\\1\end{matrix}\right]\ \text{m}$

$\displaystyle A_{2} = \left[\begin{matrix}1.0\\0\\-0.4\end{matrix}\right]\ \text{m}$

$\displaystyle A_{3} = \left[\begin{matrix}1.0\\-1.75\\1.0\end{matrix}\right]\ \text{m}$

Készítette:

       Juhos-Kiss Álmos (Alkalmazott Mechanika Szakosztály)
       Bodor Bálint (BME MM) kidolgozása alapján.

        Hibák, javaslatok:
        amsz.bme@gmail.com
        csuzdi02@gmail.com
        almosjuhoskiss@gmail.com

            2021.04.19