Skip to content

Commit

Permalink
reordered operators + using SparsePauliOp
Browse files Browse the repository at this point in the history
  • Loading branch information
yiiyama committed Apr 25, 2023
1 parent 5679d69 commit e3f0d63
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 156 deletions.
32 changes: 16 additions & 16 deletions source/ja/dynamics_simulation.md
Expand Up @@ -315,15 +315,15 @@ $$

```{math}
:label: heisenberg
H = -J \sum_{j=0}^{n-2} (\sigma^X_j\sigma^X_{j+1} + \sigma^Y_j\sigma^Y_{j+1} + \sigma^Z_j \sigma^Z_{j+1})
H = -J \sum_{j=0}^{n-2} (\sigma^X_{j+1}\sigma^X_{j} + \sigma^Y_{j+1}\sigma^Y_{j} + \sigma^Z_{j+1} \sigma^Z_{j})
```

です。ここで、$\sigma^{[X,Y,Z]}_j$は第$j$スピンに作用するパウリ演算子です。

ただし、式{eq}`heisenberg`の和の記法には実は若干の省略があります。例えば第$j$項をより正確に書くと、

$$
I_0 \otimes \dots \otimes I_{j-1} \otimes \sigma^X_j \otimes \sigma^X_{j+1} \otimes I_{j+2} \otimes \dots I_{n-1}
I_{n-1} \otimes \dots \otimes I_{j+2} \otimes \sigma^X_{j+1} \otimes \sigma^X_{j} \otimes I_{j-1} \otimes \dots I_{0}
$$

です。ここで$\otimes$は線形演算子間の「テンソル積」を表しますが、聞き慣れない方は掛け算だと思っておいてください。重要なのは、式{eq}`heisenberg`の各項が、上で触れたように$n$個の基底演算子の積になっているということです。さらに、この系では隣接スピン間の相互作用しか存在しないため、ハミルトニアンが$n-1$個の項に分解できています。
Expand All @@ -335,27 +335,27 @@ $$
時間発展演算子は

$$
U_H(t) = \exp \left[ \frac{i\omega t}{2} \sum_{j=0}^{n-2} (\sigma^X_j\sigma^X_{j+1} + \sigma^Y_j\sigma^Y_{j+1} + \sigma^Z_j \sigma^Z_{j+1}) \right]
U_H(t) = \exp \left[ \frac{i\omega t}{2} \sum_{j=0}^{n-2} (\sigma^X_{j+1}\sigma^X_{j} + \sigma^Y_{j+1}\sigma^Y_{j} + \sigma^Z_{j+1} \sigma^Z_{j}) \right]
$$

ですが、ハミルトニアンの各項が互いに可換でないので、シミュレーションでは鈴木・トロッター分解を用いて近似します。各時間ステップ$\Delta t$での近似時間発展は

$$
\tilde{U}_{H;\Delta t} = \prod_{j=0}^{n-2} \exp\left( \frac{i \omega \Delta t}{2} \sigma^X_j\sigma^X_{j+1} \right) \exp\left( \frac{i \omega \Delta t}{2} \sigma^Y_j\sigma^Y_{j+1} \right) \exp\left( \frac{i \omega \Delta t}{2} \sigma^Z_j\sigma^Z_{j+1} \right)
\tilde{U}_{H;\Delta t} = \prod_{j=0}^{n-2} \exp\left( \frac{i \omega \Delta t}{2} \sigma^X_{j+1}\sigma^X_{j} \right) \exp\left( \frac{i \omega \Delta t}{2} \sigma^Y_{j+1}\sigma^Y_{j} \right) \exp\left( \frac{i \omega \Delta t}{2} \sigma^Z_{j+1}\sigma^Z_{j} \right)
$$

です。

### 量子ゲートでの表現

これを回転ゲートと制御ゲートで表します。まず$\exp(\frac{i \omega \Delta t}{2} \sigma^Z_j\sigma^Z_{j+1})$について考えてみましょう。この演算子の$j$-$(j+1)$スピン系の4つの基底状態への作用は
これを回転ゲートと制御ゲートで表します。まず$\exp(\frac{i \omega \Delta t}{2} \sigma^Z_{j+1}\sigma^Z_{j})$について考えてみましょう。この演算子の$j$-$(j+1)$スピン系の4つの基底状態への作用は

