🪐

Google Cirq + cuQuantum で遊んでみる (2) — グローバー探索アルゴリズム

2022/11/04に公開

目的

Google Cirq で遊んでみる (1) — グローバー探索アルゴリズム に引き続き、Google Cirq を使ってグローバー探索アルゴリズムを実行してみたい。のだが、今回はさらに cuQuantum を用いてみたい。

cuQuantum 対応 qsim のビルド

cuQuantum 対応の qsim を使いたい場合、自分で qsim をビルドしないとならない様子。
GPU-based quantum simulation on Google Cloud に従う。

想定環境

  • Ubuntu 18.04
  • Python 3.8
  • CUDA 11.2
  • NVIDIA T4

開発環境セットアップ

sudo apt install -y nvidia-cuda-toolkit
sudo apt install -y python3.8-dev
sudo apt install -y cmake && pip3 install pybind11

cuQuantum のインストール

wget https://developer.download.nvidia.com/compute/cuquantum/22.07.1/local_installers/cuquantum-local-repo-ubuntu1804-22.07.1_1.0-1_amd64.deb
sudo dpkg -i cuquantum-local-repo-ubuntu1804-22.07.1_1.0-1_amd64.deb
sudo cp /var/cuquantum-local-repo-ubuntu1804-22.07.1/cuquantum-*-keyring.gpg /usr/share/keyrings/
sudo apt-get update
sudo apt-get -y install cuquantum cuquantum-dev cuquantum-doc

qsim インストール

Installation and Compilation に従う。
おおよそ 8 分くらいで終わると思う。

git clone https://github.com/quantumlib/qsim.git
export PATH=/usr/local/cuda-11.2/bin${PATH:+:${PATH} };export CUQUANTUM_DIR="/opt/nvidia/cuquantum" && cd qsim &&  make clean all && pip install .

セットアップの確認

python -c "import qsimcirq; print(qsimcirq.qsim_gpu)"

<module 'qsimcirq.qsim_cuda' from '/usr/local/lib/python3.8/dist-packages/qsimcirq/qsim_cuda.cpython-38-x86_64-linux-gnu.so'>

こういう出力が得られれば正常にセットアップされたと考えて良さそう。

実験用回路の実装

Google Cirq で遊んでみる (1) — グローバー探索アルゴリズム と同様なので細かい説明は省く。

import numpy as np
import cirq
from cirq.contrib.svg import SVGCircuit
%matplotlib inline

def revserse_phase(circuit, qubits, state: str):
    n_qubits = len(qubits)
    qr = []
    for i, digit in enumerate(state):
        if digit == '0':
            qr.append(i)

    circuit.append([
        cirq.Moment(*[cirq.X(qubits[i]) for i in qr]),
        # MCZ start (HXH = Z)
        cirq.Z.controlled(n_qubits-1).on(
            *[qubits[i] for i in range(1, n_qubits)], qubits[0]),
        # MCZ end
        cirq.Moment(*[cirq.X(qubits[i]) for i in qr])
    ])

def define_oracle(n_qubits, solutions):
    # Create the oracle
    qubits = cirq.LineQubit.range(n_qubits)
    oracle = cirq.Circuit()

    for sol in solutions:
        revserse_phase(oracle, qubits, sol)

    return oracle

def define_diffuser(n_qubits):
    qubits = cirq.LineQubit.range(n_qubits)
    diffuser = cirq.Circuit()
    diffuser.append([
        cirq.Moment(*[cirq.H(qubits[i]) for i in range(n_qubits)]),
        cirq.Moment(*[cirq.X(qubits[i]) for i in range(n_qubits)]),
        # MCZ start (HXH = Z)
        cirq.Z.controlled(n_qubits-1).on(
            *[qubits[i] for i in range(1, n_qubits)], qubits[0]),
        # MCZ end
        cirq.Moment(*[cirq.X(qubits[i]) for i in range(n_qubits)]),
        cirq.Moment(*[cirq.H(qubits[i]) for i in range(n_qubits)])
    ])

    return diffuser

8 量子ビットに挑戦!

特に深い意味はないのだが、測定結果の可視化が潰れない程度の量子ビット数が 8 だった。

n_qubits = 8
solutions = ['10011011', '11101110']

oracle = define_oracle(n_qubits, solutions)
SVGCircuit(oracle)

diffuser = define_diffuser(n_qubits)
SVGCircuit(diffuser)

