特定のフォーマット済み回線を Azure Quantum に送信する方法
azure-quantum
Python パッケージを使用して、特定の形式の回線を Azure Quantum サービスに送信する方法について説明します。 この記事では、回線を次の形式で送信する方法について説明します。
詳細については、量子回路に関するページを参照してください。
前提条件
Azure portal のノートブックで回線を実行するには、次のものが必要です。
- アクティブなサブスクリプションが含まれる Azure アカウント。 Azure アカウントをお持ちでない場合は、無料で登録し、 従って支払うサブスクリプションにサインアップしてください。
- Azure Quantum ワークスペース。 詳細については、「Azure Quantum ワークスペースを作成する」を参照してください。
Visual Studio Code で回線を開発して実行するには、次も必要です。
Azure Quantum Development Kit、Python、および Jupyter 拡張機能がインストールされている VS Code。
Azure Quantum
qsharp
、azure-quantum
、ipykernel
パッケージ。python -m pip install --upgrade qsharp azure-quantum ipykernel
新しい Jupyter Notebook を作成する
ノートブックは VS Code で作成することも、Azure Quantum ポータルで直接作成することもできます。
- Azure portal にログインし、前の手順のワークスペースを選択します。
- 左側のブレードで、[ノートブック] を選択します。
- [マイ ノートブック] をクリックし、[新規追加] をクリックします。
- [カーネルの種類] で [IPython] を選択します。
- ファイルの名前を入力し、[ファイルの作成 ] をクリック。
新しい Notebook が開くと、サブスクリプションとワークスペースの情報に基づいて、最初のセルのコードが自動的に作成されます。
from azure.quantum import Workspace
workspace = Workspace (
resource_id = "", # Your resource_id
location = "" # Your workspace location (for example, "westus")
)
QIR 形式の回線を送信する
量子中間表現 (QIR) は、量子プログラミング言語/フレームワークとターゲットの量子計算プラットフォーム間の共通インターフェイスとして機能する中間表現です。 詳細については、「量子中間表現」を参照してください。
QIR 回線を作成します。 たとえば、次のコードは単純なエンタングルメント回路を作成します。
QIR_routine = """%Result = type opaque %Qubit = type opaque define void @ENTRYPOINT__main() #0 { call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*)) call void @__quantum__qis__cx__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Qubit* inttoptr (i64 1 to %Qubit*)) call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*)) call void @__quantum__qis__cz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Qubit* inttoptr (i64 0 to %Qubit*)) call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*)) call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 3 to %Qubit*)) call void @__quantum__qis__cz__body(%Qubit* inttoptr (i64 3 to %Qubit*), %Qubit* inttoptr (i64 1 to %Qubit*)) call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 3 to %Qubit*)) call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) #1 call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 3 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) #1 call void @__quantum__rt__tuple_record_output(i64 2, i8* null) call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) ret void } declare void @__quantum__qis__ccx__body(%Qubit*, %Qubit*, %Qubit*) declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*) declare void @__quantum__qis__cy__body(%Qubit*, %Qubit*) declare void @__quantum__qis__cz__body(%Qubit*, %Qubit*) declare void @__quantum__qis__rx__body(double, %Qubit*) declare void @__quantum__qis__rxx__body(double, %Qubit*, %Qubit*) declare void @__quantum__qis__ry__body(double, %Qubit*) declare void @__quantum__qis__ryy__body(double, %Qubit*, %Qubit*) declare void @__quantum__qis__rz__body(double, %Qubit*) declare void @__quantum__qis__rzz__body(double, %Qubit*, %Qubit*) declare void @__quantum__qis__h__body(%Qubit*) declare void @__quantum__qis__s__body(%Qubit*) declare void @__quantum__qis__s__adj(%Qubit*) declare void @__quantum__qis__t__body(%Qubit*) declare void @__quantum__qis__t__adj(%Qubit*) declare void @__quantum__qis__x__body(%Qubit*) declare void @__quantum__qis__y__body(%Qubit*) declare void @__quantum__qis__z__body(%Qubit*) declare void @__quantum__qis__swap__body(%Qubit*, %Qubit*) declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 declare void @__quantum__rt__result_record_output(%Result*, i8*) declare void @__quantum__rt__array_record_output(i64, i8*) declare void @__quantum__rt__tuple_record_output(i64, i8*) attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="4" "required_num_results"="2" } attributes #1 = { "irreversible" } ; module flags !llvm.module.flags = !{!0, !1, !2, !3} !0 = !{i32 1, !"qir_major_version", i32 1} !1 = !{i32 7, !"qir_minor_version", i32 0} !2 = !{i32 1, !"dynamic_qubit_management", i1 false} !3 = !{i32 1, !"dynamic_result_management", i1 false} """
QIR 回線をtargetに送信する
submit_qir_job
ヘルパー関数を作成します。 入力データ形式と出力データ形式は、それぞれqir.v1
およびmicrosoft.quantum-results.v1
として指定されることに注意してください。# Submit the job with proper input and output data formats def submit_qir_job(target, input, name, count=100): job = target.submit( input_data=input, input_data_format="qir.v1", output_data_format="microsoft.quantum-results.v1", name=name, input_params = { "entryPoint": "ENTRYPOINT__main", "arguments": [], "count": count } ) print(f"Queued job: {job.id}") job.wait_until_completed() print(f"Job completed with state: {job.details.status}") #if job.details.status == "Succeeded": result = job.get_results() return result
targetを選択し、QIR 回線を Azure Quantum に送信します。 たとえば、QIR 回線を IonQ シミュレーター targetに送信するには、次のようにします。
target = workspace.get_targets(name="ionq.simulator") result = submit_qir_job(target, QIR_routine, "QIR routine") result
{'Histogram': ['(0, 0)', 0.5, '(1, 1)', 0.5]}
プロバイダー固有の形式の回線を Azure Quantum に送信する
Q# や Qiskit などの QIR 言語に加えて、プロバイダー固有の形式で量子回路を Azure Quantum に送信できます。 各プロバイダーには、量子回路を表す独自の形式があります。
JSON 形式を使用して IonQ に回線を送信する
IonQ API ドキュメントで説明されているように、IonQ targetsでサポートされている言語に依存しない JSON 形式を使用して量子回線を作成。 たとえば、次のサンプルでは、3 つの量子ビットの間に重ね合わせが作成されます。
circuit = { "qubits": 3, "circuit": [ { "gate": "h", "target": 0 }, { "gate": "cnot", "control": 0, "target": 1 }, { "gate": "cnot", "control": 0, "target": 2 }, ] }
回線を IonQ targetに送信します。 次の例では、
Job
オブジェクトを返す IonQ シミュレーターを使用します。target = workspace.get_targets(name="ionq.simulator") job = target.submit(circuit)
ジョブが完了するまで待ってから、結果を取得します。
results = job.get_results() print(results)
..... {'duration': 8240356, 'histogram': {'0': 0.5, '7': 0.5}}
その後、Matplotlib を使用して結果を視覚化できます。
import pylab as pl pl.rcParams["font.size"] = 16 hist = {format(n, "03b"): 0 for n in range(8)} hist.update({format(int(k), "03b"): v for k, v in results["histogram"].items()}) pl.bar(hist.keys(), hist.values()) pl.ylabel("Probabilities")
QPU でジョブを実行する前に、実行するコストを見積もる必要があります。
Note
最新の価格の詳細については、IonQ の価格に関するページを参照するか、またはワークスペースを見つけ、そのワークスペースの [プロバイダー] タブで aka.ms/aq/myworkspaces を使用して価格オプションを表示してください。
Pulser SDK を使用して PASQAL に回線を送信する
PASQAL に回線を送信するには、Pulser SDK を使用してパルス シーケンスを作成し、PASQAL targetに送信します。
Pulser SDK をインストールする
Pulser は、中性原子量子デバイスのパルス シーケンスを作成、シミュレート、実行するためのフレームワークです。 これは、量子プロセッサに量子実験を送信するためのパススルーとして PASQAL によって設計されています。 詳細については、 Pulser のドキュメントを参照してください。
pulse シーケンスを送信するには、まず Pulser SDK パッケージをインストールします。
try:
import pulser
except ImportError:
!pip -q install pulser
量子レジスタを作成する
続行する前に、レジスタとレイアウトの両方を定義する必要があります。 レジスタは原子を配置する場所を指定し、レイアウトはレジスタ内でこれらの原子をキャプチャして構造化するために必要なトラップの位置を指定します。
レイアウトの詳細については、 Pulser のドキュメントを参照してください。
最初に、PASQAL 量子コンピューター target Fresnelをインポートする 'devices' オブジェクトを作成します。
from pulser_pasqal import PasqalCloud devices = PasqalCloud().fetch_available_devices() QPU = devices["FRESNEL"]
事前に調整されたレイアウト
デバイスは、事前に調整されたレイアウトの一覧を定義します。 これらのレイアウトの 1 つからレジスタをビルドできます。
QPU のパフォーマンスが向上するため、これは推奨されるオプションです。
オプション 1: 事前に調整されたレイアウトを使用してレジスタを定義する
Fresnel で使用できるレイアウトを調べて、このレイアウトからレジスタを定義します。 その方法の詳細については、pulser のドキュメントを参照してください。
例:
# let's say we are interested in the first layout available on the device layout = QPU.pre_calibrated_layouts[0] # Select traps 1, 3 and 5 of the layout to define the register traps = [1,3,5] reg = layout.define_register(*traps) # You can draw the resulting register to verify it matches your expectations reg.draw()
任意のレイアウト
事前に調整されたレイアウトが実験の要件を満たしていない場合は、カスタム レイアウトを作成できます。
任意のレジスタに対して、ニュートラルアトム QPU はレイアウトに従ってトラップを配置し、調整を行う必要があります。 各校正には時間が必要なため、通常は可能な限り既存の校正済みレイアウトを再利用することをお勧めします。
オプション 2: 定義されたレジスタからレイアウトを自動的に派生させる
このオプションを使用すると、指定したレジスタに基づいてレイアウトを自動生成できます。 ただし、大規模なレジスタの場合、レイアウトの作成に使用されるアルゴリズムの制限により、このプロセスによって最適なソリューションが得られる可能性があります。
qubits = { "q0": (0, 0), "q1": (0, 10), "q2": (8, 2), "q3": (1, 15), "q4": (-10, -3), "q5": (-8, 5), } reg = Register(qubits).with_automatic_layout(device)
オプション 3: 手動で定義されたレイアウトを使用してレジスタを定義する
- 2D 平面にランダムに配置された 20 個のトラップを使用して任意のレイアウトを作成する
import numpy as np # Generating random coordinates np.random.seed(301122) # Keeps results consistent between runs traps = np.random.randint(0, 30, size=(20, 2)) traps = traps - np.mean(traps, axis=0) # Creating the layout layout = RegisterLayout(traps, slug="random_20")
- 特定のトラップ ID を使用してレジスタを定義する
trap_ids = [4, 8, 19, 0] reg = layout.define_register(*trap_ids, qubit_ids=["a", "b", "c", "d"]) reg.draw()
パルス シーケンスを書き込む
中性原子はレーザーパルスで制御されます。 Pulser SDK を使用すると、量子レジスタに適用するパルス シーケンスを作成できます。
まず、原子の制御に使用するチャネルを宣言することで、パルス シーケンス属性を定義します。
Sequence
を作成するには、シーケンスが実行されるデバイスと共にRegister
インスタンスを指定する必要があります。 たとえば、次のコードは、1 つのチャネルを宣言します:ch0
。Note
QPU = devices["FRESNEL"]
デバイスを使用するか、Pulser から仮想デバイスをインポートして柔軟性を高めることができます。VirtualDevice
を使用すると、デバイスの仕様によって制約が少ないシーケンス作成が可能になり、エミュレーターでの実行に適しています。 詳細については、 Pulser のドキュメントを参照してください。from pulser import Sequence seq = Sequence(reg, QPU) # print the available channels for your sequence print(seq.available_channels) # Declare a channel. In this example we will be using `rydberg_global` seq.declare_channel("ch0", "rydberg_global")
シーケンスにパルスを追加します。 これを行うには、宣言したチャネルにパルスを作成して追加します。 たとえば、次のコードはパルスを作成し、チャネル
ch0
に追加します。from pulser import Pulse from pulser.waveforms import RampWaveform, BlackmanWaveform import numpy as np amp_wf = BlackmanWaveform(1000, np.pi) det_wf = RampWaveform(1000, -5, 5) pulse = Pulse(amp_wf, det_wf, 0) seq.add(pulse, "ch0") seq.draw()
次の図は、パルス シーケンスを示しています。
シーケンスを JSON 文字列に変換する
パルス シーケンスを送信するには、Pulser オブジェクトを入力データとして使用できる JSON 文字列に変換する必要があります。
import json
# Convert the sequence to a JSON string
def prepare_input_data(seq):
input_data = {}
input_data["sequence_builder"] = json.loads(seq.to_abstract_repr())
to_send = json.dumps(input_data)
return to_send
パルス シーケンスを PASQAL に送信する target
まず、適切な入力データ形式と出力データ形式を設定する必要があります。 たとえば、次のコードは、入力データ形式を
pasqal.pulser.v1
に設定し、出力データ形式をpasqal.pulser-results.v1
に設定します。# Submit the job with proper input and output data formats def submit_job(target, seq, shots): job = target.submit( input_data=prepare_input_data(seq), # Take the JSON string previously defined as input data input_data_format="pasqal.pulser.v1", output_data_format="pasqal.pulser-results.v1", name="PASQAL sequence", shots=shots # Number of shots ) print(f"Queued job: {job.id}") return job
Note
QPU でジョブを実行するために必要な時間は、現在のキュー時刻によって異なります。 ワークスペースの Providers ブレードを選択すると、targetの平均キュー時間を表示できます。
プログラムを PASQAL に送信します。 実際の量子ハードウェアにコードを送信する前に、エミュレーター
pasqal.sim.emu-tn
を targetとして使用してコードをテストできます。target = workspace.get_targets(name="pasqal.sim.emu-tn") # Change to "pasqal.qpu.fresnel" to use Fresnel QPU job = submit_job(target, seq, 10) job.wait_until_completed() print(f"Job completed with state: {job.details.status}") result = job.get_results() print(result)
{ "1000000": 3, "0010000": 1, "0010101": 1 }
OpenQASM を使用して Quantinuum に回線を送信する
OpenQASM 表記で量子回路を作成します。 たとえば、次の例では、テレポーテーション回路が作成されます。
circuit = """OPENQASM 2.0; include "qelib1.inc"; qreg q[3]; creg c0[3]; h q[0]; cx q[0], q[1]; cx q[1], q[2]; measure q[0] -> c0[0]; measure q[1] -> c0[1]; measure q[2] -> c0[2]; """
必要に応じて、回路をファイルから読み込むことができます。
with open("my_teleport.qasm", "r") as f: circuit = f.read()
Quantinuum targetに回線を送信します。 次の例では、
Job
オブジェクトを返す Quantinuum API 検証コントロールを使用します。target = workspace.get_targets(name="quantinuum.sim.h1-1sc") job = target.submit(circuit, shots=500)
ジョブが完了するまで待ってから、結果を取得します。
results = job.get_results() print(results)
........ {'c0': ['000', '000', '000', '000', '000', '000', '000', ... ]}
その後、Matplotlib を使用して結果を視覚化できます。
import pylab as pl pl.hist(results["c0"]) pl.ylabel("Counts") pl.xlabel("Bitstring")
このヒストグラムを見ると、乱数ジェネレーターから毎回 0 が返されている (乱数とは言えない) ことに気付くかもしれません。 これは、この API 検証コントロールが Quantinuum ハードウェアでコードが確実に正常に実行されるようにする一方で、どの量子測定に対しても 0 を返すためです。 真の乱数ジェネレーターにするには、量子ハードウェアで回路を実行する必要があります。
QPU でジョブを実行する前に、実行するコストを見積もる必要があります。
Note
最新の価格の詳細については、「 Azure Quantum の価格」を参照するか、ワークスペースの [プロバイダー] タブでワークスペースを検索し、価格オプションを表示します。 aka.ms/aq/myworkspaces。
Quil を使用してリゲッティに回線を送信する
Quil ジョブを送信する最も簡単な方法は、pyQuil ライブラリのツールとドキュメントを使用できるため、pyquil-for-azure-quantum パッケージを使用することです。 このパッケージがないと、pyQuil を使用して construct Quil プログラムを作成できますが、Azure Quantum に送信することはできません。
また、Quil プログラムを手動で構築し、azure-quantum
パッケージを直接使用して送信することもできます。
まず、必要なインポートを読み込みます。
from pyquil.gates import CNOT, MEASURE, H from pyquil.quil import Program from pyquil.quilbase import Declare from pyquil_for_azure_quantum import get_qpu, get_qvm
get_qvm
またはget_qpu
関数を使用して、QVM または QPU への接続を取得します。qc = get_qvm() # For simulation # qc = get_qpu("Ankaa-9Q-3") for submitting to a QPU
Quil プログラムを作成します。 任意の有効な Quil プログラムを受け入れますが、readout must
ro
という名前を付けます。program = Program( Declare("ro", "BIT", 2), H(0), CNOT(0, 1), MEASURE(0, ("ro", 0)), MEASURE(1, ("ro", 1)), ).wrap_in_numshots_loop(5) # Optionally pass to_native_gates=False to .compile() to skip the compilation stage result = qc.run(qc.compile(program)) data_per_shot = result.readout_data["ro"]
ここでは、
data_per_shot
はnumpy
配列であるため、numpy
メソッドを使用できます。assert data_per_shot.shape == (5, 2) ro_data_first_shot = data_per_shot[0] assert ro_data_first_shot[0] == 1 or ro_data_first_shot[0] == 0
すべてのデータを出力します。
print("Data from 'ro' register:") for i, shot in enumerate(data_per_shot): print(f"Shot {i}: {shot}")
重要
1 つのジョブで複数の回路を送信することは現在サポートされていません。 回避策として、backend.run
メソッドを呼び出して各回路を非同期に送信し、その後、各ジョブの結果を取得できます。 次に例を示します。
jobs = []
for circuit in circuits:
jobs.append(backend.run(circuit, shots=N))
results = []
for job in jobs:
results.append(job.result())
関連するコンテンツ
- Qiskit を使用して回線を Azure Quantum に送信します。
- Cirq を使用して回線を Azure Quantum に送信します。