$$
\begin{align}
\upket_j \upket_{j+1} \rightarrow e^{i \omega \Delta t / 2} \upket_j \upket_{j+1} \\
\upket_j \downket_{j+1} \rightarrow e^{-i \omega \Delta t / 2} \upket_j \downket_{j+1} \\
\downket_j \upket_{j+1} \rightarrow e^{-i \omega \Delta t / 2} \downket_j \upket_{j+1} \\
\downket_j \downket_{j+1} \rightarrow e^{i \omega \Delta t / 2} \downket_j \downket_{j+1}
\upket_{j+1} \upket_{j} \rightarrow e^{i \omega \Delta t / 2} \upket_{j+1} \upket_{j} \\
\upket_{j+1} \downket_{j} \rightarrow e^{-i \omega \Delta t / 2} \upket_{j+1} \downket_{j} \\
\downket_{j+1} \upket_{j} \rightarrow e^{-i \omega \Delta t / 2} \downket_{j+1} \upket_{j} \\
\downket_{j+1} \downket_{j} \rightarrow e^{i \omega \Delta t / 2} \downket_{j+1} \downket_{j}
\end{align}
$$

Expand Down Expand Up @@ -386,16 +386,16 @@ $$
\end{align}
$$

と変換するので(確認してください)、まさに$\exp(\frac{i \omega \Delta t}{2} \sigma^Z_j\sigma^Z_{j+1})$の表現になっています。
と変換するので(確認してください)、まさに$\exp(\frac{i \omega \Delta t}{2} \sigma^Z_{j+1}\sigma^Z_{j})$の表現になっています。

残りの2つの演算子も同様にパリティに対する回転で表せますが、CNOTで表現できるのは$Z$方向のパリティだけなので、先にスピンを回転させる必要があります。$\exp(\frac{i \omega \Delta t}{2} \sigma^X_j\sigma^X_{j+1})$による変換は
残りの2つの演算子も同様にパリティに対する回転で表せますが、CNOTで表現できるのは$Z$方向のパリティだけなので、先にスピンを回転させる必要があります。$\exp(\frac{i \omega \Delta t}{2} \sigma^X_{j+1}\sigma^X_{j})$による変換は

$$
\begin{align}
\rightket_j \rightket_{j+1} \rightarrow e^{i \omega \Delta t / 2} \rightket_j \rightket_{j+1} \\
\rightket_j \leftket_{j+1} \rightarrow e^{-i \omega \Delta t / 2} \rightket_j \leftket_{j+1} \\
\leftket_j \rightket_{j+1} \rightarrow e^{-i \omega \Delta t / 2} \leftket_j \rightket_{j+1} \\
\leftket_j \leftket_{j+1} \rightarrow e^{i \omega \Delta t / 2} \leftket_j \leftket_{j+1}
\rightket_{j+1} \rightket_{j} \rightarrow e^{i \omega \Delta t / 2} \rightket_{j+1} \rightket_{j} \\
\rightket_{j+1} \leftket_{j} \rightarrow e^{-i \omega \Delta t / 2} \rightket_{j+1} \leftket_{j} \\
\leftket_{j+1} \rightket_{j} \rightarrow e^{-i \omega \Delta t / 2} \leftket_{j+1} \rightket_{j} \\
\leftket_{j+1} \leftket_{j} \rightarrow e^{i \omega \Delta t / 2} \leftket_{j+1} \leftket_{j}
\end{align}
$$