N = 2**n_qubits
angle = np.arcsin(np.sqrt(len(solutions) / N))
iterations = int((np.pi/2 - angle) / (2*angle) + 0.5)
print(f'{angle=}, {np.pi/2=}, {iterations=}')

angle=0.08850384314401546, np.pi/2=1.5707963267948966, iterations=8

qubits = cirq.LineQubit.range(n_qubits)
grover = cirq.Circuit()
# initialize |s>
grover.append([
    cirq.Moment(*[cirq.H(qubits[i]) for i in range(n_qubits)])
])
for _ in range(iterations):
    grover += oracle
    grover += diffuser
SVGCircuit(grover)

これは以下に見るように回路の深さが深いので途中で切れている。スクロールさせてまでキャプチャする意味も感じなかったので画面に表示されている部分だけ記念にキャプチャした。

print(len(grover))

89

さて、次は qsimcuQuantum を有効にする。Optional: Use the NVIDIA cuQuantum SDK によると

gpu_options = qsimcirq.QSimOptions(use_gpu=True, gpu_mode=1)

を使うことで cuQuantum が有効になるらしい。因みに、cuQuantum 有効版として qsim をビルドしていない場合には実行時例外が発生する。

import qsimcirq
import matplotlib.pyplot as plt

def binary_labels(num_qubits):
    return [bin(x)[2:].zfill(num_qubits) for x in range(2 ** num_qubits)]

qubits = cirq.LineQubit.range(n_qubits)
grover.append(cirq.measure(qubits[:]))

gpu_options = qsimcirq.QSimOptions(use_gpu=True, gpu_mode=1)
simulator = qsimcirq.QSimSimulator(qsim_options=gpu_options)
result = simulator.run(grover, repetitions=1000)

1 秒もかからずに計算は完了した。

# 確率として見たい場合には以下のようにすれば良い。
#result = cirq.get_state_histogram(result)
#result = result / np.sum(result)
_ = cirq.plot_state_histogram(
    result, plt.subplot(), title = 'Measurement results',
    xlabel = 'State', ylabel = 'Count',
    tick_label=binary_labels(n_qubits))
plt.xticks(rotation=70)
plt.show()

counts = cirq.get_state_histogram(result)
print(solutions)
print(counts[[int(sol, 2) for sol in solutions]])

['10011011', '11101110']
[524. 474.]

ということで期待する結果に。
念のために確率振幅も求めてみる。

gpu_options = qsimcirq.QSimOptions(use_gpu=True, gpu_mode=1)
simulator = qsimcirq.QSimSimulator(qsim_options=gpu_options)
results = simulator.compute_amplitudes(grover, bitstrings=[int(sol, 2) for sol in solutions])

print(f'qsim results: {results}')

qsim results: [(0.7055538296699524+0j), (0.705554187297821+0j)]

も一瞬で計算は終わって期待通り。

25 量子ビットに挑戦!

とりあえずやってみる。

n_qubits = 25
solutions = ['100110110000000000011111', '111011100000000000011111']

でやってみた。

N = 2**n_qubits
angle = np.arcsin(np.sqrt(len(solutions) / N))
iterations = int((np.pi/2 - angle) / (2*angle) + 0.5)
print(f'{angle=}, {np.pi/2=}, {iterations=}')

angle=0.0002441406274253193, np.pi/2=1.5707963267948966, iterations=3216

嫌な予感・・・。

print(len(grover))

35377

流石に・・・深い。

counts = cirq.get_state_histogram(result)
print(solutions)
print(counts[[int(sol, 2) for sol in solutions]])

['1001101100000000000011111', '1110111000000000000011111']
[483. 517.]

ということで期待する結果は得られていた。2 回計算してみたけど、1 回目は 5 分 16 秒で、2 回目は 5 分 10 秒かかった。

ただ、残念なことに、このやり方で cuQuantum が利用されているのかはよく分からない。

まとめ

一応 cuQuantum 対応の qsim がビルドできたように思う。そして、API からも有効化したシミュレータを作成できているようには思う・・・。が、本当に cuQuantum を有効に活用したパスを通っているのか?とか、どういう使い方をした時に cuQuantumcuStateVeccuTensorNet がどう使われているのかが分からないことに気付いた。もっと調査を続けないとうまく活用できそうにない。

GitHubで編集を提案

Discussion