```javascript
%%javascript
$('#appmode-leave').hide();
$('#copy-binder-link').hide();
$('#visit-repo-link').hide();
```

Copyright **Paolo Raiteri**, January 2022

# Numerical solution of equilibrium problems

Let's consider a generic chemical reaction

\begin{equation}
\nu_aA + \nu_bB + \dots \leftrightharpoons \nu_xX + \nu_yY + \dots \tag{1}
\end{equation}

if the concentrations of the species are not the equilibrium ones, the reaction will progress (either forward or backwards) to reach the equilibrium conditions, and the free energy of the system will decrease.

An infinitesimal change of the system's free energy can then be written in terms of the chemical potential, $\mu$, of all the species involved in the reaction.

\begin{equation}
\mathrm{d}G = \sum_i \mu_i \mathrm{d}n_i \tag{2}
\end{equation}

Because the concentrations of reactants and products are coupled through the reaction equation, they cannot change independently, _i.e._ if any products are formed, some reactants must be consumed.
We can therefore define a unique parameter, $\xi$, as the _progress of the reaction_

\begin{equation}
\mathrm{d}\xi = \frac{\mathrm{d}n_i}{ \nu_i } \tag{3}
\end{equation}

where $\nu_i$ is the stoichiometric coefficient of the species $i$ taken as a positive number for products (formed) and as a negative number for reactants (consumed), and $\mathrm{d}n_i$ is an infinitesimal change in the concentration of species $i$.
Thus, the free energy change due to the progress of the reaction (in any direction) becomes

\begin{equation}
\mathrm{d}G = \sum_i \nu_i \mu_i \mathrm{d}\xi \tag{4}
\end{equation}

At equilibrium, the concentrations of all species are constant, $\mathrm{d}n_i=0$, and the reaction is not _progressing_ anymore, $\mathrm{d}\xi=0$, hence the free energy is not changing anymore.
On the other hand, if the system is out of equilibrium, there is a driving force that pulls the system towards equilibrium.
A force can always be obtained as the negative of the derivative of the energy with respect to some coordinate.
In this case, the driving force that pushes the reaction towards equilibrium can be immediately obtained from equation (4).

\begin{equation}
\frac{\mathrm{d}G}{\mathrm{d}\xi} = \sum_i \nu_i \mu_i = -force \tag{5}
\end{equation}

If we then substitute the definition of the chemical potential, $\mu=\mu^0+RT\ln x$, where $x$ is the molar fraction of the species, we obtain

\begin{eqnarray}
\frac{\mathrm{d} G}{\mathrm{d}\xi} &=& \sum_i\nu_i\mu_i\\
&=& \sum_i\nu_i \big[\mu_i^0 +RT\ln x_i\big]\\
&=& \sum_i\nu_i\mu_i^0 +RT\sum_i\nu_i\ln x_i\\
&=& \Delta_r G^0 +RT\sum_i\ln x_i^{\nu_i}\\
&=& \Delta_r G^0 +RT\ln \prod_i x_i^{\nu_i}\\
&=& \Delta_r G^0 +RT\ln Q\tag{6}
\end{eqnarray}

here $Q$ is the reaction quotient. By remembering the definition of equilibrium constant, $-RT\ln K_{eq}=\Delta_r G^0$ we get

\begin{equation}
force = -\frac{\mathrm{d} G}{\mathrm{d}\xi} = RT\ln K_{eq} - RT\ln Q = RT\ln\ \frac{K_{eq}}{Q} \tag{7}
\end{equation}

This equation provides us with a conceptual framework to solve *numerically* any equilibrium problem.
Because the above equations are differential, they are valid only for infinitesimal changes in the conditions.
Hence each a small change in the concentrations, $\nu_i\mathrm{d}c$ the driving force may change significantly.

Therefore we need to set up an iterative procedure where we compute the driving force, change the concentrations, compute the driving force\dots until the equilibrium is reached.

Given the concentration of all species, we can compute the driving force using the equation above and alter the concentrations of all species using

\begin{equation}
\mathrm{d}n_i \approx \nu_i \times force \times \mathrm{d}c  \tag{8}
\end{equation}

where $\nu_i$ are the stoichiometric coefficients of the species and $\mathrm{d}c$ is an adjustable parameter.
$\mathrm{d}c$ must be small enough to maintain the validity of the approximation abover but not too small to allow for a quick convergence of the procedure.

## Example #1: Dimerisation reaction
In order to elucidate how that procedure works we can take a model dimerisation reaction

\begin{equation}
2A \leftrightharpoons B
\end{equation}

whose equilibrium constant can be written as
\begin{equation}
K_{eq} = \frac{[B]}{[A]^2} = 0.156
\end{equation}

We now want to calculate the equilibrium concentrations of $[A]_{eq}$ and $[B]_{eq}$ given their initial concentrations $[A]_{0}$ and $[B]_{0}$.
Although this is a simple problem that can be solved analytically, in this workshop we will learn how we can use an iterative method to numerically solve it.
We will use a relatively simple minimisation procedure that can be applied to a large number of problems, for which it is not possible or it is too complicated to get an analytic solution.

Imagine mixing the reagents and then to be able to monitor the concentration of all the species in the system at very short discrete time intervals (*timesteps*). What you will see is that the concentrations will change and tend to the equilibrium value. As you have learnt in first year, the reaction quotient, $Q$, can be used to decided which way to reaction will proceed, and that at equilibrium the reaction quotient is equal to the equilibrium constant. Hence, as we have discussed in class, the reaction quotient and the equilibrium constant can be use to define a *driving force* that pulls the system towards equilibrium.

This *driving force* can then be used in conjunction with an *ICE* table to numerically compute the equilibrium concentration of reactant and products.

The working principle of the minimisation procedure that we will employ is

1. compute the reaction quotient
\begin{equation}
Q = \dfrac{\mathrm{[B]}_0}{\mathrm{[A]}^2_0}
\end{equation}

2. compute the driving force

\begin{equation}
F = RT \ln\bigg[\frac{K_{eq}}{Q}\bigg]
\end{equation}

3. evolve the concentrations using the ICE table

|      | [A]        | [B]
| :---: | :--------: |:---------:
| *I*  | [A]$_0$    | [B]$_0$
| *C*  | $-2F\delta c$        | $+F\delta c$
| *E*  | [A]$_0$-2$F\delta c$ | [B]$_0$+$F\delta c$

4. Repeat until the solution does not change

Let's try to implement this

# [Open empty notebook](Empty.ipynb)

### Critical thinking questions
1. Verify that the final equilibrium concentrations are independent of the starting conditions, provided that $[A]+[B]$ is constant
2. What is the effect of changing the values of $\delta c$ or the number of cycles?
3. Is the value of RT important?

## Example #2:  Dissociation of a mono-protic acid
As an exercise let's now try to calculate the final pH of a solution of a 0.15M of acetic acid

\begin{equation}
\mathrm{CH_3COOH \rightleftharpoons CH_3COO^{-} + H^+} \qquad pK_{a} = 3.74
\end{equation}

Remember that the equilibrium constant for the reation
\begin{equation}
AH \leftrightharpoons A^- + H^+
\end{equation}
is $K_{eq}=10^{-pK_a}$ and the reaction quotient
\begin{equation}
Q = \frac{[A^-][H^+]}{[HA]}
\end{equation}

We can then build the ICE table

|     | [HA] | [A$^-$] | [H$^+$]
| :---: |:---------: |:---------: |:---------:
| *I*  | [HA]$_0$ | [A$^-$]$_0$ | [H$^+$]$_0$
| *C*  | $-F_1\delta c$ | $+F_1\delta c$ | $+F_1\delta c$
| *E*  | [HA]$_0-F\delta c$ | [A$^-$]$_0+F_1\delta c$ | [H$^+$]$_0+F\delta c$

## Example #3: External titration
Let's now imagine that the pH of the solution of the previous example is kept constant by external titration.

What do you have to change in your program to account for this?
What is the concentration of [AH] if the pH is kept to values of 3, 6, 9 or 12?

## Example #4:  Coupled reactions
Let's now imagine we have two coupled reactions in the system, _e.g._ ascorbic acid in pure water.

\begin{eqnarray}
H_2C_6H_6O_6 + H_2O &\leftrightharpoons& H_3O^+ + HC_6H_6O_6^- &\qquad\qquad& pK_{a1} = 4.1\\
HC_6H_6O_6^- + H_2O &\leftrightharpoons& H_3O^+ + C_6H_6O_6^= &\qquad\qquad& pK_{a2} = 11.8 \\
\end{eqnarray}

For brevity, let's call ascorbic acid "H$_2$A".

We now have to compute two reaction quotients

\begin{eqnarray}
Q_1 = \frac{[HA^-][H^+]}{[H_2A]} &\qquad\qquad& Q_2 = \frac{[A^=][H^+]}{[HA^-]}\\
\end{eqnarray}

and use a "double" ICE table.

|      | [H$_2$A]   | [HA$^-$] | [A$^=$] | [H$^+$]
| :---: | :--------: |:---------: |:---------: |:---------:
| *I*  | [H$_2$A]$_0$ | [HA$^-$]$_0$ | [A$^=$]$_0$ | [H$^+$]$_0$
| *C1*  | $-F_1\delta c$ | $+F_1\delta c$ | $-$ | $+F_1\delta c$
| *C2*  | $-$ | $-F_2\delta c$ | $+F_2\delta c$  | $+F_2\delta c$
| *E*  | [H$_2$A]$_0-F_1\delta c$ | [HA$^-$]$_0+(F_1-F_2)\delta c$ | [A$^=$]$_0+F_2\delta c$ | [H$^+$]$_0+(F_1+F_2)\delta c$

Let's now try to implement this in our notebook.


# 基于脉冲的变分量子本征求解器

*版权所有 (c) 2021 百度量子计算研究所，保留所有权利。*

## 内容概要
**注意：完整运行本教程的程序可能会花费超过 50 个 Quantum Hub 点数**

本教程将介绍如何在脉冲层面实现变分量子本征求解器算法。本教程的大纲如下：

- 变分量子本征求解器（VQE）
- 基于脉冲的变分量子本征求解器（PBVQE）
- 准备工作
- 构造哈密顿量
- 优化双量比特门的脉冲
- 构造氢分子的哈密顿量
- 构造基于脉冲的参数化电路及优化
- 总结

## 变分量子本征求解器（VQE）

变分量子本征求解器（Variational Quantum Eigensolver, VQE）是在嘈杂中型量子（Noisy Intermediate-Scale Quantum, NISQ）计算机上运行的一种近似求解分子基态能量的算法。它的基本方法是估计给定哈密顿量的最小本征值并求其基态。对于近期的量子计算设备，门错误率较高、退相干时间较短以及连通性较差等问题限制了量子电路的深度。然而，VQE 算法只需要深度较低的量子电路即可实现，因而它被认为是利用 NISQ 设备解决实际问题的理想选择。

VQE 的基本任务是制备参数化的量子态（trail state）$|\psi(\vec{\theta})\rangle$ 并估计出给定分子离散哈密顿量 $\hat{H}_{\rm mole}$ 的基态能量。其中，量子态 $|\psi(\vec{\theta})\rangle$ 是由参数化的量子电路（ansatz）生成。在这个过程中，我们采用经典的优化方法来寻找一组最优的参数 $\vec{\theta}^*$，以最小化期望值 $E = \langle \psi(\vec{\theta}) | \hat{H}_{\rm mole} | \psi(\vec{\theta}) \rangle$，即分子哈密顿量 $\hat{H}_{\rm mole}$ 的近似基态能量 $E_0^*$：

$$
E_0^* = {\rm min}_{\vec{\theta}} \langle \psi(\vec{\theta}) | \hat{H}_{\rm mole} | \psi(\vec{\theta}) \rangle.
$$

在本教程中，我们将介绍在超导平台上使用 VQE 近似求解氢分子基态能量的基本方法。我们将考虑多种非理想因素，并从脉冲层面模拟 VQE 算法。首先，我们来介绍本教程中所使用的参数化量子电路模板，如下图所示：

它主要由若干参数化的单量子比特旋转门和 CNOT 门组成。由于 CNOT 门在超导平台上不能直接实现，因而在本教程中，我们将使用超导平台中实现效率更高的（hardware-efficient）双量子比特门，即 Cross-Resonance （CR）门来代替 CNOT 门作为纠缠门。同样，CR 门也可以配合若干单量子比特门产生最大纠缠态。理想 CR 门的矩阵为：

$$
\begin{equation}
\hat{U}_{\rm CR}(\alpha) = \begin{bmatrix}
\cos{\frac{\alpha}{2}} & -i\sin{\frac{\alpha}{2}} & 0 & 0 \\
-i\sin{\frac{\alpha}{2}} & \cos{\frac{\alpha}{2}} & 0 & 0 \\
0 & 0 & \cos{\frac{\alpha}{2}} & i\sin{\frac{\alpha}{2}} \\
0 & 0 & i\sin{\frac {\alpha}{2}} & \cos{\frac{\alpha}{2}}
\end{bmatrix}.
\end{equation}
$$