Expand All @@ -415,7 +415,7 @@ circuit.h(1)
circuit.draw('mpl')
```

最後に、$\exp(\frac{i \omega \Delta t}{2} \sigma^Y_j\sigma^Y_{j+1})$に対応する回路は
最後に、$\exp(\frac{i \omega \Delta t}{2} \sigma^Y_{j+1}\sigma^Y_{j})$に対応する回路は

```{code-cell} ipython3
:tags: [remove-input]
Expand Down
38 changes: 19 additions & 19 deletions source/ja/more_dynamics.md
Expand Up @@ -56,10 +56,10 @@ $\newcommand{\minusket}{\ket{-}}$
import numpy as np
import matplotlib.pyplot as plt
from qiskit import QuantumCircuit, transpile
from qiskit.quantum_info import SparsePauliOp
from qiskit_aer import AerSimulator
# このワークブック独自のモジュール
from qc_workbook.dynamics import plot_heisenberg_spins, bit_expectations_sv, bit_expectations_counts
from qc_workbook.hamiltonian import make_hamiltonian, diagonalized_evolution
from qc_workbook.dynamics import plot_heisenberg_spins, bit_expectations_sv, bit_expectations_counts, diagonalized_evolution
```

```{code-cell} ipython3
Expand Down Expand Up @@ -266,7 +266,7 @@ $$
と表現できることがわかります。一方、$\Phi^{\dagger}_j\Phi_{j+1}$に関しては、やや込み入った議論{cite}`PhysRevD.13.1043`の末、

$$
\Phi^{\dagger}_j\Phi_{j+1} \rightarrow i \sigma^+_j \sigma^-_{j+1}
\Phi^{\dagger}_j\Phi_{j+1} \rightarrow i \sigma^-_{j+1} \sigma^+_{j}
$$

が正しい表現であることがわかっています。ここで、
Expand All @@ -275,16 +275,16 @@ $$
\sigma^{\pm} = \frac{1}{2}(\sigma^X \pm i \sigma^Y)
$$

です。ハミルトニアンには$\Phi_j\Phi^{\dagger}_{j+1} \rightarrow i \sigma^-_j \sigma^+_{j+1}$も登場するので、二つの項を合わせると
です。また、このワークブックでの約束に従って、右辺の$j$と$j+1$の順番をひっくり返してあります。ハミルトニアンには$\Phi_j\Phi^{\dagger}_{j+1} \rightarrow i \sigma^+_{j+1}\sigma^-_{j}$も登場するので、二つの項を合わせると

$$
\Phi^{\dagger}_{j} \Phi_{j+1} + \Phi_j \Phi^{\dagger}_{j+1} \rightarrow \frac{i}{2} (\sigma^X_j \sigma^X_{j+1} + \sigma^Y_j \sigma^Y_{j+1})
\Phi^{\dagger}_{j} \Phi_{j+1} + \Phi_j \Phi^{\dagger}_{j+1} \rightarrow \frac{i}{2} (\sigma^X_{j+1}\sigma^X_j + \sigma^Y_{j+1}\sigma^Y_j)
$$

となります。まとめると、

$$
H \rightarrow \frac{1}{4a} \left\{ \sum_{j=0}^{n-2} (\sigma^X_j \sigma^X_{j+1} + \sigma^Y_j \sigma^Y_{j+1}) + J \sum_{j=1}^{n-2} (n - j - 1) \sum_{k=0}^{j-1} \sigma^Z_k \sigma^Z_j + \sum_{j=0}^{n-1} \left[ (-1)^{j+1} \mu - J \flhalf{n-j} \right] \sigma^Z_j \right\}
H \rightarrow \frac{1}{4a} \left\{ \sum_{j=0}^{n-2} (\sigma^X_{j+1}\sigma^X_j + \sigma^Y_{j+1}\sigma^Y_j) + J \sum_{j=1}^{n-2} (n - j - 1) \sum_{k=0}^{j-1} \sigma^Z_j \sigma^Z_k + \sum_{j=0}^{n-1} \left[ (-1)^{j+1} \mu - J \flhalf{n-j} \right] \sigma^Z_j \right\}
$$

です。ただし、計算過程で現れる定数項(恒等演算子に比例する項)は時間発展において系の状態に全体位相をかける作用しか持たないため、無視しました。
Expand All @@ -309,18 +309,18 @@ $$

**ヒント**:

上のハミルトニアンのパラメターの値は参考文献{cite}`Martinez_2016`と同一です。したがって、$n=4$, $\omega \Delta t = \pi/8$とすれば、論文中の図3aを再現できるはずです。答え合わせに使ってください。
上のハミルトニアンのパラメターの値は参考文献{cite}`Martinez_2016`と同一です(ただし$\sigma$積の順番は逆です)。したがって、$n=4$, $\omega \Delta t = \pi/8$とすれば、論文中の図3aを再現できるはずです。答え合わせに使ってください。

