qdk.azure
Python モジュールを使用して、特定の形式の回線をAzure Quantum サービスに送信する方法について説明します。 この記事では、回線を次の形式で送信する方法について説明します。
詳細については、量子回路に関するページを参照してください。
前提条件
Visual Studio Code (VS Code) で回線を開発して実行するには、次のものが必要です。
アクティブなサブスクリプションを持つAzure アカウント。 Azure アカウントをお持ちでない場合は、無料で登録し、従後払いサブスクリプションにサインアップしてください。
Azure Quantum ワークスペース。 詳細については、「Azure Quantum ワークスペースを作成するを参照してください。
Microsoft Quantum Development Kit (QDK)、Python、および Jupyter 拡張機能がインストールされている VS Code。
Python extra 付きの
azureqdkパッケージと、ipykernelパッケージ。python -m pip install --upgrade "qdk[azure]" ipykernel
新しい Jupyter ノートブックを作成し、Quantum ワークスペースに接続する
VS Code の Jupyter ノートブックでワークスペースに接続するには、次の手順に従います。
VS Code で、[ 表示 ] メニューを開き、[ コマンド パレット] を選択します。
「Create: New Jupyter Notebook」と入力します。 空のJupyter Notebook ファイルが新しいタブで開きます。
ノートブックの最初のセルで、次のコードを実行します。 リソース ID は、Azure ポータルのワークスペースの Overview ペインにあります。
from qdk.azure import Workspace workspace = Workspace (resource_id="") # Add your resource ID
QIR形式で回路を送信する
量子中間表現 (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 回線を
submit_qir_jobに送信するtarget ヘルパー関数を作成します。 この例では、入力データ形式と出力データ形式がそれぞれ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 resultQIR 回線を特定の Azure Quantum target に送信します。 たとえば、QIR 回線を IonQ シミュレーター targetに送信するには、次のコードを実行します。
target = workspace.get_targets(name="ionq.simulator") result = submit_qir_job(target, QIR_routine, "QIR routine") result
プロバイダー固有の形式の回線をAzure Quantumに送信する
各Azure Quantum プロバイダーには、量子回路を表す独自の形式があります。 Q# や Qiskit などの QIR 言語ではなく、プロバイダー固有の形式で回線をAzure Quantumに送信できます。
回線を JSON 形式で IonQ に送信する
IonQ は JSON 形式を使用して、 targetsで回線を実行します。 詳細については、 IonQ targets と IonQ API のドキュメントを参照してください。
次の例では、JSON 形式の 3 つの量子ビット間に重ね合わせを作成します。
新しいセルで、JSON 形式の量子回路を作成します。
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)
Pulser SDK 形式で Pasqal に回線を送信する
Pulser SDK を使用してパルス シーケンスを作成し、Pasqal targetsに送信できます。
Pulser SDK をインストールする
Pulser は、中性原子量子デバイスのパルス シーケンスを作成、シミュレート、実行できるフレームワークです。 Pulser は、量子実験を量子プロセッサに送信するためのパススルーとして PASQAL によって設計されています。 詳細については、 Pulser のドキュメントを参照してください。
pulse シーケンスを送信するには、まず Pulser SDK パッケージをインストールします。
try:
import pulser
import pulser_azure
except ImportError:
!pip -q install pulser pulser-azure --index-url https://pypi.org/simple
量子レジスタを作成する
レジスタとレイアウトの両方を定義します。 レジスタはアトムを配置する場所を指定し、レイアウトはレジスタ内の原子をキャプチャして構造化するトラップの位置を指定します。
レイアウトの詳細については、 Pulser のドキュメントを参照してください。
Pasqal 量子コンピューター devices をインポートして、targetオブジェクトFRESNEL_CAN1を作成します。
from pulser_azure import AzureConnection
connection = AzureConnection(resource_id="") # Add your resource ID
devices = connection.fetch_available_devices()
QPU = devices["pasqal.qpu.fresnel-can1"]
レイアウトを設定する
事前に調整されたレイアウトが実験の要件を満たしていない場合は、カスタム レイアウトを使用します。
特定の任意のレジスタの場合、ニュートラルアトム QPU はレイアウトに従ってトラップを配置し、調整する必要があります。 各調整には時間がかかるため、可能な場合は、既存の校正済みレイアウトを再利用することをお勧めします。
任意のレイアウトを作成するには、次のいずれかのオプションを選択します。
指定したレジスタに基づいてレイアウトを自動的に生成します。 大規模なレジスタの場合、このプロセスは最適ではないソリューションを生成する可能性があります。 次に例を示します。
from pulser import Register 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(QPU)レジスタを作成するレイアウトを手動で定義するには、 Pulser のドキュメントを参照してください。
パルス シーケンスを書き込む
中性原子はレーザーパルスで制御されます。 Pulser SDK を使用すると、量子レジスタに適用するパルス シーケンスを作成できます。
原子を制御するチャネルを宣言して、パルス シーケンス属性を定義します。
Sequenceを作成するには、シーケンスが実行されるデバイスと共にRegisterインスタンスを指定します。 たとえば、次のコードは、1 つのチャネルを宣言します:ch0。from pulser import Sequence seq = Sequence(reg, QPU) # Print the available channels for your sequence print(seq.available_channels) # Declare a channel. For example, `rydberg_global` seq.declare_channel("ch0", "rydberg_global")注記
QPU = devices["pasqal.qpu.fresnel-can1"]デバイスを使用するか、Pulser から仮想デバイスをインポートして柔軟性を高めることができます。VirtualDeviceを使用すると、デバイスの仕様によって制約が少ないシーケンス作成が可能になり、エミュレーターで実行できます。 詳細については、 Pulser のドキュメントを参照してください。シーケンスにパルスを追加します。 これを行うには、宣言したチャネルにパルスを作成して追加します。 たとえば、次のコードはパルスを作成し、チャネル
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注記
QPU でジョブを実行するために必要な時間は、現在のキュー時刻によって異なります。 ワークスペースの [target] ウィンドウで、の平均キュー時間を表示できます。
プログラムを Pasqal に送信します。 実際の量子ハードウェアにコードを送信する前に、エミュレーター
pasqal.sim.emu-mpstargetでコードをテストすることをお勧めします。target = workspace.get_targets(name="pasqal.sim.emu-mps") # Change to "pasqal.qpu.fresnel-can1" to use FRESNEL_CAN1 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){ "counter": { "1000000": 8, "0010000": 1, "0010101": 1 } }
pulser バックエンドを使用してシーケンスを実行する
AzureConnection からのを使用すると、QPU バックエンドまたはリモート エミュレーター バックエンドを直接使用してシーケンスを実行できます。
from pulser.backends import RemoteMPSBackend, QPUBackend
backend = RemoteMPSBackend(sequence=seq, connection=connection) # Replace RemoteMPSBackend with QPUBackend to execute the sequence on the QPU
results = backend.run(job_params=[{"runs": 10}], wait=True)
print(results.results[0].final_bitstrings)
Counter({"1000000": 8, "0010000": 1, "0010101": 1})
Quantinuum に OpenQASM 回線を送信する
OpenQASM 表記で量子回路を作成します。 たとえば、次のコードは Teleportation 回線を作成します。
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]; """または、OpenQASM ファイルから回線を読み込みます。
with open("my_teleport.qasm", "r") as f: circuit = f.read()Quantinuum targetに回線を送信します。 次の例では、Quantinuum シミュレーター targetsのいずれかにジョブを送信します。
target = workspace.get_targets(name="quantinuum.sim.h2-1sc") job = target.submit(circuit, shots=500)ジョブが完了するのを待ってから、結果を取得してください。
results = job.get_results() print(results)
注記
これらの結果は、ランダムではないすべてのショットに対して 000 を返します。 これは、API バリデーターは、コードが Quantinuum ハードウェア上で実行できるかどうかをチェックするだけで、量子測定ごとに 0 を返すからです。 真の乱数ジェネレーターにするには、量子ハードウェアで回路を実行する必要があります。
リゲッティにキル回路を提出する
Quil ジョブを Rigetti target に送信するには、qdk.azurePython モジュールを使用します。
必要なインポートを読み込みます。
from azure.quantum import Workspace from azure.quantum.target.rigetti import Result, Rigetti, RigettiTarget, InputParamstargetオブジェクトを作成し、ジョブを送信する Rigetti targetの名前を渡します。 たとえば、次のコードはQVMtargetを選択します。target = Rigetti(workspace=workspace, name=RigettiTarget.QVM)Quil プログラムを作成します。 プログラムを受け入れるには、読み取り値を
"ro"に設定する必要があります。readout = "ro" bell_state_quil = f""" DECLARE {readout} BIT[2] H 0 CNOT 0 1 MEASURE 0 {readout}[0] MEASURE 1 {readout}[1] """ num_shots = 5 job = target.submit( input_data=bell_state_quil, name="bell state", shots=100, input_params=InputParams(skip_quilc=False) ) print(f"Job completed with state: {job.details.status}") result = Result(job) # This throws an exception if the job failed読み取りアウトの名前を使用して Result にインデックスを付けることができます。 次のコードでは、
data_per_shotは長num_shotsのリストであり、リスト内の各項目は、そのショットのレジスタのデータを含む別のリストです。data_per_shot = result[readout] ro_data_first_shot = data_per_shot[0]この場合、レジスタの型は BIT であるため、型は整数であり、値は 0 または 1 です。
assert isinstance(ro_data_first_shot[0], int) assert ro_data_first_shot[0] == 1 or ro_data_first_shot[0] == 0すべてのデータを出力します。
print(f"Data from '{readout}' 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に送信します。