在这里，我们设置 $\alpha = -\pi/2$。关于 CR 门的更多细节请[点击这里](https://quanlse.baidu.com/#/doc/tutorial-cr)。

## 基于脉冲的变分量子本征求解器（PBVQE）

在本教程中，我们将从脉冲层面研究 VQE 算法，我们称之为 **pulse-based VQE (PBVQE)**。与标准的 VQE 算法不同，PBVQE 不再优化逻辑量子电路中每个旋转门的参数，而是直接将脉冲参数作为优化参数来最小化损失函数（即基态能量）。下图显示了 PBVQE 和标准 VQE 算法之间的差异：

为了实现 PBVQE，我们需要将逻辑量子电路转换成**基于脉冲的参数化量子电路（pulse-based ansatz）**，即逻辑旋转门 $R_x(\theta_m)$ 和 $R_y(\theta_m)$ 分别被 $X$ 和 $Y$ 通道上振幅不同的控制脉冲所取代，我们称之为**基于脉冲的量子门（pulse-based gates）**：

上图中，$U_{\rm ENT}$ 是用于产生纠缠的酉算符（细节将会在后面的章节介绍）。这里，我们使用一种新的符号来表示**基于脉冲的量子门**的参数：

$$
\vec{A} = [A_0, \cdots, A_m, \cdots, A_{M-1}],
$$

其中，$M$ 是**基于脉冲的量子门**的个数；$A_m$ 表示第 $m$ 个**基于脉冲的量子门**的高斯波形的振幅，因而脉冲包络的函数可以写为：

$$
\Omega_m(t) = A_m e^{-(\frac{t - \tau_m}{\sqrt{2} \sigma_m}) ^2}.
$$

除脉冲强度以外的其它高斯脉冲参数，如宽度 $\sigma_m$ 和中心位置 $\tau_m$ 等在整个过程中都将被固定。这样一来，每个**基于脉冲的量子门**都只有一个需要优化的参数。引入**基于脉冲的参数化量子电路**后，在每次迭代中我们不再需要优化产生用于实现逻辑量子电路的驱动脉冲，这大大提高了 VQE 的效率和结果的准确性。

在上面的章节中，我们简要介绍了传统的 VQE 和 PBVQE。在下面的部分中，我们将逐步演示使用量脉实现 PBVQE 的方法。

## 准备工作

成功安装量脉后，您可以按照本教程运行下面的量脉程序。要运行此教程，您需要从量脉（Quanlse）和其它常用 Python 库导入以下包：

```python
# This module creates the Hamiltonian dictionary
from Quanlse.QHamiltonian import QHamiltonian

# These functions help us perform matrix calculations
from Quanlse.Utils.Functions import tensor
from Quanlse.Utils.Infidelity import unitaryInfidelity

# These functions define useful operator matrices
from Quanlse.QOperator import sigmaX, sigmaY, sigmaZ, sigmaI

# This function generates wave data
from Quanlse.QWaveform import gaussian, square

# This function uploads jobs to Quanlse Cloud Service and receives results
from Quanlse.remoteSimulator import remoteSimulatorRunHamiltonian as runHamiltonian

# This module defines matrices of the frequently used quantum gates
from Quanlse.QOperation import FixedGate

# This module saves the PBVQE results
from Quanlse.Define import outputPath
```

```python
# Import the necessary packages
import os
from numpy import linalg, min, random, savez, load, identity, kron
from math import pi
from functools import reduce
from scipy import optimize

# Generate the path of npz file
localFile = os.path.join(outputPath, f'pbvqe.npz')
```

## 构造哈密顿量

首先，我们定义一些必要的常数，包括任意波形发生器（arbitrary wave generator, AWG）的采样周期、系统的量子比特的数量及能级。

```python
# Sampling period (Nano second)
dt = 2.0

# Number of qubits
qubits = 4

# System energy level
level = 2
```

然后，我们定义超导量子比特的硬件参数。`freq` 列表中的项分别是 $\omega_{\rm q0}, \omega_{\rm q1}, \omega_{\rm q2}, \omega_{\rm q3}$，即每个量子比特的跃迁频率；`coupling` 列表中的项分别保存了量子比特 0-1、1-2、2-3、3-0 的耦合信息。利用旋转波近似（Rotating Wave Approximation, RWA），我们将系统定义在频率为 $\omega_{\rm RWA} = \omega_{\rm q0} = \omega_{\rm q2} = 4.914 \times 2\pi$ GHz 的旋转坐标系中。

```python
# Define the hardware parameters of the qubits (GHz)
freq = [4.914 * (2 * pi), 5.114 * (2 * pi), 4.914 * (2 * pi), 5.114 * (2 * pi)]

# Define the coupling strength (GHz)
coupling = [
    [[0, 1], 0.016 * (2 * pi)],
    [[1, 2], 0.016 * (2 * pi)],
    [[2, 3], 0.016 * (2 * pi)],
    [[3, 0], 0.016 * (2 * pi)]
]

# Frequency of rotating frame (GHz)
rwa = 4.914 * (2 * pi)
```

接下来，我们为所有的单量子比特门和双量子比特门设置固定的执行时间：

```python
# Gate duration time (Nano second)
tg2q = 200
tg1q = 64
```

随后，我们根据以下硬件结构使用量脉创建其哈密顿量，每个量子比特与其相邻量子比特耦合，耦合强度为一个恒定值：

上述系统的哈密顿量可以写为：
$$
\hat{H}_{\rm total} = \sum_{q=0}^{3} \delta_{q} \hat{a}^{\dagger}_{q}\hat{a}_{q} + \frac{1}{2}\sum_{q=0}^{3}g_{q,(q+1) {\rm\ mod}\ 4}(\hat{a}_{q}\hat{a}^{\dagger}_{(q+1) {\rm\ mod}\ 4}+\hat{a}^{\dagger}_{q}\hat{a}_{(q+1) {\rm\ mod}\ 4}) + \sum_{q=0}^{3}\Omega_{q}^x (t) \hat{\sigma}_{q}^{x} + \sum_{q=0}^{3}\Omega_{q}^y (t) \hat{\sigma}_{q}^{y} + \sum_{q=0}^{3}\Omega_{q}^z (t) \hat{\sigma}_{q}^{z} ,
$$

其中 $\hat{a}_{q}$ 和 $\hat{a}^{\dagger}_{q}$ 分别是作用在第 $q$ 个量子比特的湮灭和产生算符。$\hat{\sigma}^x_{q}, \hat{\sigma}^y_{q}$ 和 $\hat{\sigma}^z_{q}$ 分别是作用在第 $q$ 个量子比特上的泡利算符。$\delta_{q}=\omega_{q} - \omega_{\rm RWA}$ 表示第 $q$ 个量子比特的失调强度；$g_{q,(q+1){\rm\ mod}\ 4}$ 是第 $q$ 和第 $(q+1) {\rm\ mod}\ 4$ 个量子比特之间的耦合强度； $\Omega_q^{x,y,z}(t)$ 是作用在第 $q$ 个量子比特上的磁通调控或微波调控的包络函数。我们可以使用量脉方便地定义上述系统的哈密顿量：

```python
# Create the Hamiltonian
vqeHam = QHamiltonian(qubits, level, dt)

# Add the coupling terms
for item in coupling:
    q0, q1 = item[0][0], item[0][1]
    vqeHam.addCoupling([q0, q1], g=item[1] / 2)

for qubit in range(qubits):
    # Add the detuning terms
    detuning = freq[qubit] - rwa
    vqeHam.addDrift(sigmaZ, qubit, coef=detuning)
```

关于使用量脉构建哈密顿量的更多方法，可以查看教程[单量子比特门](https://quanlse.baidu.com/#/doc/tutorial-single-qubit)。

## 优化双量子比特门

在本教程中，我们使用 CR 门作为纠缠门（关于 CR 门的更多信息，可以查看教程：[Cross-Resonance 门](https://quanlse.baidu.com/#/doc/tutorial-cr)）。由于在本教程中，相邻量子比特之间的耦合方式为直接耦合，因此在一个量子比特上施加 $X$ 脉冲会同时影响到另外两个相邻的量子比特。因此，我们在设计脉冲时需要考虑这个因素，以抑制串扰造成的影响。

在这里，我们使用 `vqeHam.subSystem()` 从系统哈密顿量 `vqeHam` 中提取两个由三个量子比特组成的子系统用于优化 CR 门，其中一个是由量子比特 0-1-2 组成的子系统，另一个是由量子比特 1-2-3 组成的子系统。在这些子系统上，我们分别设置 $\hat{U}_{\rm goal}=I\otimes\hat{U}_{\rm CR}$ 作为目标酉矩阵来优化相应脉冲，即在子系统的第二和第三个量子系统上生成一个 CR 门。

我们定义函数 `makeCrPulse()` 用于生成 CR 门所需的脉冲序列。我们在当前子系统的第二个量子比特上施加高斯微波驱动脉冲，同时固定其宽度和中心位置，并将其振幅作为优化的第一个参数。第二个需要优化的参数是施加在第一个量子比特上的磁通控制的振幅。请注意，`tag="det"` 的驱动还同时用于将旋转坐标参考系转换为特定频率。

```python
def makeCrPulse(ham, subSys3q, driveFreq, amp, shift, t):
    """ Assemble the pulses for CR gates """
    subHam = ham if subSys3q is None else ham.subSystem(subSys3q)
    subHam.clearWaves()
    subHam.appendWave(sigmaX, 1, gaussian(t, amp, tg2q / 2, tg2q / 8), tag="XY")
    # frame transformation
    subHam.appendWave(sigmaZ, 0, square(t, rwa - driveFreq + shift), tag="Z")
    subHam.appendWave(sigmaZ, 1, square(t, rwa - driveFreq), tag="det")
    subHam.appendWave(sigmaZ, 2, square(t, rwa - driveFreq), tag="det")
    return subHam.job if subSys3q is None else subHam.outputInverseJob(qubits)
```

随后，我们定义一个函数 `optimize_cr()` 来进行优化过程，并保存最佳参数以供进一步使用。

```python
def optimizeCr(subSys3q, driveFreq):
    """ Realize a CR gate on the second & third qubits """
    crHam = vqeHam.subSystem(subSys3q)
    uGoal = tensor([identity(2), FixedGate.CR.getMatrix()])

    def crLoss(_x):
        # Clear and add waves
        crHam.clearWaves()
        # Generate and add waves for CR gate implementation
        _crJob = makeCrPulse(crHam, None, driveFreq, _x[0], _x[1], tg2q)
        # Simulate the system's evolution and obtain the infidelity
        unitary = crHam.simulate(job=_crJob)[0]["unitary"]
        infidelity = unitaryInfidelity(uGoal, unitary, 3)
        return infidelity

    opt = optimize.dual_annealing(crLoss, [(-2, 2), (-0.2, 0.2)], maxiter=60)
    print("Min infidelity:", opt["fun"])
    return opt["x"][0], opt["x"][1]

lhlQ1X, lhlQ0Z = optimizeCr([0, 1, 2], 4.914 * 2 * pi)
hlhQ1X, hlhQ0Z = optimizeCr([1, 2, 3], 5.114 * 2 * pi)
```

## 构造氢分子的哈密顿量

在这一节中，我们将介绍如何在脉冲层面上估计氢分子的基态能量。我们将省略费米子—量子比特（fermion-to-qubit）映射的具体细节（请访问[量桨](https://github.com/PaddlePaddle/Quantum/blob/master/tutorial/quantum_simulation/VQE_CN.ipynb)获得更多相关信息）。首先，我们定义一个函数 `pauli_str_to_matrix()`，将**泡利字符串**转换为氢分子的离散哈密顿量 $\hat{H}_{\rm mole}$：

```python
def pauliStrToMatrix(pauli_str, n):
    """
    Convert the Pauli string in Hamiltonian
    """
    def NKron(AMatrix, BMatrix, *args):
        return reduce(
            lambda result, index: kron(result, index),
            args,
            kron(AMatrix, BMatrix), )
    pauli_dict = {
        'i': sigmaI().matrix,
        'x': sigmaX().matrix,
        'y': sigmaY().matrix,
        'z': sigmaZ().matrix
    }
    # Parse pauli_str; 'x0,z1,y4' to 'xziiy'
    new_pauli_str = []
    for coeff, op_str in pauli_str:
        init = list('i' * n)
        op_list = op_str.split(',')
        for op in op_list:
            pos = int(op[1:])
            assert pos < n, 'n is too small'
            init[pos] = op[0]
        new_pauli_str.append([coeff, ''.join(init)])

    # Convert new_pauli_str to matrix; 'xziiy' to NKron(x, z, i, i, y)
    matrices = []
    for coeff, op_str in new_pauli_str:
        sub_matrices = []
        for op in op_str:
            sub_matrices.append(pauli_dict[op])
        if len(op_str) == 1:
            matrices.append(coeff * sub_matrices[0])
        else:
            matrices.append(coeff * NKron(sub_matrices[0], sub_matrices[1], *sub_matrices[2:]))

    return sum(matrices)
```

在这里，我们使用原子间隔为 $d=74$ pm 的氢分子空间构型数据，这些数据来自[量桨](https://github.com/PaddlePaddle/Quantum/blob/master/tutorial/quantum_simulation/VQE_CN.ipynb)。

```python
targetHam = [
    [-0.042078976477822, 'i0'],
    [ 0.177712874651399, 'z0'],
    [ 0.177712874651399, 'z1'],
    [-0.242742805131446, 'z2'],
    [-0.242742805131462, 'z3'],
    [ 0.170597383288005, 'z0,z1'],
    [ 0.044750144015351, 'y0,x1,x2,y3'],
    [-0.044750144015351, 'y0,y1,x2,x3'],
    [-0.044750144015351, 'x0,x1,y2,y3'],
    [ 0.044750144015351, 'x0,y1,y2,x3'],
    [ 0.122933050561837, 'z0,z2'],
    [ 0.167683194577189, 'z0,z3'],
    [ 0.167683194577189, 'z1,z2'],
    [ 0.122933050561837, 'z1,z3'],
    [ 0.176276408043195, 'z2,z3']
]
hMatrix = pauliStrToMatrix(targetHam, 4)
```

上述分子哈密顿量基态能量的理论值可以通过如下方法计算：

```python
# Calculate the theoretical eigenvalue
eigVal, eigState = linalg.eig(hMatrix)
minEigH = min(eigVal.real)
print(f"Ground state energy: {minEigH} Ha")
```

## 基于脉冲的量子电路

首先，我们参考标准 VQE 中最常用的参数化量子电路模板，设计了一个基于脉冲的量子电路。下图显示了该量子电路中的一层，其中，每个量子比特都包含 3 个单量子比特门，而每个单量子比特门都有一个参数作为高斯脉冲包络的最大振幅，脉冲宽度和中心位置是固定的。

由于脉冲电路较为复杂，因而我们定义了一个函数 `makeWaveSchedule()` 专门用于生成并排列上述电路所对应的脉冲序列。其中，参数 `x` 是优化参数列表（即脉冲参数 $\vec{A}$）；`vqeJob` 是由 `addWave()` 生成的波形数据列表，用于保存用户定义波形的详细信息。

```python
def makeWaveSchedule(x):
    """ Generate waves for pulse-based circuit """
    # Generate pulses for CR gate
    crJob = vqeHam.createJob()
    crJob += makeCrPulse(vqeHam, [3, 0, 1], 5.114 * 2 * pi, hlhQ1X, hlhQ0Z, tg2q)
    crJob += makeCrPulse(vqeHam, [0, 1, 2], 4.914 * 2 * pi, lhlQ1X, lhlQ0Z, tg2q)
    crJob += makeCrPulse(vqeHam, [1, 2, 3], 5.114 * 2 * pi, hlhQ1X, hlhQ0Z, tg2q)
    crJob += makeCrPulse(vqeHam, [2, 3, 0], 4.914 * 2 * pi, lhlQ1X, lhlQ0Z, tg2q)
    # Assemble the pulses
    depth = int(len(x) / 12)
    vqeJob = vqeHam.createJob()
    for d in range(depth):
        gate1QJob = vqeHam.createJob()
        # Add pulses for single-qubit gates
        for q in range(4):
            # X/Y/X controls
            gate1QJob.addWave(sigmaX, q, gaussian(tg1q, x[12 * d + q], tg1q / 2, tg1q / 8), t0=0)
            gate1QJob.addWave(sigmaY, q, gaussian(tg1q, x[12 * d + 4 + q], tg1q / 2, tg1q / 8), t0=tg1q)
            gate1QJob.addWave(sigmaX, q, gaussian(tg1q, x[12 * d + 8 + q], tg1q / 2, tg1q / 8), t0=tg1q * 2)
            # Set detuning
            gate1QJob.addWave(sigmaZ, q, square(tg1q * 3, rwa - freq[q]), t0=0, tag="det")
        vqeJob += gate1QJob
        vqeJob += crJob
    return vqeJob
```

在本教程中，我们使用 `Scipy` 提供的基于梯度的优化方法（L-BFGS-B）来最小化目标函数。在每次迭代中，L-BFGS-B 需要用户提供每个参数的梯度信息，在这里我们使用两点有限差分法来近似计算梯度：
$$
\frac{\partial{\rm Loss}(\vec{A})}{\partial a_m} = \frac{{\rm Loss}(a_0, \cdots, a_m + \epsilon, \cdots, a_{M-1}) - {\rm Loss}(a_0, \cdots, a_m - \epsilon, \cdots, a_{M-1})}{2\epsilon} ,
$$

其中，$\vec{A} = [A_0, \cdots, A_{M-1}]$ 是脉冲参数列表，$\epsilon$ 是一个很小的正数，而损失函数 ${\rm Loss}(\vec{A})$ 定义为：

$$
{\rm Loss}(\vec{A}) =  \langle \psi(\vec{A}) | \hat{H}_{\rm mole} | \psi(\vec{A}) \rangle.
$$

其中，量子态 $\psi(\vec{A})$ 是基于脉冲的量子电路所产生的。有限差分法需要大量的样本，例如，当脉冲参数的参数为 $M$ 时，我们需要 $2M$ 次采样来估计近似梯度。因此，我们使用量脉云服务来加速这个过程。

为了使用量脉云服务，我们需要导入 `Define` 并传入 token，用户可以在 [Quantum-hub](http://quantum-hub.baidu.com) 申请获得 token。

```python
# Define the loss function
import copy
from Quanlse import Define
Define.hubToken = ""
```

接下来，我们定义 VQE 的损失函数。在这个函数中，我们模拟了脉冲参数为 $\vec{x}$ 时基于脉冲的电路的演化，并用上面提到的有限差分法计算这一点的梯度。在每次迭代中，我们将当前脉冲参数列表 $\vec{x}$ 输入到损失函数中，并将所有采样所需的脉冲数据生成并打包到 `waveList` 中。最终，`waveList` 包含用于求解梯度的 $2M$ 次采样和用于获取损失值的 1 个采样的脉冲数据。

我们在上面的步骤中将所有的任务集成到一个列表中，即 `waveList`，并通过函数 `runHamiltonian()` 将任务列表提交给量脉云服务。正常情况下，大约 15 到 20 秒后，我们将收到返回结果，结果将作为 JSON 文件保存到 `Output` 文件夹中。同时，变量 `result` 会被赋予一个列表，其中包含与 `waveList` 对应的所有模拟结果。

**注意**：`waveList` 的每一项都包含由 `makeWaveSchedule()` 函数生成的基于脉冲的 VQE 的所有波。

```python
def loss(x):
    global lossHistory
    # Add wave for current point
    waveList = vqeHam.createJobList()
    waveList.addJob(makeWaveSchedule(x))

    # Add wave for calculating gradient
    for xId in range(len(x)):
        xList = copy.deepcopy(x)
        xList[xId] -= 1e-8
        waveList.addJob(makeWaveSchedule(xList))
        xList[xId] += 2 * 1e-8
        waveList.addJob(makeWaveSchedule(xList))

    # Simulate the evolution
    result = runHamiltonian(vqeHam, jobList=waveList)

    # Calculate the loss function
    lossList = []
    for item in result:
        state = item["unitary"]
        lossVal = (state.conj().T @ hMatrix @ state).real[0][0]
        lossList.append(lossVal)

    # Calculate the gradients
    gradient = []
    for index in range(len(x)):
        gradient.append((lossList[2 + 2 * index] - lossList[1 + 2 * index]) / 1e-8 / 2)

    print("Loss function:", lossList[0])
    lossHistory.append(lossList[0])
    return lossList[0], gradient
```

然后我们使用由 `Scipy` 提供的 `fmin_l_bfgs_b()` 函数最小化前面定义的损耗函数。

**注意**：此优化可能需要超过 15 分钟。

```python
depth = 3
lossHistory = []
initParas = [random.rand() for _ in range(depth * 12)]
bounds = [(-1.5, 1.5) for _ in range(depth * 12)]
x, f, d = optimize.fmin_l_bfgs_b(loss, initParas, fprime=None, bounds=bounds, maxiter=200)

# Save the loss history to a file for further usage
savez(localFile, lossHistory)
```

```python
print(f"The estimated ground state energy is: {f} Ha")
print("Total iteration:", d["nit"])
```

可见，最终收敛的精度很高，迭代次数为 72 次。随后，我们绘制完整的迭代过过程：

```python
# Load the loss_history list from the npz file.
lossHistory = load(localFile)['arr_0']

# Plot the figures
import matplotlib.pyplot as plt
plt.plot(range(len(lossHistory)), lossHistory, label="Energy")
plt.axhline(minEigH, c="gray", ls="--", lw=1.0)
plt.xlabel("Iteration")
plt.ylabel("Energy (Ha)")
plt.show()
```

最后，我们可以使用 `plot()` 方法绘制脉冲序列：

```python
# Print the waveforms.
makeWaveSchedule(x).plot(color=['red', 'green', 'blue'])
```

## 总结
用户可以通过点击这个链接 [tutorial-pbvqe.ipynb](https://github.com/baidu/Quanlse/blob/main/Tutorial/CN/tutorial-pbvqe-cn.ipynb) 跳转到此 Jupyter Notebook 文档相应的 GitHub 页面并获取相关代码以运行该程序。我们鼓励用户使用量脉开发更多脉冲层的 NISQ 算法。

## 参考文献

\[1\] [Peruzzo, Alberto, et al. "A variational eigenvalue solver on a photonic quantum processor." *Nature communications* 5 (2014): 4213.](https://doi.org/10.1038/ncomms5213)

\[2\] [Moll, Nikolaj, et al. "Quantum optimization using variational algorithms on near-term quantum devices." *Quantum Science and Technology* 3.3 (2018): 030503.](https://doi.org/10.1088/2058-9565/aab822)

\[3\] [Kandala, Abhinav, et al. "Hardware-efficient variational quantum eigensolver for small molecules and quantum magnets." *Nature* 549.7671 (2017): 242-246.](https://doi.org/10.1038/nature23879)

\[4\] [Rigetti, Chad, and Michel Devoret. "Fully microwave-tunable universal gates in superconducting qubits with linear couplings and fixed transition frequencies." *Physical Review B* 81.13 (2010): 134507.](https://doi.org/10.1103/PhysRevB.81.134507)

\[5\] [Meitei, Oinam Romesh, et al. "Gate-free state preparation for fast variational quantum eigensolver simulations: ctrl-VQE." *arXiv preprint arXiv:2008.04302* (2020).](https://arxiv.org/abs/2008.04302)

\[6\] [Wilhelm, Frank K., et al. "An introduction into optimal control for quantum technologies." *arXiv preprint arXiv:2003.10132* (2020).](https://arxiv.org/abs/2003.10132)

\[7\] [Krantz, Philip, et al. "A quantum engineer's guide to superconducting qubits." *Applied Physics Reviews* 6.2 (2019): 021318.](https://aip.scitation.org/doi/abs/10.1063/1.5089550)


```octave
% some housekeeping stuff
register_graphics_toolkit ("gnuplot");
available_graphics_toolkits ();
graphics_toolkit ("gnuplot")
clear
% load packages
pkg load statistics
% end of housekeeping
```

    error: package statistics is not installed
    error: called from
        load_packages at line 47 column 7
        pkg at line 588 column 7

# Chemical speciation modelling

 Solving for the equilibrium position of a set of simultaneous reactions subject to the constraints of mass balance and mass action is a common problem in environmental modeling.
There exist many available computer programs to solve these types of chemical equilibrium
problems, examples include MINEQL (Allison et al., 1991) and PHREEQ (Saini-Eidukat
and Yahin, 1999). For engineering and scientific practices though these stand-alone pro-
grams are not necessarily completely useful. Often, engineers and scientists will use so-called high-level programming languages such as Matlab or Scilab to implement their larger scale problems. Problems such as reactive transport, or simulation of industrial scale processes can be “coded” into Matlab in a very flexible task-specific manner.

Many processes require the solution of simultaneous equilibria and thus the speciation
code presented here was developed for Matlab. In the researcher’s lab this code is used
to fit experimental data to chemical equilibrium binding constants for metal/organic and
metal/phosphate systems but with slight modifications the code could be embedded in Mat-
lab code for other applications (such as transport modeling).

The Matlab code was written based on the paper by (Carrayrou et al., 2002). An example calculation for the Fe(III) system in water is presented below.

### Iron (III) system

Mass balance and mass action for solving chemical equilibrium problems can all be represented in Tableau notation (Smith and Ferris, 2001; Morel and Hering, 1993). For a given defined system it is desirable to determine the equilibrium concentration of all species. Using the iron (III) system as an example a list of species of interest could include H+, OH – , Fe3+, FeOH2+, Fe(OH) +2 , and Fe(OH) –4 . There are other possible species but to keep the discussion simple these are the only ones included here. Also, for dilute solutions water can be assumed as a fixed component. The list includes six species so it is necessary to define six relationships in order to solve this system. The situation can be simplified if it is realized that each of these species are not independent. We can select components from the list of species and use those components to solve the equilibrium problem. For example, if we know H+ (pH) and Fe3+ we can determine the concentration of all other species from their logK values (mass action). In matrix notation we think of pH and Fe3+ as spanning the basis set.

Now we need two equations and two unknowns. First relation is mass balance of iron and
the second is proton balance (related to electroneutrality). Now we can write the tableau:

| H+ | Fe3+ | logK | species |
| --- | --- | --- | --- |
| 1 | 0 | 0 | H$^+$ |
| 0 | 1 | 0 | Fe$^{3+}$ |
| -1 | 0 | -14 | OH$^-$ |
| -1 | 1 | -2.19 | FeOH$^{2+}$ |
| -2 | 1 | -5.67 | Fe(OH)$_2^+$ |
| -4 | 1 | -21.6 | Fe(OH)$_4^-$ |
||||
| TOTH | FeT | |  |

Table 1 completely defines the equilibrium problem. The entries in the columns are the
stoichiometric coefficients required for formation of each species. For example, there is 1 H+ and 0 Fe3+ in H+. Also, Fe(OH) –4 is formed with one iron and removing 4 protons from
water. Given the tableau, all that remains is to determine the values for [H+] and [Fe3+] for specified TOTH and FeT. If you multiply across rows it is possible to determine species concentration and if you sum down columns the total values (mass balance) are recovered. This is best understood by writing out some entries:

\begin{align}
{[\mathrm{H}+]}&={[\mathrm{H}+]}^1\times{[\mathrm{Fe}^{3+}]}^0\times10^0\\
{[\mathrm{Fe}^{3+}]}&={[\mathrm{H}+]}^0\times{[\mathrm{Fe}^{3+}]}^1\times10^0\\
{[\mathrm{OH}^-]}&={[\mathrm{H}+]}^{-1}\times{[\mathrm{Fe}^{3+}]}^0\times10^{-14} \label{eqn:Kw}\\
{[\mathrm{FeOH}^{2+}]}&={[\mathrm{H}+]}^{-1}\times{[\mathrm{Fe}^{3+}]}^1\times10^{-2.19} \label{eqn:KH1}\\
&\text{....}
\end{align}

If equation Kw is rearranged to solve for $\mathrm{[OH-]}$ then the equation above for [OH-] s obtained.  As another example, to understand equation formation of FeOH given above, consider that $K_{H1}$ for the first hydrolysis of iron (III) corresponds to the following reaction:

\begin{equation}
\mathrm{Fe^{3+}} + \mathrm{H_2O} \rightleftharpoons \mathrm{FeOH^{2+}} + \mathrm{H+} \quad K_{H1} = \dfrac{\mathrm{[FeOH^{2+}]}\mathrm{[H+]}}{\mathrm{[Fe^{3+}]}}
\end{equation}

if K is rearranged to solve for $\mathrm{[FeOH^{2+}]}$ then the equation $\mathrm{[FeOH^{2+}]}={[\mathrm{H}+]}^{-1}\times{[\mathrm{Fe}^{3+}]}^1\times10^{-2.19}$  is obtained.  To explain how the summation down the columns is the mass balance.  Consider total iron ....

\begin{equation}
\mathrm{Fe_T}=0\mathrm{[H+]}+1\mathrm{[Fe^{3+}]}+0\mathrm{[OH^-]}+1\mathrm{[FeOH^{2+}]}+1\mathrm{[Fe(OH)2^+]}+1\mathrm{[Fe(OH)4^-]}
\end{equation}

notice that the coefficients are the entries down the iron column in the tableau.

The problem can easily be expressed in matrix notation.  We'll define the $1 \times 2$ vector of unknown component concentrations as $\mathbf{X}$ so we can write $\mathbf{X}=\bigl( \begin{smallmatrix}
\log\mathrm{[H+]} & \log\mathrm{[Fe^{3+}]} \end{smallmatrix} \bigr)$.  There is a $6 \times 1$ vector of species concentrations as well \begin{equation}
\mathbf{C}= \begin{pmatrix}
\mathrm{[H+]} \\
\mathrm{[Fe^{3+}]} \\
\mathrm{[OH-]} \\
\mathrm{[FeOH^{2+}]} \\
\mathrm{[Fe(OH)2+]} \\
\mathrm{[Fe(OH)4-]}
\end{pmatrix}     \end{equation}

Total concentrations are in a $2\times 1$ vector $\mathbf{T} = \bigl( \begin{smallmatrix}
\text{TOTH} \\ \text{Fe}_{\mathrm{T}} \end{smallmatrix} \bigr)$.  The logK values are summarized in the $ 6\times 1$ vector
\begin{equation}
 \mathbf{K}=\begin{pmatrix}
0\\
0 \\
\log{K_w}\\
\log{K_{H1}} \\
 \log{K_{H2}}\\
  \log{K_{H4}}\\
   \end{pmatrix} \end{equation}

Finally, we need a $6 \times 2$ matrix of stoichiometric coefficients
\begin{equation}
\mathbf{A} = \begin{pmatrix} 1&0\\
0&1\\
-1 & 0\\
-1 & 1\\
-2 & 1\\
-4 & 1\\
 \end{pmatrix}
\end{equation}

Now the minimization problem is to determine $\mathbf{X}$ that minimizes the residuals in the mass balance.  This calculation is performed as follows:
\begin{align}
 \text{minimize $\mathbf{R}$ as a function of $\mathbf{X}$ where}\quad
 \mathbf{R}&=\mathbf{A}'\times(10^\mathbf{C})-\mathbf{T}\\
  \text{and} \quad      \mathbf{C}&=10^{(\mathbf{K}+\mathbf{A}\times\mathbf{X}')}
\end{align}

Minimization can be performed using all element of $\mathbf{R}$ using Newton-Raphson method for example or using other nonlinear optimization methods on some summation of $\mathbf{R}$ such as sum of squares or sum of absolute values of residuals (BrassardandBodurtha:2000).  In this case $\mathbf{R}$ would be a $2 \times 1$ vector of mass balance residuals.

To solve this here first we will add some functions.  One to return the "tableau" and one to adjust for fixed pH (for this example we calculate speciation versus pH, so pH will be fixed).  The third function below solves for the minimum in the difference between calculated and actual mass balance.

```octave
function [KSOLUTION,ASOLUTION,SOLUTIONNAMES] = get_equilib_defn
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Tableau=[...
%H	  FeIII        logK            species
 1	  0	            0              {'H'}
 0	  1	            0              {'Fe'}
-1	  0	            -14            {'OH'}
-1    1             -2.19          {'FeOH'}
-2    1             -5.67          {'FeOH2'}
-4    1             -21.6          {'FeOH4'}
];

n=size(Tableau,2);
ASOLUTION=cell2mat(Tableau(:,1:n-2));
KSOLUTION=cell2mat(Tableau(:,n-1));
SOLUTIONNAMES=strvcat(Tableau(:,n));

end

```

```octave
% ----------- for fixed pH ----------------

function [Ksolution,Asolution]=get_equilib_fixed_pH(KSOLUTION,ASOLUTION,pH)

    [N,M]=size(ASOLUTION);
    Ksolution=KSOLUTION-ASOLUTION(:,1)*pH;
    Asolution=[ASOLUTION(:,2:M)];

end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
```

```octave
%%file nl_massbalancerrnosolid_NR.m

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [X,F,J,C] = nl_massbalancerrnosolid_NR(X,Asolution,Ksolution,T)

[Nc,Nx]=size(Asolution); %Xsolution=X(1:Nx);
criteria=1e-15;

for i=1:1000

logC=(Ksolution)+Asolution*log10(X); C=10.^(logC); % calc species
R=Asolution'*C-T;

% Evaluate the Jacobian
   z=zeros(Nx,Nx);
for j=1:Nx
	for k=1:Nx
		for i=1:Nc; z(j,k)=z(j,k)+Asolution(i,j)*Asolution(i,k)*C(i)/X(k); end
   	end
end

J = z;

deltaX=z\(-1*R);
one_over_del=max([1, -1*deltaX'./(0.5*X')]);
del=1/one_over_del; X=X+del*deltaX;

tst=sum(abs(R));
if tst<=criteria; break; end

end

F=[R];

end

```

    Created file '/run/media/ssmith/T7/ssmith/scottsmith/teaching/CH480_numerical_methods_chemistry/OCTAVE/CH480CH680/nl_massbalancerrnosolid_NR.m'.

### make the graph

So for each pH we will solve the equilibrium problem.  First load the tableau, then modify for the pH of that iternation. Then solve.  Then store the results and loop.

At the end make a plot.

```octave
%plot -s 600,600 -f 'svg'

%define the equilibrium sytem (The tableau)
[KSOLUTION,ASOLUTION,SOLUTIONNAMES] = get_equilib_defn;
% initial guess (just Fe becasue pH is fixed)
Feguess=[-5.5]; guess=[10.^Feguess];

%set the pH range and total
pH=2:0.1:12; FeT=1e-4; T=[FeT];

%now for each pH solve
for i=1:length(pH)
% adjust for fixed pH
[Ksolution,Asolution]=get_equilib_fixed_pH(KSOLUTION,ASOLUTION,pH(i));
% calculate species using NR
[X,F,J,C] = nl_massbalancerrnosolid_NR(guess,Asolution,Ksolution,T);
species_summary(:,i)=C; err(i)=F;
end

for i=1:size(species_summary,1)
txt=[SOLUTIONNAMES(i,:),"=species_summary(i,:);"]; eval(txt);
end

h=plot(pH,Fe/T,pH,FeOH/T,pH,FeOH2/T,pH,FeOH4/T);
set(gca,"fontsize",12); set(h,'linewidth',2); set(gca,'linewidth',2);
h=xlabel('pH'); set(h,'fontsize',12); h=ylabel('fraction of total Fe'); set(h,'fontsize',12);
legend('Fe','FeOH','FeOH2','FeOH4','Location','northeast','Orientation','vertical');
axis([min(pH) max(pH) 0 1])

```

```octave
plot(pH,err)
```

### exercises 7

recalculate the speciation above but at 4e-5 M added salicylic acid, H2L (There is already 1e-4 M Fe(III).

Fe + L = FeL logK=17.55

pKa1=2.97 pKa2=13.7

plot species versus pH, and also a plot of the two mass balance errors versus pH.

for matrix dimensions to match, you will need to put totals in like this T=[FeT; LT];

```octave

```


##### Copyright 2020 The TensorFlow Authors.

```
#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
```

# 计算梯度

       在 TensorFlow.org 上查看
       在 Google Colab 运行

       在 Github 上查看源代码
       下载笔记本

本教程探讨适用于量子电路期望值的梯度计算算法。

计算量子电路中某个可观测对象的期望值的梯度是一个复杂的过程。可观测对象的期望值并不具备总是易于编写的解析梯度公式——这不同于诸如矩阵乘法或向量加法等具备易于编写的解析梯度公式的传统机器学习变换。因此，可以轻松地为不同的场景采用不同的量子梯度计算方法。本教程比较了两种不同的微分方案。

## 设置

```
!pip install tensorflow==2.4.1
```

安装 TensorFlow Quantum：

```
!pip install tensorflow-quantum
```

```
# Update package resources to account for version changes.
import importlib, pkg_resources
importlib.reload(pkg_resources)
```

现在，导入 TensorFlow 和模块依赖项：

```
import tensorflow as tf
import tensorflow_quantum as tfq

import cirq
import sympy
import numpy as np

# visualization tools
%matplotlib inline
import matplotlib.pyplot as plt
from cirq.contrib.svg import SVGCircuit
```

## 1. 准备工作

我们来更具体地说明量子电路的梯度计算概念。假设您具有如下所示的参数化电路：

```
qubit = cirq.GridQubit(0, 0)
my_circuit = cirq.Circuit(cirq.Y(qubit)**sympy.Symbol('alpha'))
SVGCircuit(my_circuit)
```

以及可观测对象：

```
pauli_x = cirq.X(qubit)
pauli_x
```

所用算子为 $⟨Y(\alpha)| X | Y(\alpha)⟩ = \sin(\pi \alpha)$

```
def my_expectation(op, alpha):
    """Compute ⟨Y(alpha)| `op` | Y(alpha)⟩"""
    params = {'alpha': alpha}
    sim = cirq.Simulator()
    final_state_vector = sim.simulate(my_circuit, params).final_state_vector
    return op.expectation_from_state_vector(final_state_vector, {qubit: 0}).real

my_alpha = 0.3
print("Expectation=", my_expectation(pauli_x, my_alpha))
print("Sin Formula=", np.sin(np.pi * my_alpha))
```

如果定义 $f_{1}(\alpha) = ⟨Y(\alpha)| X | Y(\alpha)⟩$，则 $f_{1}^{'}(\alpha) = \pi \cos(\pi \alpha)$。请参见下例：

```
def my_grad(obs, alpha, eps=0.01):
    grad = 0
    f_x = my_expectation(obs, alpha)
    f_x_prime = my_expectation(obs, alpha + eps)
    return ((f_x_prime - f_x) / eps).real

print('Finite difference:', my_grad(pauli_x, my_alpha))
print('Cosine formula:   ', np.pi * np.cos(np.pi * my_alpha))
```

## 2. 对微分器的需求

对于大型电路，要始终具备可精确计算给定量子电路梯度的公式并不现实。如果简单的公式不足以计算梯度，则可以使用 `tfq.differentiators.Differentiator` 类来定义用于计算电路梯度的算法。例如，您可以使用以下方法在 TensorFlow Quantum (TFQ) 中重新创建以上示例：

```
expectation_calculation = tfq.layers.Expectation(
    differentiator=tfq.differentiators.ForwardDifference(grid_spacing=0.01))

expectation_calculation(my_circuit,
                        operators=pauli_x,
                        symbol_names=['alpha'],
                        symbol_values=[[my_alpha]])
```

但是，如果您改为基于采样（在真实设备上进行）估计期望值，则值可能会有所变化。这意味着您的估计方法并不完善：

```
sampled_expectation_calculation = tfq.layers.SampledExpectation(
    differentiator=tfq.differentiators.ForwardDifference(grid_spacing=0.01))

sampled_expectation_calculation(my_circuit,
                                operators=pauli_x,
                                repetitions=500,
                                symbol_names=['alpha'],
                                symbol_values=[[my_alpha]])
```

涉及到梯度时，这会迅速加剧造成严重的准确率问题：

```
# Make input_points = [batch_size, 1] array.
input_points = np.linspace(0, 5, 200)[:, np.newaxis].astype(np.float32)
exact_outputs = expectation_calculation(my_circuit,
                                        operators=pauli_x,
                                        symbol_names=['alpha'],
                                        symbol_values=input_points)
imperfect_outputs = sampled_expectation_calculation(my_circuit,
                                                    operators=pauli_x,
                                                    repetitions=500,
                                                    symbol_names=['alpha'],
                                                    symbol_values=input_points)
plt.title('Forward Pass Values')
plt.xlabel('$x$')
plt.ylabel('$f(x)$')
plt.plot(input_points, exact_outputs, label='Analytic')
plt.plot(input_points, imperfect_outputs, label='Sampled')
plt.legend()
```

```
# Gradients are a much different story.
values_tensor = tf.convert_to_tensor(input_points)

with tf.GradientTape() as g:
    g.watch(values_tensor)
    exact_outputs = expectation_calculation(my_circuit,
                                            operators=pauli_x,
                                            symbol_names=['alpha'],
                                            symbol_values=values_tensor)
analytic_finite_diff_gradients = g.gradient(exact_outputs, values_tensor)

with tf.GradientTape() as g:
    g.watch(values_tensor)
    imperfect_outputs = sampled_expectation_calculation(
        my_circuit,
        operators=pauli_x,
        repetitions=500,
        symbol_names=['alpha'],
        symbol_values=values_tensor)
sampled_finite_diff_gradients = g.gradient(imperfect_outputs, values_tensor)

plt.title('Gradient Values')
plt.xlabel('$x$')
plt.ylabel('$f^{\'}(x)$')
plt.plot(input_points, analytic_finite_diff_gradients, label='Analytic')
plt.plot(input_points, sampled_finite_diff_gradients, label='Sampled')
plt.legend()
```

在这里可以看到，尽管有限差分公式在解析示例中可以快速计算出梯度本身，但当涉及到基于采样的方法时，却产生了大量噪声。必须使用更细致的技术来确保可以计算出良好的梯度。接下来，您将了解一种速度缓慢而不太适用于解析期望梯度计算的技术，但该技术在基于实际样本的真实示例中却有着出色的表现：

```
# A smarter differentiation scheme.
gradient_safe_sampled_expectation = tfq.layers.SampledExpectation(
    differentiator=tfq.differentiators.ParameterShift())

with tf.GradientTape() as g:
    g.watch(values_tensor)
    imperfect_outputs = gradient_safe_sampled_expectation(
        my_circuit,
        operators=pauli_x,
        repetitions=500,
        symbol_names=['alpha'],
        symbol_values=values_tensor)

sampled_param_shift_gradients = g.gradient(imperfect_outputs, values_tensor)

plt.title('Gradient Values')
plt.xlabel('$x$')
plt.ylabel('$f^{\'}(x)$')
plt.plot(input_points, analytic_finite_diff_gradients, label='Analytic')
plt.plot(input_points, sampled_param_shift_gradients, label='Sampled')
plt.legend()
```

从上面可以看到，某些微分器最好用于特定的研究场景。通常，在更为“真实”的环境下测试或实现算法时，基于样本的较慢方法在面对设备噪声等问题时鲁棒性更佳，因此是理想的微分器。诸如有限差分之类的较快方法非常适合面向解析计算且需要更高吞吐量的场景，但尚未考虑算法在实际设备上是否可行。

## 3. 多个可观测对象

我们来引入一个额外的可观测对象，借此了解 TensorFlow Quantum 对单个电路的多个可观测对象的支持情况。

```
pauli_z = cirq.Z(qubit)
pauli_z
```

如果此可观测对象同样用于之前的电路，则 $f_{2}(\alpha) = ⟨Y(\alpha)| Z | Y(\alpha)⟩ = \cos(\pi \alpha)$ 且 $f_{2}^{'}(\alpha) = -\pi \sin(\pi \alpha)$。快速检查：

```
test_value = 0.

print('Finite difference:', my_grad(pauli_z, test_value))
print('Sin formula:      ', -np.pi * np.sin(np.pi * test_value))
```

结果匹配（足够接近）。

现在，如果定义 $g(\alpha) = f_{1}(\alpha) + f_{2}(\alpha)$，则 $g'(\alpha) = f_{1}^{'}(\alpha) + f^{'}_{2}(\alpha)$。在 TensorFlow Quantum 中为电路定义多个可观测对象，相当于向 $g$ 添加更多项。

这意味着，电路中特定符号的梯度等于该符号应用于该电路的每个可观测对象的相应梯度之和。这与 TensorFlow 梯度计算和反向传播（将所有可观测对象的梯度总和作为特定符号的梯度）相兼容。

```
sum_of_outputs = tfq.layers.Expectation(
    differentiator=tfq.differentiators.ForwardDifference(grid_spacing=0.01))

sum_of_outputs(my_circuit,
               operators=[pauli_x, pauli_z],
               symbol_names=['alpha'],
               symbol_values=[[test_value]])
```

在这里可以看到，第一个条目是相对于 Pauli X 的期望，第二个条目是相对于 Pauli Z 的期望。现在，梯度计算方法如下：

```
test_value_tensor = tf.convert_to_tensor([[test_value]])

with tf.GradientTape() as g:
    g.watch(test_value_tensor)
    outputs = sum_of_outputs(my_circuit,
                             operators=[pauli_x, pauli_z],
                             symbol_names=['alpha'],
                             symbol_values=test_value_tensor)

sum_of_gradients = g.gradient(outputs, test_value_tensor)

print(my_grad(pauli_x, test_value) + my_grad(pauli_z, test_value))
print(sum_of_gradients.numpy())
```

现在，您已验证每个可观测对象的梯度之和即为 $\alpha$ 的梯度。所有 TensorFlow Quantum 微分器均支持此行为，且此行为在与其余 TensorFlow 的兼容性方面起着至关重要的作用。

## 4. 高级用法

TensorFlow Quantum 子类 `tfq.differentiators.Differentiator` 中存在的所有微分器。要实现微分器，用户必须实现两个接口之一。标准是实现 `get_gradient_circuits` ，它告诉基类要测量哪些电路以获得梯度估计值。或者，也可以重载 `differentiate_analytic` 和`differentiate_sampled`；类 `tfq.differentiators.Adjoint` 就采用这种方式。

下面使用 TensorFlow Quantum 实现一个电路的梯度。您将使用一个参数转移的小示例。

回想上文定义的电路，$|\alpha⟩ = Y^{\alpha}|0⟩$。和之前一样，可以定义一个函数作为该电路对 $X$ 可观测对象的期望值，$f(\alpha) = ⟨\alpha|X|\alpha⟩$。对于该电路使用[参数转移规则](https://pennylane.ai/qml/glossary/parameter_shift.html)，您可以发现导数是 $$\frac{\partial}{\partial \alpha} f(\alpha) = \frac{\pi}{2} f\left(\alpha + \frac{1}{2}\right) - \frac{ \pi}{2} f\left(\alpha - \frac{1}{2}\right)$$。`get_gradient_circuits` 函数返回该导数的分量。

```
class MyDifferentiator(tfq.differentiators.Differentiator):
    """A Toy differentiator for <Y^alpha | X |Y^alpha>."""

    def __init__(self):
        pass

    def get_gradient_circuits(self, programs, symbol_names, symbol_values):
        """Return circuits to compute gradients for given forward pass circuits.

        Every gradient on a quantum computer can be computed via measurements
        of transformed quantum circuits.  Here, you implement a custom gradient
        for a specific circuit.  For a real differentiator, you will need to
        implement this function in a more general way.  See the differentiator
        implementations in the TFQ library for examples.
        """

        # The two terms in the derivative are the same circuit...
        batch_programs = tf.stack([programs, programs], axis=1)

        # ... with shifted parameter values.
        shift = tf.constant(1/2)
        forward = symbol_values + shift
        backward = symbol_values - shift
        batch_symbol_values = tf.stack([forward, backward], axis=1)

        # Weights are the coefficients of the terms in the derivative.
        num_program_copies = tf.shape(batch_programs)[0]
        batch_weights = tf.tile(tf.constant([[[np.pi/2, -np.pi/2]]]),
                                [num_program_copies, 1, 1])

        # The index map simply says which weights go with which circuits.
        batch_mapper = tf.tile(
            tf.constant([[[0, 1]]]), [num_program_copies, 1, 1])

        return (batch_programs, symbol_names, batch_symbol_values,
                batch_weights, batch_mapper)
```

`Differentiator` 基类使用从 `get_gradient_circuits` 返回的分量来计算导数，如上面的参数转移公式所示。现在，这个新的微分器可以与现有 `tfq.layer` 对象一起使用：

```
custom_dif = MyDifferentiator()
custom_grad_expectation = tfq.layers.Expectation(differentiator=custom_dif)

# Now let's get the gradients with finite diff.
with tf.GradientTape() as g:
    g.watch(values_tensor)
    exact_outputs = expectation_calculation(my_circuit,
                                            operators=[pauli_x],
                                            symbol_names=['alpha'],
                                            symbol_values=values_tensor)

analytic_finite_diff_gradients = g.gradient(exact_outputs, values_tensor)

# Now let's get the gradients with custom diff.
with tf.GradientTape() as g:
    g.watch(values_tensor)
    my_outputs = custom_grad_expectation(my_circuit,
                                         operators=[pauli_x],
                                         symbol_names=['alpha'],
                                         symbol_values=values_tensor)

my_gradients = g.gradient(my_outputs, values_tensor)

plt.subplot(1, 2, 1)
plt.title('Exact Gradient')
plt.plot(input_points, analytic_finite_diff_gradients.numpy())
plt.xlabel('x')
plt.ylabel('f(x)')
plt.subplot(1, 2, 2)
plt.title('My Gradient')
plt.plot(input_points, my_gradients.numpy())
plt.xlabel('x')
```

现在，可以使用这个新的微分器来生成可微运算。

要点：如果微分器之前已附加到一个运算，那么在附加到新的运算之前，必须先进行刷新，因为一个微分器一次只能附加到一个运算。

```
# Create a noisy sample based expectation op.
expectation_sampled = tfq.get_sampled_expectation_op(
    cirq.DensityMatrixSimulator(noise=cirq.depolarize(0.01)))

# Make it differentiable with your differentiator:
# Remember to refresh the differentiator before attaching the new op
custom_dif.refresh()
differentiable_op = custom_dif.generate_differentiable_op(
    sampled_op=expectation_sampled)

# Prep op inputs.
circuit_tensor = tfq.convert_to_tensor([my_circuit])
op_tensor = tfq.convert_to_tensor([[pauli_x]])
single_value = tf.convert_to_tensor([[my_alpha]])
num_samples_tensor = tf.convert_to_tensor([[5000]])

with tf.GradientTape() as g:
    g.watch(single_value)
    forward_output = differentiable_op(circuit_tensor, ['alpha'], single_value,
                                       op_tensor, num_samples_tensor)

my_gradients = g.gradient(forward_output, single_value)

print('---TFQ---')
print('Foward:  ', forward_output.numpy())
print('Gradient:', my_gradients.numpy())
print('---Original---')
print('Forward: ', my_expectation(pauli_x, my_alpha))
print('Gradient:', my_grad(pauli_x, my_alpha))
```

成功：现在，您可以使用 TensorFlow Quantum 提供的所有微分器，以及定义自己的微分器了。


## This Jupyter notebook is available at https://github.com/dkp-quantum/Tutorials

## Further Information

#### * Qiskit: https://qiskit.org

#### * Qiskit GitHub: https://github.com/Qiskit

## Exercise 2:
## Design a quantum circuit that creates a 3-qubit entangled state: $\alpha|000\rangle+\beta|111\rangle$,
## such that $|\alpha|^2 = 0.25$, and $|\beta|^2 = 0.75$.
## Check the answer with the QASM simulation and by plotting the histogram of the measurement statistics.

### Hint:
$R_y(\theta) = \begin{bmatrix} \cos(\theta/2) & -\sin(\theta/2)\\ \sin(\theta/2) & \cos(\theta/2)\end{bmatrix}$ can be implemented by the qiskit code: `qc.ry(theta,q)`

```python
%matplotlib inline
# Importing standard Qiskit libraries and configuring account
from qiskit import *
from qiskit.visualization import *
import numpy as np
```

```python
# Create a quantum register with 3 qubits
q3 = QuantumRegister(3,'q')
# Create a classical register with 3 qubits
c3 = ClassicalRegister(3,'c')

# Create the quantum circuit with the measurement in one go.
qc_ex2 = QuantumCircuit(q3,c3,name="ex1")

qc_ex2.ry(2*np.pi/3,0)
qc_ex2.cx(0,1)
qc_ex2.cx(1,2)
qc_ex2.barrier()
qc_ex2.measure(q3,c3)
qc_ex2.draw(output='mpl')# Now, add a Toffoli gate
qc_toff = QuantumCircuit(q3,c3,name="ex1")
```

```python
backend_q = Aer.get_backend('qasm_simulator')
```

```python
# Execute the circuit on the qasm simulator.
job_ex2 = execute(qc_ex2, backend_q, shots=4096)

# Grab the results from the job.
result_ex2 = job_ex2.result()
plot_histogram(result_ex2.get_counts(qc_ex2))
```

## 3-qubit gate example: Toffoli gate

```python
# Create a quantum register with 3 qubits
q3 = QuantumRegister(3,'q')
# Create a classical register with 3 qubits
c3 = ClassicalRegister(3,'c')

# Create the quantum circuit without a Toffoli gate
qc_toff = QuantumCircuit(q3,c3,name="ex1")

qc_toff.ry(2*np.pi/3,0)
qc_toff.h(1)
qc_toff.h(2)
qc_toff.barrier()
qc_toff.measure(q3,c3)
qc_toff.draw(output='mpl')
```

```python
# Execute the circuit on the qasm simulator.
job_toff = execute(qc_toff, backend_q, shots=4096)

# Grab the results from the job.
result_toff = job_toff.result()
plot_histogram(result_toff.get_counts(qc_toff))
```

```python
# Now, add a Toffoli gate
qc_toff = QuantumCircuit(q3,c3,name="ex1")

qc_toff.ry(2*np.pi/3,0)
qc_toff.h(1)
qc_toff.h(2)
qc_toff.ccx(1,2,0)
qc_toff.barrier()
qc_toff.measure(q3,c3)
qc_toff.draw(output='mpl')
```

```python
# Execute the circuit on the qasm simulator.
job_toff = execute(qc_toff, backend_q, shots=4096)

# Grab the results from the job.
result_toff = job_toff.result()
plot_histogram(result_toff.get_counts(qc_toff))
```

## Native single-qubit gates of IBM Q devices

One way to write a general form of a single qubit unitary:

$$
U(\theta,\phi,\lambda)=\begin{bmatrix}
 \cos(\theta/2) & -e^{i\lambda}\sin(\theta/2) \\
e^{i\phi}\sin(\theta/2) & e^{i(\lambda+\phi)}\cos(\theta/2)
\end{bmatrix}
$$
***
Native single qubit gates:
* `u3`=$U(\theta,\phi,\lambda)$
* `u2`=$U(\pi/2,\phi,\lambda)$
* `u1`=$U(0,0,\lambda)$

Native two qubit gate:
* controlled-NOT

### But why such form? $\rightarrow$ This is related to gate implementations on real devices

### Note that $$
U(\theta,\phi,\lambda)=\begin{bmatrix}
 \cos(\theta/2) & -e^{i\lambda}\sin(\theta/2) \\
e^{i\phi}\sin(\theta/2) & e^{i(\lambda+\phi)}\cos(\theta/2)
\end{bmatrix}
$$
### can be written as:
### \begin{align}
U(\theta,\phi,\lambda)&=R_z(\phi)R_y(\theta)R_z(\lambda)\\
&=R_z(\phi)R_x(-\pi/2)R_z(\theta)R_x(\pi/2)R_z(\lambda)
\end{align}

### In RF/MW based quantum control, $R_z$ is given for free, and $R_x(\pm\pi/2)$ can be calibrated with high precision.

## Running Quantum Circuits on IBM Q

### Need IBM Token for running an experiment on a IBM cloud quantum computer:  https://quantum-computing.ibm.com

```python
# IBMQ.disable_account()
provider = IBMQ.enable_account('TOKEN')
```

```python
provider = IBMQ.get_provider(hub='ibm-q-research')
```

```python
provider.backends()
```

    [<IBMQSimulator('ibmq_qasm_simulator') from IBMQ(hub='ibm-q-research', group='Daniel-Park', project='main')>,
     <IBMQBackend('ibmqx2') from IBMQ(hub='ibm-q-research', group='Daniel-Park', project='main')>,
     <IBMQBackend('ibmq_16_melbourne') from IBMQ(hub='ibm-q-research', group='Daniel-Park', project='main')>,
     <IBMQBackend('ibmq_vigo') from IBMQ(hub='ibm-q-research', group='Daniel-Park', project='main')>,
     <IBMQBackend('ibmq_ourense') from IBMQ(hub='ibm-q-research', group='Daniel-Park', project='main')>,
     <IBMQBackend('ibmq_valencia') from IBMQ(hub='ibm-q-research', group='Daniel-Park', project='main')>,
     <IBMQBackend('ibmq_london') from IBMQ(hub='ibm-q-research', group='Daniel-Park', project='main')>,
     <IBMQBackend('ibmq_burlington') from IBMQ(hub='ibm-q-research', group='Daniel-Park', project='main')>,
     <IBMQBackend('ibmq_essex') from IBMQ(hub='ibm-q-research', group='Daniel-Park', project='main')>,
     <IBMQBackend('ibmq_armonk') from IBMQ(hub='ibm-q-research', group='Daniel-Park', project='main')>,
     <IBMQBackend('ibmq_rome') from IBMQ(hub='ibm-q-research', group='Daniel-Park', project='main')>]

```python
from qiskit.tools.monitor import backend_overview, backend_monitor, job_monitor
from qiskit.tools.visualization import plot_gate_map, plot_error_map
```

```python
# Retrieve IBM Quantum device information
backend_overview()
```

    ibmq_rome                    ibmq_armonk                  ibmq_essex
    ---------                    -----------                  ----------
    Num. Qubits:  5              Num. Qubits:  1              Num. Qubits:  5
    Pending Jobs: 4              Pending Jobs: 4              Pending Jobs: 4
    Least busy:   False          Least busy:   False          Least busy:   False
    Operational:  True           Operational:  True           Operational:  True
    Avg. T1:      95.4           Avg. T1:      203.0          Avg. T1:      88.1
    Avg. T2:      98.2           Avg. T2:      213.1          Avg. T2:      108.9

    ibmq_burlington              ibmq_london                 ibmq_valencia
    ---------------              -----------                 -------------
    Num. Qubits:  5              Num. Qubits:  5             Num. Qubits:  5
    Pending Jobs: 8              Pending Jobs: 0             Pending Jobs: 4
    Least busy:   False          Least busy:   True          Least busy:   False
    Operational:  True           Operational:  True          Operational:  True
    Avg. T1:      68.4           Avg. T1:      65.3          Avg. T1:      98.7
    Avg. T2:      44.5           Avg. T2:      77.5          Avg. T2:      69.9

    ibmq_ourense                 ibmq_vigo                    ibmq_16_melbourne
    ------------                 ---------                    -----------------
    Num. Qubits:  5              Num. Qubits:  5              Num. Qubits:  15
    Pending Jobs: 44             Pending Jobs: 32             Pending Jobs: 17
    Least busy:   False          Least busy:   False          Least busy:   False
    Operational:  True           Operational:  True           Operational:  True
    Avg. T1:      111.0          Avg. T1:      80.8           Avg. T1:      51.4
    Avg. T2:      69.4           Avg. T2:      85.3           Avg. T2:      55.2

    ibmqx2
    ------
    Num. Qubits:  5
    Pending Jobs: 12
    Least busy:   False
    Operational:  True
    Avg. T1:      64.1
    Avg. T2:      42.4

```python
# Let's get two quantum devices as an example
backend_qx2 = provider.get_backend('ibmqx2')
backend_vigo = provider.get_backend('ibmq_vigo')
```

```python
backend_monitor(backend_qx2)
```

    ibmqx2
    ======
    Configuration
    -------------
        n_qubits: 5
        operational: True
        status_msg: active
        pending_jobs: 12
        backend_version: 2.1.0
        basis_gates: ['id', 'u1', 'u2', 'u3', 'cx']
        local: False
        simulator: False
        sample_name: sparrow
        open_pulse: False
        backend_name: ibmqx2
        url: None
        max_experiments: 75
        max_shots: 8192
        description: 5 qubit device
        coupling_map: [[0, 1], [0, 2], [1, 0], [1, 2], [2, 0], [2, 1], [2, 3], [2, 4], [3, 2], [3, 4], [4, 2], [4, 3]]
        allow_object_storage: True
        credits_required: True
        n_registers: 1
        allow_q_object: True
        memory: True
        conditional: False
        quantum_volume: 8
        meas_map: [[0, 1, 2, 3, 4]]
        online_date: 2017-01-24T05:00:00+00:00

    Qubits [Name / Freq / T1 / T2 / U1 err / U2 err / U3 err / Readout err]
    -----------------------------------------------------------------------
        Q0 / 5.2829 GHz / 76.2028 µs / 25.66558 µs / 0.0 / 0.0014 / 0.0028 / 0.0435
        Q1 / 5.24766 GHz / 54.83054 µs / 24.31219 µs / 0.0 / 0.00109 / 0.00217 / 0.022
        Q2 / 5.03384 GHz / 54.14951 µs / 82.47369 µs / 0.0 / 0.00053 / 0.00107 / 0.012
        Q3 / 5.29225 GHz / 56.5262 µs / 30.2464 µs / 0.0 / 0.0006 / 0.00119 / 0.0135
        Q4 / 5.07848 GHz / 79.0084 µs / 49.4769 µs / 0.0 / 0.00046 / 0.00092 / 0.0265

    Multi-Qubit Gates [Name / Type / Gate Error]
    --------------------------------------------
        cx0_1 / cx / 0.02365
        cx0_2 / cx / 0.01616
        cx1_0 / cx / 0.02365
        cx1_2 / cx / 0.02259
        cx2_0 / cx / 0.01616
        cx2_1 / cx / 0.02259
        cx2_3 / cx / 0.0151
        cx2_4 / cx / 0.01834
        cx3_2 / cx / 0.0151
        cx3_4 / cx / 0.01376
        cx4_2 / cx / 0.01834
        cx4_3 / cx / 0.01376

```python
plot_error_map(backend_qx2)
```

```python
backend_monitor(backend_vigo)
```

    ibmq_vigo
    =========
    Configuration
    -------------
        n_qubits: 5
        operational: True
        status_msg: active
        pending_jobs: 35
        backend_version: 1.2.0
        basis_gates: ['id', 'u1', 'u2', 'u3', 'cx']
        local: False
        simulator: False
        sample_name: Giraffe
        open_pulse: False
        backend_name: ibmq_vigo
        url: None
        max_experiments: 75
        max_shots: 8192
        description: 5 qubit device Vigo
        coupling_map: [[0, 1], [1, 0], [1, 2], [1, 3], [2, 1], [3, 1], [3, 4], [4, 3]]
        allow_object_storage: True
        credits_required: True
        n_registers: 1
        allow_q_object: True
        memory: True
        conditional: False
        quantum_volume: 16
        meas_map: [[0, 1, 2, 3, 4]]
        online_date: 2019-07-03T04:00:00+00:00

    Qubits [Name / Freq / T1 / T2 / U1 err / U2 err / U3 err / Readout err]
    -----------------------------------------------------------------------
        Q0 / 4.79646 GHz / 105.13289 µs / 13.28137 µs / 0.0 / 0.00035 / 0.0007 / 0.02
        Q1 / 4.94014 GHz / 89.6182 µs / 93.21168 µs / 0.0 / 0.0005 / 0.00101 / 0.025
        Q2 / 4.83351 GHz / 57.54627 µs / 174.64128 µs / 0.0 / 0.0004 / 0.00079 / 0.015
        Q3 / 4.80797 GHz / 85.99093 µs / 98.54726 µs / 0.0 / 0.00067 / 0.00133 / 0.012
        Q4 / 4.74967 GHz / 65.84481 µs / 46.96049 µs / 0.0 / 0.00061 / 0.00123 / 0.026

    Multi-Qubit Gates [Name / Type / Gate Error]
    --------------------------------------------
        cx0_1 / cx / 0.00984
        cx1_0 / cx / 0.00984
        cx1_2 / cx / 0.00874
        cx1_3 / cx / 0.01332
        cx2_1 / cx / 0.00874
        cx3_1 / cx / 0.01332
        cx3_4 / cx / 0.00707
        cx4_3 / cx / 0.00707

```python
plot_error_map(backend_vigo)
```

## Let's create a 5-qubit GHZ state, i.e. $ \frac{|00000\rangle + |11111\rangle}{\sqrt{2}}$.

```python
# Create a 5-qubit GHZ state (i.e. (|00000> + |11111>)/sqrt(2))
q5 = QuantumRegister(5,'q')
c5 = ClassicalRegister(5,'c')
ghz5= QuantumCircuit(q5,c5)

ghz5.h(0)
for i in range(1,5):
    ghz5.cx(0,i)

ghz5.barrier()
ghz5.measure(q5,c5)
ghz5.draw(output='mpl')
```

## Now, let's run it on a real IBMQ device.

```python
# Run the 5-qubit GHZ experiment on a 5-qubit device (try vigo)
job_exp1 = execute(ghz5, backend=backend_vigo, shots=4096)
job_monitor(job_exp1)
```

    Job Status: job has successfully run

```python
# Grab experimental results
result_vigo = job_exp1.result()
counts_vigo = result_vigo.get_counts(ghz5)
```

```python
# Let's also try the same experiment on the 15-qubit device.
job_exp2 = execute(ghz5, backend=provider.get_backend('ibmq_16_melbourne'), shots=4096)
job_monitor(job_exp2)
```

    Job Status: job has successfully run

```python
# Grab experimental results
result_mel = job_exp2.result()
counts_mel = result_mel.get_counts(ghz5)
```

```python
# Now, compare to theory by running it on qasm_simulator
job_qasm = execute(ghz5,backend=backend_q)
result_qasm = job_qasm.result()
counts_qasm = result_qasm.get_counts(ghz5)

# Plot both experimental and ideal results
plot_histogram([counts_qasm,counts_vigo,counts_mel],
               color=['black','green','blue'],
               legend=['QASM','Vigo','Melbourne'],figsize = [20,8])
```

## Elementary Quantum Protocols

* Superdense coding
* Quantum teleportation

```python
%matplotlib inline
# Importing standard Qiskit libraries and configuring account
from qiskit import *
from qiskit.visualization import *
import numpy as np
```

```python
# Define a function that takes a QuantumCircuit (qc)
# a qubit index (index) and a message string (msg)
def encoding(qc, index, msg):
    if msg == 0:
        pass    # To send 00 we do nothing
    elif msg == 1:
        qc.x(index) # To send 10 we apply an X-gate
    elif msg == 2:
        qc.z(index) # To send 01 we apply a Z-gate
    elif msg == 3:
        qc.z(index) # To send 11, we apply a Z-gate
        qc.x(index) # followed by an X-gate
    else:
        print("Invalid Message. Sending '00'.")
```

```python
def decoding(qc, a, b):
    qc.cx(a,b)
    qc.h(a)
```

```python
def qc_sdc(msg):

    # Create the quantum circuit with 2 qubits
    qc = QuantumCircuit(2)

    # First, an entangled pair is created between Alice and Bob
    # Bob has the first qubit, Alice has the second qubit.
    qc.h(0)
    qc.cx(0,1)

    qc.barrier()

    # Next, Bob encodes his message onto qubit 0.
    encoding(qc, 0, msg)
    qc.barrier()
    # Bob then sends his qubit to Alice.

    # After recieving qubit 0, Alice applies the recovery protocol:
    decoding(qc, 0, 1)

    # Finally, Alice measures her qubits to read Bob's message
    qc.measure_all()

    return qc
```

```python
backend_qasm = Aer.get_backend('qasm_simulator')

rand_msg = np.random.randint(4)
qc = qc_sdc(rand_msg)
job_sim = execute(qc, backend_qasm, shots=1024)
sim_result = job_sim.result()

measurement_result = sim_result.get_counts(qc)
print(measurement_result)
plot_histogram(measurement_result)
```

```python
print("The random message was: %s" % rand_msg)
qc.draw(output='mpl')
```

## Now, let's run it on a real IBMQ device.

```python
from qiskit.tools.monitor import backend_overview, backend_monitor, job_monitor
from qiskit.tools.visualization import plot_gate_map, plot_error_map
```

```python
# Retrieve IBM Quantum device information
backend_overview()
```

    ibmq_rome                    ibmq_armonk                  ibmq_essex
    ---------                    -----------                  ----------
    Num. Qubits:  5              Num. Qubits:  1              Num. Qubits:  5
    Pending Jobs: 6              Pending Jobs: 2              Pending Jobs: 70
    Least busy:   False          Least busy:   False          Least busy:   False
    Operational:  True           Operational:  True           Operational:  True
    Avg. T1:      77.2           Avg. T1:      155.4          Avg. T1:      117.6
    Avg. T2:      109.1          Avg. T2:      152.1          Avg. T2:      152.9

    ibmq_burlington              ibmq_london                  ibmq_valencia
    ---------------              -----------                  -------------
    Num. Qubits:  5              Num. Qubits:  5              Num. Qubits:  5
    Pending Jobs: 3              Pending Jobs: 11             Pending Jobs: 2
    Least busy:   False          Least busy:   False          Least busy:   False
    Operational:  True           Operational:  True           Operational:  True
    Avg. T1:      78.0           Avg. T1:      66.3           Avg. T1:      98.0
    Avg. T2:      76.1           Avg. T2:      81.0           Avg. T2:      65.1

    ibmq_ourense                 ibmq_vigo                    ibmq_16_melbourne
    ------------                 ---------                    -----------------
    Num. Qubits:  5              Num. Qubits:  5              Num. Qubits:  15
    Pending Jobs: 1              Pending Jobs: 1              Pending Jobs: 15
    Least busy:   True           Least busy:   False          Least busy:   False
    Operational:  True           Operational:  True           Operational:  True
    Avg. T1:      106.2          Avg. T1:      97.4           Avg. T1:      55.2
    Avg. T2:      64.7           Avg. T2:      79.7           Avg. T2:      59.5

    ibmqx2
    ------
    Num. Qubits:  5
    Pending Jobs: 8
    Least busy:   False
    Operational:  True
    Avg. T1:      61.4
    Avg. T2:      41.4

```python
# Run the superdense coding experiment on a 5-qubit device (try london)
job_sdc = execute(qc, backend=provider.get_backend('ibmq_london'), shots=4096)
job_monitor(job_sdc)
```

    Job Status: job has successfully run

```python
experiment_result = job_sdc.result().get_counts(qc)
print("The random message was: %s" % rand_msg)
plot_histogram(experiment_result)
```

```python
def create_bell_pair(qc, a, b):
    # Creates a bell pair in qc using qubits a & b
    qc.h(a) # Put qubit a into state |+>
    qc.cx(a,b) # CNOT with a as control and b as target
```

```python
qr = QuantumRegister(3)    # Protocol uses 3 qubits
cr1 = ClassicalRegister(1) # and 2 classical bits
cr2 = ClassicalRegister(1) # in 2 different registers
teleportation = QuantumCircuit(qr, cr1, cr2)
```

```python
## STEP 1
# Entangle qubits q1 and q2
create_bell_pair(teleportation, 1, 2)
teleportation.barrier()

# And view the circuit so far:
teleportation.draw(output='mpl')
```

```python
## STEP 2
# Bob performs his gates
teleportation.cx(0,1)
teleportation.h(0)
teleportation.barrier()

# And view the circuit so far:
teleportation.draw(output='mpl')
```

```python
## STEP 3
# Bob measures his part
teleportation.measure(0,0)
teleportation.measure(1,1)

# And view the circuit so far:
teleportation.draw(output='mpl')
```

```python
# This function takes a QuantumCircuit (qc), qubit index
# and ClassicalRegisters (cr1 & cr2) to decide which gates to apply
def alice_recover(qc, index, cr1, cr2):
    # Here we use c_if to control our gates with a classical
    # bit instead of a qubit
    qc.z(index).c_if(cr1, 1) # Apply gates if the registers
    qc.x(index).c_if(cr2, 1) # are in the state '1'
```

```python
## STEP 4
# Alice perform recovery
alice_recover(teleportation, 2, cr1, cr2)

# And view the circuit so far:
teleportation.draw(output='mpl')
```

## Let's test Quantum Teleportation with a random state!

```python
def random_init(qc,r1,r2,index):

    ## STEP 0
    # Bob prepares a quantum state to teleport
    # by applying a random rotation around y and z
    qc.ry(r1,index)
    qc.rz(r2,index)
    qc.barrier()
```

```python
def quantum_teleportation(qc):

    ## STEP 1
    # Entangle qubits q1 and q2
    create_bell_pair(qc, 1, 2)
    qc.barrier()

    ## STEP 2
    # Bob performs his gates
    qc.cx(0,1)
    qc.h(0)
    qc.barrier()

    ## STEP 3
    # Bob measures his part
    qc.measure(0,0)
    qc.measure(1,1)

    ## STEP 4
    # Alice perform recovery
    alice_recover(qc, 2, cr1, cr2)
```

```python
qr = QuantumRegister(3)    # Protocol uses 3 qubits
cr1 = ClassicalRegister(1) # and 2 classical bits
cr2 = ClassicalRegister(1) # in 2 different registers
qc_teleportation = QuantumCircuit(qr, cr1, cr2)
qc_ref = QuantumCircuit(qr)

r1 = np.random.random()*np.pi
r2 = np.random.random()*2*np.pi

random_init(qc_ref, r1, r2, 0)

random_init(qc_teleportation, r1, r2, 0)

quantum_teleportation(qc_teleportation)

qc_teleportation.draw(output='mpl')
```

```python
backend_sv = BasicAer.get_backend('statevector_simulator')

in_vector = execute(qc_ref, backend_sv).result().get_statevector()
out_vector = execute(qc_teleportation, backend_sv).result().get_statevector()
```

```python
plot_bloch_multivector(in_vector)
```

```python
plot_bloch_multivector(out_vector)
```

```python

```


# FFM232, Klassisk fysik och vektorfält - Föreläsningsanteckningar

 **[Christian Forssén](http://fy.chalmers.se/subatom/tsp/), Institutionen för fysik, Chalmers, Göteborg, Sverige**

Date: **Oct 3, 2016**

# 8. Potentialteori

## Konservativa fält och potentialer

Vi har definierat ett konservativt fält som ett fält $\vec{F}$ sådant att

$$
\begin{equation}
  \oint_C \vec{F}\cdot \mbox{d}\vec{r} = 0
\end{equation}
$$

för varje sluten kurva $C$.  Enligt Stokes sats följer det nu att ett fält som har $\nabla \times \vec{F} = 0$ överallt i ett enkelt sammanhängande område är konservativt. Vi har sedan antagit att vi kan skriva

$$
\begin{equation}
\vec{F} = - \nabla \phi,
\end{equation}
$$

där $\phi$ kallas för det konservativa fältets potential. Gäller då
$$
\nabla \times \vec{F} = 0 \, \Longleftrightarrow \, \vec{F} = - \nabla \phi \, ??
$$
Nej, det har vi ännu inte visat! Vi har att $\vec{F} = - \nabla \phi \, \Rightarrow \, \nabla \times \vec{F} = 0 $ eftersom $\nabla \times ( \nabla \phi) = 0$ (se nedan), men det omvända gäller inte per automatik. Vi har ännu inte visat att konservativa fält alltid kan skrivas som $-\nabla \phi$.

### Bevis av $\nabla \times ( \nabla \phi) = 0$ mha indexnotation

-----------------------

Vi kan visa att $\nabla \times ( \nabla \phi) = 0$ mha indexnotation: Vektorn $\left[ \nabla \times ( \nabla \phi) \right]_i = \epsilon_{ijk} \partial_j \partial_k \phi = 0$ eftersom den resulterande summan med nio termer $\sum_{j,k=1}^3$ har två nollskilda som tar ut varandra. T.ex. för $i=1$ fås $\partial_2\partial_3 \phi - \partial_3\partial_2 \phi = 0$.

----------------------------------------------

Definitionen av ett konservativt fält gör dock att vi kan definiera en potential från

$$
\begin{equation}
  \int_{\vec{r}_1}^{\vec{r}_2} \vec{F} \cdot \mbox{d} \vec{r} = \phi\left( \vec{r}_1 \right) - \phi\left( \vec{r}_2 \right).
\end{equation}
$$

Notera tecknet på potentialskillnaden i HL. Det hade gått lika bra med det omvända, men just denna konvention stämmer överens med energitolkningen i klassisk mekanik.

Vi ser nu att förändringen av potentialen mellan punkterna $\vec{r}$ och $\vec{r} + \mbox{d}\vec{r}$ är

$$
\begin{equation}
  \mbox{d}\phi = -\vec{F}\cdot \mbox{d}\vec{r},
\end{equation}
$$

men denna förändring kan också skrivas som

$$
\begin{equation}
  \mbox{d}\phi = \nabla \phi \cdot \mbox{d}\vec{r}.
\end{equation}
$$

Det följer därmed att det finns en potential $\phi$ så att det konservativa fältet $\vec{F}$ kan skrivas

$$
\begin{equation}
  \vec{F} = -\nabla \phi.
\end{equation}
$$

Faktum är att $\phi$ inte är entydigt bestämd.  Man kan skapa en ny potential genom att addera en konstant till en potential: $\phi \mapsto \phi + \phi_0$.

### Exempel: Arbete i klassisk mekanik

-----------------------

För ett konservativt kraftfält $\vec{F}$ gäller att det uträttade arbetet längs en rörelsebana motsvarar en *ökning* av den kinetiska energin åtföljt av en motsvarade *minskning* av en potentiell energi så att den totala mekaniska energin $E = T + V$ är *konserverad*.

[Comment 1: Notera att uträttat arbete kan vara negativt vilket isf innebär en minskning av den kinetiska energin och en ökning av den potentiella.]

Arbetet som uträttas längs en kurva $C$ motsvaras av kurvintegralen

$$
\begin{equation}
\int_C \vec{F} \cdot \mbox{d}\vec{r} = \left\{ \begin{array}{l}
\vec{F} = m \vec{a} = m \frac{\mbox{d}\vec{v}}{\mbox{d}t}
\end{equation}
$$

$$
\begin{equation}
\mbox{d}\vec{r} = \vec{v} \mbox{d}t
\end{array}
, \quad \frac{\mbox{d}\vec{v}}{\mbox{d}t} \cdot \vec{v} = \frac{1}{2}\frac{\mbox{d}}{\mbox{d}t} (\vec{v} \cdot \vec{v})
\right\}
\end{equation}
$$

$$
\begin{equation}
= \frac{m}{2} \int_{t_A}^{t_B} \frac{\mbox{d}}{\mbox{d}t} (\vec{v} \cdot \vec{v}) \mbox{d}t = \frac{m v_2^2}{2} - \frac{m v_1^2}{2} = \delta T.
\end{equation}
$$

Här har vi använt NII. Tidpunkterna $t_A$ och $t_B$ motsvarar start- respektive sluttiden för rörelsen.

Ett exempel på ett sådant konservativt kraftfält är gravitationskraften nära jordytan: $\vec{F} = - m g \hat{z}$.

Med vår nuvarande kunskap skulle vi uttrycka detta som $\vec{F} = -\nabla \phi$, där vi alltså använder beteckningen $\phi$ istället för $V$. För vårt exempel finner vi att potentialen $\phi(\vec{r}) = m g z$ uppfyller denna likhet.

Vidare uppfyller kraftfältet $\nabla \times \vec{F} = 0$. Vi kontrollerar detta för vårt exempel:
$$
\begin{vmatrix}
\hat{x} & \hat{y} & \hat{z} \\
\frac{\partial}{\partial x} & \frac{\partial}{\partial y} & \frac{\partial}{\partial z} \\
\mbox{0}  &  \mbox{0}  & -mg \\
\end{vmatrix}
= 0
$$

Kurvintegralen
$$
\int_C \vec{F} \cdot \mbox{d}\vec{r} = \phi\left( \vec{r}_A \right) - \phi\left( \vec{r}_B \right),
$$
där $A$ och $B$ är kurvans start- respektive slutpunkt. Här finner vi att potentialskillnaden $-\delta \phi = \phi(z_A) - \phi(z_B) = mg (z_A-z_B)$. Detta är alltså den negativa skillnaden i potentiell energi och vi får
$$
\delta T = - \delta \phi \quad \Rightarrow \quad \delta(T+V) = 0.
$$

--------------------------------------------

## Poissons och Laplaces ekvationer

Konservativa vektorfält är alltså rotationsfria, men de kan fortfarande ha nollskild divergens. Denna kallas ofta för *källtäthet*

$$
\begin{equation}
\rho(\vec{r}) = \nabla \cdot \vec{F},
\end{equation}
$$

och vi har stött på exempel där denna har tolkats i termer av elektrostatik och massflöde. Med $\vec{F} = -\nabla \phi$ får vi *Poissons ekvation*

$$
\begin{equation}
\nabla \cdot \nabla \phi(\vec{r}) = \Delta \phi(\vec{r}) = -\rho(\vec{r}),
\end{equation}
$$

Specialfallet av denna ekvation utan källa, dvs divergensfritt, ger *Laplaces ekvation*

$$
\begin{equation}
\Delta \phi(\vec{r}) = 0.
\end{equation}
$$

Kom ihåg att $\Delta$ är Laplacianen vilken kan skrivas

$$
\begin{equation}
\Delta \phi = \left( \frac{\partial^2}{\partial x^2} + \frac{\partial^2}{\partial y^2} + \frac{\partial^2}{\partial z^2} \right) \phi,
\end{equation}
$$

i cartesiska koordinater. Den ser annorlunda ut i kroklinjiga koordinatsystem.

[Comment 2: Notera att $\vec{F} = -m g \hat{z}$ är divergensfritt och att potentialen $\phi = m g z$ uppfyller Laplaces ekvation $\Delta \phi = 0$.]

[Rita 3: Skissa gärna hur ett fält som uppfyller Laplaces ekvation beter sig. Titta på ett fält $\phi(x,y)$ och använd Taylorexpansion för att teckna beteendet nära en punkt $\vec{r}_0$. Villkoret $\frac{\partial^2\phi}{\partial x^2} + \frac{\partial^2\phi}{\partial y^2} = 0$ ger en andragradskurva
$$
\phi(\vec{r}) =  c_0 + c_x (x-x_0) + c_y (y-y_0) + c_{xy} (x-x_0)(y-y_0)+ \frac{c_{ii}}{2}(x-x_0)^2 - \frac{c_{ii}}{2}(y-y_0)^2,
$$
där t.ex. $c_0 = \phi(\vec{r}_0)$, $c_x = d\phi/dx(\vec{r}_0)$, etc. Det viktiga är alltså att expansionen slutar med den kvadratiska termen och att dessa är lika, men motriktade, i $x$- och $y$-led.
]

Notera att Poissons och Laplaces ekvationer är exempel på *differentialekvationer*. För att lösa dessa i ett område behöver man också veta randvillkor för fältet $\phi$.

### Exempel: Lösningar till Laplaces ekvation

------------------------

Lösningen ($\phi = xy$) till Laplaces ekvation i två dimensioner på ett kvadratiskt område med randvillkor enligt figuren.

Lösningen ($\phi = x^2 - y^2$) till Laplaces ekvation i två dimensioner på ett cirkulärt område med vinkelberoende randvillkor enligt figuren.

Två exempel på lösningar till Laplaces ekvation i ett område med givna randvillkor visas i figurerna ref{fig:xy} och ref{fig:x2y2}.

------------------------------------------------

Vi kommer t.ex. se att (ett stationärt) temperaturfält uppfyller någon av dessa ekvationer (Laplaces ekvation om det inte finns en värmekälla). För att t.ex. räkna ut temperaturfältet inuti ett värmeisolerande fönster behöver vi veta randvillkor för fältet på glasets in- och utsida.

Olika tekniker för lösning av dessa ekvationer presenteras i kapitel 9.

## Divergensfria fält

Kan man erhålla *divergensfria* fält från någon potential på liknande sätt som vi just gjorde för rotationsfria fält?

Vi betraktar ett divergensfritt fält $\vec{G}$
$$
\nabla \cdot \vec{G} = 0,
$$
och nöjer oss med att konstatera att divergensfriheten uppenbarligen uppfylls om fältet kan skrivas

$$
\begin{equation}
\vec{G} = \nabla \times \vec{A}.
\end{equation}
$$

[Kommentar 4: Visa detta om ni är osäkra. Gärna med indexnotation.
$$
\nabla \cdot (\nabla \times \vec{A}) = \partial_i \epsilon_{ijk} \partial_j A_k,
$$
Vilket är en summa med 27 termer, vara sex är nollskilda och dessa tar ut varandra parvis.
]

Vektorfältet $\vec{A}$ kallas då för en *vektorpotential*.

### Standardexempel: Statiskt magnetfält

------------------------

Ett statiskt magnetfält $\vec{B}(\vec{r})$ är divergensfritt och uppfyller $\vec{B}(\vec{r}) = \nabla \times \vec{A}(\vec{r})$ där $\vec{A}$ kallas för den elektromagnetiska vektorpotentialen.

---------------------------------------------

Vi fann tidigare att skalära potentialer hade en invarians genom att vi kunde addera en konstant term $\phi \mapsto \phi + \phi_0$ utan att ändra fältstyrkan.

På samma sätt har vektorpotentialen en invarians

$$
\begin{equation}
\vec{A}(\vec{r}) \mapsto \vec{A}(\vec{r}) + \nabla \Lambda(\vec{r}),
\end{equation}
$$

där $\Lambda$ kallas för en Gaugeparameter och invariansen kallas för *Gaugeinvarians*.

[Kommentar 5: Detta må verka som en kuriositet, men just Gaugeinvarians är av fundamental betydelse för vår teoretiska förståelse av elektromagnetiska, svaga och starka krafter.]

[Kommentar 6: Ofta använder man Gaugeinvariansen till att skapa en vektorpotential som är divergensfri. Dvs man väljer $\Lambda$ så att $\Delta \Lambda = -\nabla \cdot \vec{A}$.]

Rotationen kallas ofta för virveltäthet
$$
\nabla \times \vec{G} = \vec{j} \quad \Rightarrow \nabla \times (\nabla \times \vec{A}) = \vec{j}.
$$
Vi använder sambandet $\nabla \times \left( \nabla \times \vec{A} \right) = \nabla \left( \nabla \cdot \vec{A} \right) - \Delta \vec{A}$ vilket alltså ger
$$
\Delta \vec{A} - \nabla \left( \nabla \cdot \vec{A} \right) = -\vec{j}.
$$
Genom att välja Gaugeparametern så att $\nabla \cdot \vec{A} = 0$ får vi Poissons ekvation för vektorpotentialen

$$
\begin{equation}
\Delta \vec{A} = -\vec{j}.
\end{equation}
$$

## Potentialer för godtyckliga vektorfält

* Betrakta ett fält med både källor och virvlar, dvs $\nabla \cdot \vec{H} = \rho \neq 0$ och $\nabla \times \vec{H} = \vec{j} \neq 0$.

* Detta allmänna vektorfält kan vi dela upp i två delar $\vec{H} = \vec{F} + \vec{G}$ där

$$
\begin{equation}
\nabla \cdot \vec{F} = \rho \quad  \nabla \cdot \vec{G} = 0
\end{equation}
$$

$$
\begin{equation}
\nabla \times \vec{F} = 0 \quad  \nabla \times \vec{G} = \vec{j}
\end{equation}
$$

* Dvs, fältet $\vec{H}$ kan skrivas som summan av ett rotationsfritt fält $\vec{F}$ och ett divergensfritt fält $\vec{G}$ som representeras av potentialerna $\phi$ och $\vec{A}$.

$$
\vec{H} = \vec{F} + \vec{G} = -\nabla \phi + \nabla \times \vec{A}.
$$

### Exempel: Stagnationsström, konservativt hastighetsfält

---------------

Betrakta (det rotationsfria) hastighetsfältet som ges av potentialen
$$
\phi(x,y) = \frac{A}{2}(x^2 - y^2),
$$
där $A$ är en positiv konstant. Hastighetsfältet blir
$$
\vec{v} = -\nabla \phi \quad \Rightarrow
\left\{
\begin{array}{l}
v_x = -A x \\
v_y = A y
\end{array}
\right.
$$
Vi noterar att detta fält också är divergensfritt.

En fältbild (potential och fältlinjer) visas nedan. Notera att det finns områden där hastigheten är noll. Dessa kallas stagnationspunkter. Vi noterar också att fältlinjerna blir parallella med $x$- och $y$-axlarna när vi kommer tillräckligt nära. Vi kan därför tänka oss dessa som fasta väggar och att vårt hastighetsfält beskriver strömmen vid ett hörn.

I verkligheten kommer dock friktionen nära väggen att skapa virvlar, och vår rotationsfria approximation ger en sämre beskrivning.

------------------------------------

## Standardexempel på käll- och virvelfördelningar

### Punktkälla med styrkan $q$ i origo

* vektorfält

$$
\vec F=\frac{q}{4\pi r^2}\hat r.
$$
* rotationsfritt, men med en källa $\rho=q\delta^3(\vec{r})$. Fältet har en potential

$$
\phi=\frac{q}{4\pi r},
$$
som uppfyller Poissons ekvation med källan $\rho=q\delta^3(\vec{r})$,
$$
\Delta\phi=-q\delta^3(\vec{r}).
$$

### Linjekälla på $z$-axeln med konstant styrka $k$

* vektorfält

$$
\vec F=\frac{k}{2\pi\varrho}\hat\varrho
$$
* Motsvarande potential är

$$
\phi=-\frac{k}{2\pi}\log\frac{\varrho}{\varrho_0},
$$
* Potentialen uppfyller Poissons ekvation:

$$
\Delta\phi=-k\delta^2(\vec{\varrho}).
$$

### Virveltråd på $z$-axeln med styrka $J$

* fältet

$$
\vec G=\frac{J}{2\pi\varrho}\hat\varphi.
$$
* Vektorpotentialen är (t.ex., med tanke på gaugeinvarians)

$$
\vec A=-\frac{J\hat z}{2\pi}\log\frac{\varrho}{\varrho_0},
$$
(kontrollera; $\nabla\times\vec A$ i cylindriska koordinater)
$$
\nabla\times\vec A=\frac{1}{\varrho}
	\begin{vmatrix}
        \hat\varrho & \varrho\hat\varphi & \hat z \\ \frac{\partial}{\partial \varrho} &
\frac{\partial}{\partial \varphi} &
        \frac{\partial}{\partial z} \\ 0\mbox{} & 0\mbox{} & -\frac{J}{2\pi}\log\varrho \\
        \end{vmatrix}
           =\frac{J}{2\pi\varrho}\hat\varphi.
$$
* Notera att denna vektorpotential uppfyller $\nabla\cdot\vec A=0$, och därför ges virvelfördelningen av

$$
\nabla \times \vec{G} = \vec{\jmath} = -\Delta\vec A.
$$
* Poissons ekvation,

$$
\Delta\vec A=-J\hat z\delta^2(\vec{\varrho}).
$$

## Randvärdesproblem

[Comment 7: Att ta reda på precis "hur mycket" villkor, och av vilket slag, man bör lägga på fältet på randen $\partial V$ är ju ett matematiskt problem, men det matematiska svaret på frågan bör också vara ett svar inom fysik, så att en given fysikalisk förutsättning ger en unik lösning (fältkonfiguration).]

* Vårt randvärdesproblem består av den partiella differentialekvationen $\Delta\phi=-\rho$ samt några randvillkor

* Vilka randvillkor ger en *unik* lösning? Eller hur skall "bra" randvillkor formuleras?

* Antag att $\phi_1$ och $\phi_2$ båda är lösningar.

* $\psi=\phi_1-\phi_2$, uppfyller Laplaces ekvation, $\Delta\psi=0$.

* En trivial lösning, $\psi=\mathrm{konstant}$, innebär att lösningen till Poissons ekvation är unik

[Comment 8: Eftersom $\phi_1$ och $\phi_2$ ger samma vektorfält om de enbart skiljer på en konstant.]

Betrakta nu identiteten
$$
\nabla\cdot(\psi\nabla\psi)=\nabla\psi\cdot\nabla\psi+\psi\Delta\psi
=\nabla\psi\cdot\nabla\psi=|\nabla\psi|^2,
$$
som gäller när $\Delta\psi=0$. Tillämpa nu Gauss sats på vektorfältet $\psi\nabla\psi$.
$$
\int_V|\nabla\psi|^2dV=\int_{\partial V}\psi\nabla\psi\cdot d\vec S.
$$
* VL är positivt semidefinit, och noll endast om $\psi = \mathrm{konstant}$.

* Randvillkor som gör HL till noll innebär därför det vi vill.

Ytintegralen i högerledet är
$$
\int_{\partial V}\psi(\nabla\psi\cdot\vec n)dS.
$$
Två faktorer i integranden: $\psi$ och $\nabla\psi\cdot\vec n$.

[Comment 9: Den andra faktorn är "normalderivatan" vid randen, alltså riktningsderivatan i normalens riktning.]

[Comment 10: Lösningen till Laplaces ekvation är trivial (konstant) om den ena eller den andra är noll på randen.]

### Dirichlets randvillkor:

$$
\psi = 0 \mathrm{~på~} \partial V \quad \Rightarrow \quad \psi = 0 \mathrm{~i~} V
$$
Detta ger att
$$
\phi|_{\partial V}=f,
$$
där $f$ är en funktion på randen

### Neumanns randvillkor:

$$
(\nabla\psi) \cdot \vec n=0 \mathrm{~på~} \partial V \quad \Rightarrow \quad \psi = \mathrm{konstant} \mathrm{~i~} V
$$
Detta ger att
$$
(\nabla\phi)|_{\partial V}\cdot\vec n=g,
$$
där $g$ är en funktion på randen.

### Sammanfattning:

Poissons ekvation i volymen $V$ med någon källfördelning $\rho$ har en unik lösning (sånär
som på en ointressant konstant) för dessa två typer av randvillkor.