また、問題を解くためのヒントではありませんが、ハイゼンベルグモデルと同様にこのモデルでも対角化による厳密解を比較的簡単にプロットできるように道具立てがしてあります。下のコードのテンプレートでは、シミュレーション回路と厳密解を計算するためのハミルトニアンのパウリ行列分解だけ指定すれば、`plot_heisenberg_spins`と同様のプロットが作成されるようになっています。パウリ行列分解を指定するには、`paulis``coeffs`という二つのリストを作ります。これらのリストの長さはハミルトニアンの項数で、`paulis`の各要素は対応する項のパウリ行列のリスト、`coeffs`の各要素はその項にかかる係数にします。例えば
また、問題を解くためのヒントではありませんが、ハイゼンベルグモデルと同様にこのモデルでも対角化による厳密解を比較的簡単にプロットできるように道具立てがしてあります。下のコードのテンプレートでは、シミュレーション回路と厳密解を計算するためのハミルトニアンのパウリ行列分解だけ指定すれば、`plot_heisenberg_spins`と同様のプロットが作成されるようになっています。パウリ行列分解を指定するには、`paulis``coeffs`という二つのリストを作り、Qiskitの`SparsePauliOp`というクラスに渡します。これらのリストの長さはハミルトニアンの項数で、`paulis`の各要素は対応する項のパウリ行列のリスト、`coeffs`の各要素はその項にかかる係数にします。例えば

$$
H = 0.5 \sigma^X_0 \sigma^Y_1 I_2 + I_0 \sigma^Z_1 \sigma^X_2
H = 0.5 \sigma^X_2 \sigma^Y_1 I_0 + I_2 \sigma^Z_1 \sigma^X_0
$$

というハミルトニアンに対しては、

```{code-block} python
paulis = [['x', 'y', 'i'], ['i', 'z', 'x']]
paulis = ['XYI', 'IZX']
coeffs = [0.5, 1.]
```

