特定のフォーマット済み回線を Azure Quantum に送信する方法

パッケージを使用 azure-quantumPython して、特定の形式の回線を Azure Quantum サービスに送信する方法について説明します。 この記事では、回線を次の形式で送信する方法について説明します。

詳細については、量子回路に関するページを参照してください。

前提条件

Azure portal のノートブックで回線を実行するには、次のものが必要です。

  • アクティブなサブスクリプションが含まれる Azure アカウント。 Azure アカウントをお持ちでない場合は、無料で登録し、従量課金制サブスクリプションにサインアップ してください
  • Azure Quantum ワークスペース。 詳細については、「Azure Quantum ワークスペースを作成する」を参照してください。

Visual Studio Code で回線を開発して実行するには、次も必要です。

  • Python PipPythonインストールされている環境。

  • Azure Quantum Development Kit、Jupyter 拡張機能がインストールされている VS CodePython

  • Azure Quantum qsharpazure-quantumおよび ipykernel パッケージ。

    python -m pip install --upgrade qsharp azure-quantum ipykernel
    

新しい Jupyter Notebook を作成する

ノートブックは VS Code で作成することも、Azure Quantum ポータルで直接作成することもできます。

  1. Azure portal にログインし、前の手順のワークスペースを選択します。
  2. 左側のブレードで、[ノートブック] を選択します。
  3. [マイ ノートブック] をクリックし、[新規追加] をクリックします。
  4. [カーネルの種類][IPython] を選択します。
  5. ファイルの名前を入力し、[ファイルの作成] をクリックします

新しい Notebook が開くと、サブスクリプションとワークスペースの情報に基づいて、最初のセルのコードが自動的に作成されます。

from azure.quantum import Workspace
workspace = Workspace ( 
    resource_id = "", # Your resource_id 
    location = ""  # Your workspace location (for example, "westus") 
)

QIR 形式の回線を送信する

