GPT-3.5にQiskitを使ったコードを書かせてみる

遅ればせながらChatGPTを触ってみました。いま話題のGPTとは一体どんな感じだろう？といった単純な動機で、量子計算を題材に少し調べてみました。本記事は、ChatGPT歴わずか3日程度の内容です。

### 0. ChatGPTの利用

 ChatGPTの利用手順については、右のサイト([最近話題のChatGPTとは？始め方・使い方を紹介(追記あり)](https://fax-lnet.jp/toranomaki/chatgpt/) )を参考にしました。  

1. OpenAI公式サイトにアクセス  
https://openai.com/blog/chatgpt
にある「Try ChatGPT」「Sign up」を選択します。

2. アカウントの作成  
「Create your account」で、アカウントを作成します。Googleなどのアカウントと連携可能です。    
アカウント作成後、メールによる認証手続きが完了すれば、Sign up可能になります。

現状では、GPT-3.5が無料で利用できます。  
最新版は有償(月額20ドル)となります。APIも利用可能なようですが、こちらも[原則有償](https://openai.com/pricing)なようです。

ChatGPT3についての解説はQiita内にも沢山あります。より詳しい利用法、利用の際の注意事項などは以下が参考になるかも知れません。  
[1] [ChatGPT使い方総まとめ](https://qiita.com/sakasegawa/items/82069c97a1ee011c2d1e)  
[2] [知財・個人情報】ChatGPTをビジネスに利用する際の注意点 ['23/4/11]](https://qiita.com/ground0state/items/b284f7e598534b3c62ad)  
[3] [話題爆発中のAI「ChatGPT」の仕組みにせまる！](https://qiita.com/omiita/items/c355bc4c26eca2817324)

### 1. 先行例

本記事ではChatGPT(GPT-3.5)に[Qiskit](https://qiskit.org/) を使ったコーディングが可能か否かを、GPT歴わずか3日程度の筆者がいい加減に検証したものです。関連した内容に、2023/3/24付けのYouTubeのQiskitチャンネルに[Can ChatGPT Pass the Qiskit Developer Certification?](https://www.youtube.com/watch?v=HPc6zHzcZjk&ab_channel=Qiskit)というタイトルの動画があがっています。内容、コメントともに、興味深い内容になっていると思いますので、ご興味があればご視聴頂くと良いかも知れません。

### 2. ChatGPTにQiskitを使ったコードを書かせてみる
前置きは以上にして、早速ChatGPTを使ってみたいと思います。  
対話型AIということで、Qiskitを用いたコーディングについて色々聞いていきます。

#### 2-1. Bell状態
まずは、量子もつれを作るBell状態を作る基本的なコードについてGPT-3.5に伺ってみます。  
以下のように、GPT-3.5の「Send a message」に「qiskitを使ってベル状態を作るコードを書いて下さい。」と入力します。すると、すぐ下のような回答が得られました。

![gpt1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/576649/a6f7a2f2-1568-21fc-200a-1f983fff2e0a.jpeg)

日本語に対応している時点で驚きましたが、Qiskitを普通に認識しているのにも驚きます。さてコードを見てみます。  以下はGPT-3.5によって実際に書かれたコードです。

```python
import numpy as np
from qiskit import QuantumCircuit, Aer, transpile, assemble
from qiskit.visualization import plot_histogram
from qiskit.providers.aer import AerSimulator

# 2つの量子ビットを用意する回路を作成
bell_circuit = QuantumCircuit(2, 2)

# Hadamardゲートを1番目の量子ビットに適用
bell_circuit.h(0)

# CNOTゲートを1番目の量子ビットから2番目の量子ビットに適用
bell_circuit.cx(0, 1)

# 回路をシミュレーターで実行
simulator = AerSimulator()
compiled_circuit = transpile(bell_circuit, simulator)
job = simulator.run(compiled_circuit, shots=1024)
result = job.result()

# 測定結果を取得
counts = result.get_counts()

# 結果を表示
plot_histogram(counts)
```
コメント文付きで良さそうに見えます。しかし、よく見るとこのコードでは測定結果を取得するにもかかわらず、測定層が定義されていません。このまま走らせるとエラーになります。修正するには、例えばCNOTゲートを追加した後に
```python
bell_circuit.measure([0,1],[0,1])
```
とでも追記しておけば良さそうです。

#### 2-2. 水素分子の基底状態エネルギーの計算
次に、量子化学計算における典型的なコードを聞いてみます。「qiskitを使って水素分子の基底状態のエネルギーを計算するコードを書いて下さい。」と入力します。以下のような回答が得られました。

![gpt2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/576649/b94de90f-b8ed-2c07-227d-16c9c8ea190c.jpeg)

以下はGPT-3.5によって実際に書かれたコードになります。

```python
import numpy as np
from qiskit import Aer
from qiskit.chemistry import FermionicOperator
from qiskit.aqua.operators import Z2Symmetries
from qiskit.aqua.components.optimizers import L_BFGS_B
from qiskit.aqua import QuantumInstance
from qiskit.chemistry.components.variational_forms import UCCSD
from qiskit.chemistry.drivers import PySCFDriver, UnitsType

# 水素分子の基本情報
molecule = 'H .0 .0 .0; H .0 .0 0.74'
basis_set = 'sto3g'

# PySCFドライバーを使用して分子情報を取得
driver = PySCFDriver(atom=molecule, unit=UnitsType.ANGSTROM, basis=basis_set)
qmolecule = driver.run()

# フェルミオンオペレーターを生成
ferOp = FermionicOperator(h1=qmolecule.one_body_integrals, h2=qmolecule.two_body_integrals)
map_type = 'PARITY'
ferOp, energy_shift = ferOp.mapping(map_type)
num_particles = qmolecule.num_alpha + qmolecule.num_beta
num_spin_orbitals = 2 * qmolecule.num_molecular_orbitals

# Z2対称性を計算
z2_symmetries = Z2Symmetries(qubit_mapping=map_type, two_qubit_reduction=True)

# VQEに使用する波動関数形式を選択（UCCSDを使用）
num_variational_parameters = 2 * num_spin_orbitals - num_particles - 2
var_form = UCCSD(num_variational_parameters, num_qubits=num_spin_orbitals, initial_state=None,
                 qubit_mapping=map_type, two_qubit_reduction=True, num_time_slices=1)

# 量子インスタンスを設定
backend = Aer.get_backend('statevector_simulator')
quantum_instance = QuantumInstance(backend=backend)

# 最適化アルゴリズム（L-BFGS-B）を設定
optimizer = L_BFGS_B(maxfun=1000)

# VQEアルゴリズムを使用してエネルギーを計算
from qiskit.chemistry.algorithms.ground_state_solvers import GroundStateEigensolver
calc = GroundStateEigensolver(ferOp, var_form, optimizer, z2_symmetries)
result = calc.run(quantum_instance)

# 基底状態エネルギーを表示
energy = result['energy'] + energy_shift
print(f'基底状態エネルギー: {energy} Ha')
```
この場合も、こちらの意図した回答が得られています。しかしながら、Qiskitの最新のAPIに対応したコードになっておらず、私の環境の場合、このままでは動きませんでした。そこで、「qiskitの最新版qiskit natureを使って水素分子の基底状態のエネルギーを計算するコードを書いて下さい。」と再入力してみました。すると、以下の回答が得られました。

![gpt3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/576649/b6cffc4e-5054-6ac6-029e-faf7a5516ab1.jpeg)

GPT-3.5の回答によれば、Qiskit0.33.0を前提しているようです。そのため既に非推奨になっている古いライブラリを引用したコードが書かれているようです。但し、コード内のコメント文を読む限り、変分量子固有値解法の流れに沿っており、アルゴリズムの大筋は正しそうです。

#### 2-3. Qiskit Runtimeを使ったコード
量子コンピュータ実機にジョブを投入するコードが書けるかも聞いてみます。せっかくですのでQiskit Runtimeを使って書くように「qiskit runtimeを使って、IBMの量子コンピュータにベル状態を計算させるコードを書いて下さい。」と入力してみます。得られたコードは以下のようになりました。

```python
from qiskit import transpile, assemble, QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.providers.ibmq import IBMQ
from qiskit.providers.ibmq.runtime import UserMessenger, ProgramBackend
import numpy as np

# IBM Quantumアカウントにログイン
IBMQ.load_account()
provider = IBMQ.get_provider(hub='your_hub', group='your_group', project='your_project')  # あなたのプロジェクト情報に置き換えてください

# ベル状態を作成する回路を定義
def create_bell_state(circ):
    circ.h(0)
    circ.cx(0, 1)

# メインの計算ロジック
def main(backend: ProgramBackend, user_messenger: UserMessenger, shots: int = 1024):
    # 2量子ビットの回路を作成
    qr = QuantumRegister(2)
    cr = ClassicalRegister(2)
    circ = QuantumCircuit(qr, cr)
    
    # ベル状態を作成
    create_bell_state(circ)

    # 回路をトランスパイル
    transpiled_circuit = transpile(circ, backend)

    # シミュレーションまたは実行
    if backend.configuration().simulator:
        result = backend.run(transpiled_circuit, shots=shots).result()
    else:
        result = backend.run(transpiled_circuit).result()

    # 結果を返す
    counts = result.get_counts()
    user_messenger.publish(counts)

# ローカルシミュレーターで実行（オプション）
# from qiskit.providers.aer import AerSimulator
# local_backend = AerSimulator()
# main(local_backend, None)

# IBMの量子コンピュータで実行
backend = provider.get_backend('your_preferred_backend')  # 使用するバックエンドを選択
job = provider.runtime.run(program_id='your_program_id',  # あなたのプログラムIDに置き換えてください
                           inputs={'shots': 1024},
                           callback=main)
job.wait_for_final_state()  # ジョブの完了を待つ

```
先ほどの結果から予測されるように、やはり現在は非推奨のモジュールを用いたコードが返ってきました。大筋は正しそうですので、コメント文を参照しながら現行のAPIを調べれば修正ができそうです。

#### 2-4. ゲーム
ここまではチュートリアルにも記載されているような典型的な例でした。ここでは少し創造性を問うような、ひねったタスク（と言っても大してひねっていませんが）が可能か調べてみます。以下のように簡単なゲーム作成をお願いしてみます。
![gpt4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/576649/2d0a589b-1aa7-cf1d-b61e-7cbc94792050.jpeg)

以下のコードが得られました。

```python
from qiskit import QuantumCircuit, Aer, transpile, assemble
from qiskit.visualization import plot_histogram
import random

# ユーザーに質問をする
question = input("質問を入力してください（はいまたはいいえで答えてください）: ")

# 2量子ビットの回路を作成
qc = QuantumCircuit(2, 2)

# 量子ビットをエンタングルさせる
qc.h(0)
qc.cx(0, 1)

# 回路をシミュレーターで実行
simulator = Aer.get_backend('statevector_simulator')
compiled_circuit = transpile(qc, simulator)
job = simulator.run(compiled_circuit)
result = job.result()

# 量子ビットの測定結果を取得
counts = result.get_counts()

# はいまたはいいえの確率を計算
yes_probability = counts.get('00', 0) / 1024
no_probability = counts.get('11', 0) / 1024

# ランダムな占い結果を生成
if random.random() < yes_probability:
    fortune = "はい、運命がそう言っています。"
else:
    fortune = "いいえ、運命がそう言っていません。"

# 占い結果を表示
print("占い結果:")
print(f"質問: {question}")
print(f"占い師の答え: {fortune}")
```
「占い師」、「運命」といった単語を使っており、なんとなくゲームを連想させてくれます。
但し、このコードは正しい挙動を示しません。以下のような修正が必要になります。

```python
from qiskit import QuantumCircuit, Aer, transpile, assemble
from qiskit.visualization import plot_histogram
import random

# ユーザーに質問をする
question = input("質問を入力してください（はいまたはいいえで答えてください）: ")

# 2量子ビットの回路を作成
qc = QuantumCircuit(2, 2)

# 量子ビットをエンタングルさせる
qc.h(0)
qc.cx(0, 1)

#--- (修正1) ---
qc.measure([0,1],[0,1])
#--- (修正1) ---

# 回路をシミュレーターで実行
#--- (修正2) ---
simulator = Aer.get_backend('aer_simulator') # shots数はdefaultで1024
#--- (修正2) ---
compiled_circuit = transpile(qc, simulator)
job = simulator.run(compiled_circuit)
result = job.result()

# 量子ビットの測定結果を取得
counts = result.get_counts()

# はいまたはいいえの確率を計算
yes_probability = counts.get('00', 0) / 1024
no_probability = counts.get('11', 0) / 1024

# ランダムな占い結果を生成
if random.random() < yes_probability:
    fortune = "はい、運命がそう言っています。"
else:
    fortune = "いいえ、運命がそう言っていません。"

# 占い結果を表示
print("占い結果:")
print(f"質問: {question}")
print(f"占い師の答え: {fortune}")
```

#### 2-5.　ゲーム2

より漠然とお願いしてみます。以下のような回答が得られました。
![gpt5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/576649/c2a2d049-ec67-b823-8167-1d1852164e2f.jpeg)

以下が実際のコードです。

```python
from qiskit import QuantumCircuit, Aer, transpile, assemble
import random

# 2つの量子ビットの回路を作成
qr = QuantumCircuit(2, 2)

# 量子ビットにHadamardゲートを適用して均等な確率で0と1の状態にセットアップ
qr.h(0)
qr.h(1)

# 回路をシミュレーターで実行
simulator = Aer.get_backend('statevector_simulator')
compiled_circuit = transpile(qr, simulator)
job = simulator.run(compiled_circuit)
result = job.result()

# 量子ビットの測定結果を取得
counts = result.get_counts()

# ユーザーに予測を尋ねる
print("Quantum Coin Toss Game")
user_guess = input("コインの状態を予測してください（0または1）: ")

# ランダムにコインを投げて結果を表示
quantum_result = random.choice(['0', '1'])
print(f"量子コインの結果: {quantum_result}")

# ユーザーの予測と結果を比較して結果を表示
if user_guess == quantum_result:
    print("おめでとうございます！あなたの予測は正しいです。")
else:
    print("残念、あなたの予測は間違っています。")
```
GPT-3.5の回答の結びには、ふるったコメントがありました。
![gpt6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/576649/cad1fab0-0677-33f6-a3cb-d439ff0559d6.jpeg)

しかしこちらも、コードを良く見るとおかしな点があります。量子性を謳ってはいますが、肝心のquantum_resultにはpythonの乱数ライブラリが用いられています。ですので、例えば以下のような修正が必要だと思います。
```python
from qiskit import QuantumCircuit, Aer, transpile, assemble
import random

# 2つの量子ビットの回路を作成
qr = QuantumCircuit(2, 2)

# 量子ビットにHadamardゲートを適用して均等な確率で0と1の状態にセットアップ
qr.h(0)
qr.cnot(0,1)

#--- (修正1) ---
qr.measure([0,1],[0,1])
#--- (修正1) ---

# 回路をシミュレーターで実行
#--- (修正2) ---
simulator = Aer.get_backend('aer_simulator') 
#--- (修正2) ---
compiled_circuit = transpile(qr, simulator)
job = simulator.run(compiled_circuit)
result = job.result()

# 量子ビットの測定結果を取得
counts = result.get_counts()

# ユーザーに予測を尋ねる
print("Quantum Coin Toss Game")
user_guess = input("コインの状態を予測してください（0または1）: ")

#--- (修正3) ---
zero_probability = counts.get('00', 0) / 1024 
one_probability = counts.get('11', 0) / 1024   

if zero_probability > one_probability:
    quantum_result = '0'
else:
    quantum_result = '1'
#--- (修正3) ---

print(f"量子コインの結果: {quantum_result}")

# ユーザーの予測と結果を比較して結果を表示
if user_guess == quantum_result:
    print("おめでとうございます！あなたの予測は正しいです。")
else:
    print("残念、あなたの予測は間違っています。")
```
このように、こちらの無茶なお願いについてもそれなりの回答を返してくれますが、肝心のコードは色々間違っている事が分かります。

### 3. 簡単なまとめ
以上、簡単にまとめます。今回、話題になっているChatGPTを知る目的で、Qiskitのコードがどれくらい書けるのか簡単に調べたところ、
- 前提とするQiskitのversionが古い
現在非推奨になっているモジュールなどが使われたりするので、環境によってはそのまま使えない。

- コードは完璧ではない  
基本的なところでも色々間違っている。コードが間違っていても動く場合があり、チェックが必要。

- コメントの大筋は正しい  
個人的主観ですが、言葉遣いも丁寧なように感じます。

なことが分かりました。結論としては、GPT-3.5は古いversionのQiskitを前提としているため、GPT-3.5で書かれたコードを手放しで使うことは困難でした。但し、コメント文の大筋は正しいようですので、簡単な調べ物や、コメントから新たなアイデアを得るといった使い方はできるように思いました。筆者はGPT歴わずか3日(実質１時間位）ですが、GPTが直感的にすぐ使える点は良い(ログも残ったり)と思いました。より上手にGPTを使うことができれば、より有意義な使い方が見つかるかも知れません。今後、期待したいと思います。

(※)GPT-4ですと本稿の結果は改善されるのかもしれません。