Expand Down Expand Up @@ -378,25 +378,25 @@ simulator = AerSimulator()
circuits = transpile(circuits, backend=simulator)
sim_job = simulator.run(circuits, shots=shots)
sim_counts_list = sim_job.result().get_counts()
```

## Numerical solution through diagonalization
```{code-cell} ipython3
:tags: [raises-exception, remove-output]
# Construct the Hamiltonian
paulis = []
coeffs = []
## Numerical solution through diagonalization
##################
### EDIT BELOW ###
##################
paulis = [['i'] * n]
coeffs = None
paulis = ['I' * n]
coeffs = [1.]
##################
### EDIT ABOVE ###
##################
hamiltonian = make_hamiltonian(paulis, coeffs)
hamiltonian = SparsePauliOp(paulis, coeffs).to_matrix()
# Initial state as a statevector
initial_state = np.zeros(2 ** n, dtype=np.complex128)
Expand All @@ -417,11 +417,11 @@ plt.plot(time_points, number_density(bit_exp))
initial_probs = np.square(np.abs(initial_state))
fmt = f'{{:0{n}b}}'
initial_counts = dict((fmt.format(idx), prob) for idx, prob in enumerate(initial_probs) if prob != 0.)
sim_counts_list = [initial_counts] + sim_counts_list
sim_counts_list_with_init = [initial_counts] + sim_counts_list
# Plot the simulation results
time_points = np.linspace(0., omegadt * M, M + 1, endpoint=True)
_, bit_exp = bit_expectations_counts(time_points, sim_counts_list, n)
_, bit_exp = bit_expectations_counts(time_points, sim_counts_list_with_init, n)
plt.plot(time_points, number_density(bit_exp), 'o')
```
Expand Down
26 changes: 15 additions & 11 deletions source/ja/spectrum_estimation.md
Expand Up @@ -49,15 +49,15 @@ $\newcommand{\ket}[1]{|#1\rangle}$
前回登場したハイゼンベルグモデルのハミルトニアンは

$$
H = -J \sum_{j=0}^{n-2} (\sigma^X_j\sigma^X_{j+1} + \sigma^Y_j\sigma^Y_{j+1} + \sigma^Z_j \sigma^Z_{j+1}) \quad (J > 0)
H = -J \sum_{j=0}^{n-2} (\sigma^X_{j+1}\sigma^X_{j} + \sigma^Y_{j+1}\sigma^Y_{j} + \sigma^Z_{j+1} \sigma^Z_{j}) \quad (J > 0)
$$

というものでした。このハミルトニアンが表しているのは、空間中で一列に並んだスピンを持つ粒子が、隣接粒子間で相互作用を及ぼしているような系でした。ここで相互作用は、スピンの向きが揃っているときにエネルギーが低くなるようなものでした。したがって、全てののスピンが同じ方向を向いているときにエネルギーが最も低くなることが予想されました。

今回は、このハミルトニアンに外部からの磁場の影響を入れます。外部磁場がある時は、スピンが磁場の方向を向いているときにエネルギーが低くなります。したがって、外部磁場を$+Z$方向にかけるとすれば、ハミルトニアンは

$$
H = -J \sum_{j=0}^{n-1} (\sigma^X_j\sigma^X_{j+1} + \sigma^Y_j\sigma^Y_{j+1} + \sigma^Z_j \sigma^Z_{j+1} + g \sigma^Z_j)
H = -J \sum_{j=0}^{n-1} (\sigma^X_{j+1}\sigma^X_{j} + \sigma^Y_{j+1}\sigma^Y_{j} + \sigma^Z_{j+1} \sigma^Z_{j} + g \sigma^Z_j)
$$

となります。このハミルトニアンにはもう一点前回と異なる部分があります。前回はスピンに関する和を$j=0$から$n-2$まで取ることで、両端のスピンは「内側」のスピンとしか相互作用をしないような境界条件を採用していました。今回は和を$n-1$まで取っています。$\sigma^{X,Y,Z}_n$を$\sigma^{X,Y,Z}_0$と同一視することで、これは「周期境界条件」(一列ではなく環状に並んだスピン)を表します。
Expand All @@ -71,10 +71,10 @@ $$
import numpy as np
import matplotlib.pyplot as plt
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, transpile
from qiskit.quantum_info import SparsePauliOp
from qiskit.visualization import plot_histogram
from qiskit_aer import AerSimulator
# ワークブック独自のモジュール
from qc_workbook.hamiltonian import make_hamiltonian
from qc_workbook.show_state import show_state
print('notebook ready')
Expand All @@ -91,18 +91,22 @@ g = 0.
# Construct the Hamiltonian matrix
paulis = list()
coeffs = list()
xx_template = 'I' * (n_s - 2) + 'XX'
yy_template = 'I' * (n_s - 2) + 'YY'
zz_template = 'I' * (n_s - 2) + 'ZZ'
for j in range(n_s):
paulis.append(list('x' if k in (j, (j + 1) % n_s) else 'i' for k in range(n_s)))
coeffs.append(-J)
paulis.append(list('y' if k in (j, (j + 1) % n_s) else 'i' for k in range(n_s)))
coeffs.append(-J)
paulis.append(list('z' if k in (j, (j + 1) % n_s) else 'i' for k in range(n_s)))
coeffs.append(-J)
paulis.append(xx_template[j:] + xx_template[:j])
paulis.append(yy_template[j:] + yy_template[:j])
paulis.append(zz_template[j:] + zz_template[:j])
coeffs += [-J] * 3
if g != 0.:
paulis.append(list('z' if k == j else 'i' for k in range(n_s)))
paulis.append('I' * (n_s - j - 1) + 'Z' + 'I' * j)
coeffs.append(-J * g)
hamiltonian = make_hamiltonian(paulis, coeffs)
hamiltonian = SparsePauliOp(paulis, coeffs).to_matrix()
# Diagonalize and obtain the eigenvalues and vectors
eigvals, eigvectors = np.linalg.eigh(hamiltonian)
Expand Down
66 changes: 61 additions & 5 deletions source/qc_workbook/dynamics.py
Expand Up @@ -2,8 +2,8 @@
import matplotlib as mpl
import matplotlib.pyplot as plt
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp

from .hamiltonian import tensor_product, make_hamiltonian, diagonalized_evolution

def make_heisenberg_circuits(n_spins, M, omegadt):
circuits = []
Expand Down Expand Up @@ -71,6 +71,45 @@ def bit_expectations_sv(time_points, statevectors):
return x, y


def diagonalized_evolution(hamiltonian, initial_state, time, num_steps=100):
"""Diagonalize the given reduced Hamiltonian and evolve the initial state by exp(-i time*hamiltonian).
Args:
hamiltonian (np.ndarray(shape=(D, D), dtype=np.complex128)): Hamiltonian matrix divided by hbar.
initial_state (np.ndarray(shape=(D,), dtype=np.complex128)): Initial state vector.
time (float): Evolution time.
num_steps (int): Number of steps (T) to divide time into.
Returns:
np.ndarray(shape=(T,), dtype=float): Time points.
np.ndarray(shape=(D, T), dtype=np.complex128): State vector as a function of time.
"""

num_dim = hamiltonian.shape[0]
num_qubits = np.round(np.log2(num_dim)).astype(int)

# Create the array of time points
time_points = np.linspace(0., time, num_steps, endpoint=True)

## Diagonalize the Hamiltonian
eigvals, eigvectors = np.linalg.eigh(hamiltonian)

## Decompose the initial state vector into a linear combination of eigenvectors
# Matrix eigvectors has the form [v_0 v_1 v_2 ..], where v_i . v_j = delta_ij
# -> eigvectors^dagger @ initial_state = coefficients for the eigenvector decomposition of the initial state vector
initial_coeff = eigvectors.T.conjugate() @ initial_state
# Initial state as a matrix [c_0 v_0, c_1 v_1, ...] (shape (D, D))
initial_state_matrix = eigvectors * initial_coeff

## Time-evolve the initial state to each time point
# Phase at each time point (shape (D, T))
phase = np.outer(-1.j * eigvals, time_points)
phase_factor = np.exp(phase)
statevectors = initial_state_matrix @ phase_factor # shape (D, T)

return time_points, statevectors


def bit_expectations_counts(time_points, counts_list, num_bits):
"""Compute the bit expectation values from experiment results.
Expand Down Expand Up @@ -134,11 +173,11 @@ def plot_heisenberg_spins(counts_list, num_spins, initial_state, omegadt, add_th
# Construct the numerical Hamiltonian matrix from a list of Pauli operators
paulis = list()
for j in range(num_spins - 1):
paulis.append(list('x' if k in (j, j + 1) else 'i' for k in range(num_spins)))
paulis.append(list('y' if k in (j, j + 1) else 'i' for k in range(num_spins)))
paulis.append(list('z' if k in (j, j + 1) else 'i' for k in range(num_spins)))
paulis.append('I' * (num_spins - j - 2) + 'XX' + 'I' * j)
paulis.append('I' * (num_spins - j - 2) + 'YY' + 'I' * j)
paulis.append('I' * (num_spins - j - 2) + 'ZZ' + 'I' * j)

hamiltonian = make_hamiltonian(paulis)
hamiltonian = SparsePauliOp(paulis).to_matrix()

# Compute the statevector as a function of time from Hamiltonian diagonalization
time_points, statevectors = diagonalized_evolution(-0.5 * hamiltonian, initial_state, omegadt * num_steps)
Expand Down Expand Up @@ -198,3 +237,20 @@ def plot_heisenberg_spins(counts_list, num_spins, initial_state, omegadt, add_th

ax.set_xlabel(r'$\omega t$')
ax.set_ylabel(r'$\langle S_z \rangle$')


def tensor_product(ops):
"""Recursively apply np.kron to construct a tensor product of the operators.
Args:
ops (List): List of (2, 2) arrays.
Returns:
np.ndarray(shape=(2 ** nops, 2 ** nops), dtype=np.complex128): Tensor product of ops.
"""

prod = 1.
for op in ops:
prod = np.kron(op, prod)

return prod

0 comments on commit e3f0d63

Please sign in to comment.