量子中間表現 (QIR) は、量子プログラミング言語/フレームワークとターゲットの量子計算プラットフォーム間の共通インターフェイスとして機能する中間表現です。 詳細については、「量子中間表現」を参照してください。

  1. 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}
    """
    
  2. QIR 回線を submit_qir_job 送信するヘルパー関数を作成します target。 入力データ形式と出力データ形式は、それぞれ指定qir.v1microsoft.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
    
  3. QIR 回線を target 選択して 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 に回線を送信する

  1. IonQ API ドキュメントで説明されているように、IonQ でサポートされている言語に依存しない JSON 形式を使用して量子回路を作成しますtargets たとえば、次のサンプルでは、3 つの量子ビットの間に重ね合わせが作成されます。

    circuit = {
        "qubits": 3,
        "circuit": [
            {
            "gate": "h",
            "target": 0
            },
            {
            "gate": "cnot",
            "control": 0,
            "target": 1
            },
            {
            "gate": "cnot",
            "control": 0,
            "target": 2
            },
        ]
    }
    
  2. 回線を IonQ targetに送信します。 次の例では、Job オブジェクトを返す IonQ シミュレーターを使用します。

    target = workspace.get_targets(name="ionq.simulator")
    job = target.submit(circuit)
    
  3. ジョブが完了するまで待ってから、結果を取得します。

    results = job.get_results()
    print(results)
    
    .....
    {'duration': 8240356, 'histogram': {'0': 0.5, '7': 0.5}}
    
  4. その後、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")
    

    IonQ のジョブ出力

  5. 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

量子レジスタを作成する

  1. 最初に、PASQAL 量子コンピューター targetFresnel をインポートする 'devices' オブジェクトを作成します。 Fresnel QPU には、量子レジスタを作成するために使用できる定義済みのトラップ レイアウトが用意されています。 量子レジスタを構成するには、量子ビットの配列を配置します。

    from pulser_pasqal import PasqalCloud
    
    devices = PasqalCloud().fetch_available_devices()
    QPU = devices["FRESNEL"]
    
    # List all available calibrated register layouts
    for calibrated_register_layout in QPU.calibrated_register_layouts.keys():
        print(calibrated_register_layout)
    
  2. 次に、量子ビット レジスタのレイアウトを定義します。 この例では、トラップ レイアウトとして a TriangularLatticeLayout(61, 5.0µm) を使用します。

    layout = QPU.calibrated_register_layouts[
        "TriangularLatticeLayout(61, 5.0µm)"
    ]
    layout.draw()
    

    次の図は、選択したレイアウトの表示を示しています。

    量子ビット レジスタ用に選択されたレイアウトの図。レイアウトは、40 回 30 マイクロメートルの配列で 60 ポイントを示しています。

  3. 次に、レイアウトからトラップのセットを選択する量子ビット レジスタを定義します。 この例では、レイアウトに 60 個のトラップがあり、ID を使用して 7 個のトラップを選択し、7 量子ビットの量子レジスタを定義します。

    reg = layout.define_register(*[30, 21, 26, 35, 39, 34, 25])
    reg.draw()
    

    次の図は、量子ビット レジスタの最終的な表示を示しています。

    60 ポイント レイアウトから 7 ポイントを選択した後の最終的な量子レジスタを示す図。

パルス シーケンスを書き込む

中性原子はレーザーパルスで制御されます。 Pulser SDK を使用すると、量子レジスタに適用するパルス シーケンスを作成できます。

  1. まず、原子の制御に使用するチャネルを宣言することで、パルス シーケンス属性を定義します。 を作成 Sequenceするには、シーケンスが Register 実行されるデバイスと共にインスタンスを指定する必要があります。 たとえば、次のコードは 1 つのチャネルを宣言します ch0

    Note

    デバイスを QPU = devices["FRESNEL"] 使用するか、Pulser から仮想デバイスをインポートして柔軟性を高めることができます。 a を 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")
    
  2. シーケンスにパルスを追加します。 これを行うには、宣言したチャネルにパルスを作成して追加します。 たとえば、次のコードはパルスを作成し、チャネルに追加します 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

  1. まず、適切な入力データ形式と出力データ形式を設定する必要があります。 たとえば、次のコードでは、入力データ形式を 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 でジョブを実行するために必要な時間は、現在のキュー時刻によって異なります。 ワークスペースの [プロバイダー] ブレードを選択すると、a target の平均キュー時間を表示できます。

  2. プログラムを 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
    }
    

高度なエミュレーター機能を調べる

PASQAL のエミュレーター には、Fresnel QPU ではまだサポートされていない高度な機能が用意されています。 事前に調整されたレイアウトの制限なしに、カスタムの方法でレジスタを整理できます。 たとえば、次のコードでは、量子ビットの 4 x 4 平方格子が作成されます。

import numpy as np
from pulser import Register, Sequence

L = 4
square = np.array([[i, j] for i in range(L) for j in range(L)], dtype=float)
square -= np.mean(square, axis=0)
square *= 5

qubits = dict(enumerate(square))
custom_reg = Register(qubits)
custom_reg.draw()

seq = Sequence(custom_reg, QPU)

16 量子ビットを持つ 4 x 4 正方形の格子のプロット。

カスタム レジスタを定義したら、前のセクションで説明したのとまったく同じ手順に従って、エミュレーターで指定したシーケンスを送信できます。

Note

カスタム レジスタ機能は近日中に FRESNEL で利用できるようになります。

OpenQASM を使用して Quantinuum に回線を送信する

  1. 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()
    
  2. 回路を Quantinuum targetに送信します。 次の例では、Job オブジェクトを返す Quantinuum API 検証コントロールを使用します。

    target = workspace.get_targets(name="quantinuum.sim.h1-1sc")
    job = target.submit(circuit, shots=500)
    
  3. ジョブが完了するまで待ってから、結果を取得します。

    results = job.get_results()
    print(results)
    
    ........
    {'c0': ['000',
    '000',
    '000',
    '000',
    '000',
    '000',
    '000',
    ...
    ]}
    
  4. その後、Matplotlib を使用して結果を視覚化できます。

    import pylab as pl
    pl.hist(results["c0"])
    pl.ylabel("Counts")
    pl.xlabel("Bitstring")
    

    Quantinuum ジョブの出力

    このヒストグラムを見ると、乱数ジェネレーターから毎回 0 が返されている (乱数とは言えない) ことに気付くかもしれません。 これは、この API 検証コントロールが Quantinuum ハードウェアでコードが確実に正常に実行されるようにする一方で、どの量子測定に対しても 0 を返すためです。 真の乱数ジェネレーターにするには、量子ハードウェアで回路を実行する必要があります。

  5. QPU でジョブを実行する前に、実行するコストを見積もる必要があります。

    Note

    最新の価格の詳細については、「Azure Quantum の価格」を参照するか、ワークスペースを見つけて、ワークスペースの [プロバイダー] タブで価格オプションを表示します。aka.ms/aq/myworkspaces

Quil を使用してリゲッティに回線を送信する

Quil ジョブを送信する最も簡単な方法は、pyQuil ライブラリのツールとドキュメントを使用できるため、pyquil-for-azure-quantum パッケージを使用することです。 このパッケージがないと、pyQuil を使用して Quil プログラムを 構築 できますが、Azure Quantum に送信することはできません。

また、Quil プログラムを手動で構築し、azure-quantum パッケージを直接使用して送信することもできます。

  1. まず、必要なインポートを読み込みます。

    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
    
  2. またはget_qpu関数をget_qvm使用して、QVM または QPU への接続を取得します。

    qc = get_qvm()  # For simulation
    # qc = get_qpu("Ankaa-9Q-3") for submitting to a QPU
    
  3. Quil プログラムを作成します。 任意の有効なキルプログラムは受け入れられますが、読み取りは名前を付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"]
    
  4. ここでは配列 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
    
  5. すべてのデータを出力します。

    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())