次の方法で共有


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

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

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

前提条件

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

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

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

  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 回線を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
    
  3. 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 に回線を送信する

  1. 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
            },
        ]
    }
    
  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 でジョブを実行する前に、それを実行するコストを見積もることができます。 QPU でジョブを実行するコストを見積もるには、estimate_cost メソッドを使用できます。

    target = workspace.get_targets(name="ionq.qpu")
    cost = target.estimate_cost(circuit, shots=500)
    
    print(f"Estimated cost: {cost.estimated_total}")
    

    これにより、推定コストが米ドルで出力されます。

    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
    !pip -q install pulser-core

量子レジスタを作成する

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

    import numpy as np
    import pulser
    from pprint import pprint
    from pulser import Pulse, Sequence, Register
    
  2. PASQALのQPUは、格子内の明確に定義された位置に閉じ込められた中性原子で構成されています。 量子レジスタを定義するには、格子上に量子ビットの配列を作成します。 たとえば、次のコードでは、量子ビットの 4 x 4 平方格子が作成されます。

    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))
    reg = Register(qubits)
    reg.draw()
    

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

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

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

  1. まず、パルス シーケンスを設定し、原子の制御に使用するチャネルを宣言する必要があります。 たとえば、次のコードは、 ch0ch1の 2 つのチャネルを宣言します。

    from pulser.devices import Chadoq2
    
    seq = Sequence(reg, Chadoq2)
    
    seq.declare_channel("ch0", "raman_local")
    print("Available channels after declaring 'ch0':")
    pprint(seq.available_channels)
    
    seq.declare_channel("ch1", "rydberg_local", initial_target=4)
    print("\nAvailable channels after declaring 'ch1':")
    pprint(seq.available_channels)
    

    次の考慮事項があります。

    • Pulser の Sequence は、量子レジスタで実行される一連の操作です。
    • このコードは、 AnalogDevice デバイスで実行される一連の操作を設定します。 AnalogDevice は、Fresnel1 と同等の量子コンピューターを表す Pulser の定義済みデバイスです。
  2. パルス シーケンスを作成します。 これを行うには、宣言したチャネルにパルスを作成して追加します。 たとえば、次のコードは単純なパルスを作成し、チャネル ch0に追加した後、複雑なパルスを作成してチャネル ch1に追加します。

    seq.target(1, "ch0") # Target qubit 1 with channel "ch0"
    simple_pulse = Pulse.ConstantPulse(200, 2, -10, 0)
    seq.add(simple_pulse, "ch0") # Add the pulse to channel "ch0"
    seq.delay(100, "ch1")
    from pulser.waveforms import RampWaveform, BlackmanWaveform
    
    duration = 1000
    # Create a Blackman waveform with a duration of 1000 ns and an area of pi/2 rad
    amp_wf = BlackmanWaveform(duration, np.pi / 2)  
    # Create a ramp waveform with a duration of 1000 ns and a linear sweep from -20 to 20 rad/µs
    detuning_wf = RampWaveform(duration, -20, 20) 
    
    # Create a pulse with the amplitude waveform amp_wf, the detuning waveform detuning_wf, and a phase of 0 rad.
    complex_pulse = Pulse(amp_wf, detuning_wf, phase=0) 
    complex_pulse.draw()
    seq.add(complex_pulse, "ch1") # Add the pulse to channel "ch1"
    

この画像は、単純なパルスと複雑なパルスを示しています。

単純パルスと複素パルスのプロット。

シーケンスを 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)
    #print(json.dumps(input_data, indent=4, sort_keys=True))
    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):
        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=100 # Number of shots
        )
    
        print(f"Queued job: {job.id}")
        job.wait_until_completed()
        print(f"Job completed with state: {job.details.status}")
        result = job.get_results()
    
        return result
    

    Note

    QPU で回路を実行するために必要な時間は、現在のキュー時間によって異なります。 ワークスペースの Providers ブレードを選択すると、targetの平均キュー時間を表示できます。

  2. プログラムを PASQAL に送信します。 たとえば、プログラムを PASQAL Emu-TN targetに送信できます。

    target = workspace.get_targets(name="pasqal.sim.emu-tn")
    submit_job(target, seq)
    
    {'0000000000000000': 59,
     '0000100000000000': 39,
     '0100000000000000': 1,
     '0100100000000000': 1}
    

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 でジョブを実行する前に、それを実行するコストを見積もることができます。 QPU でジョブを実行するコストを見積もるには、estimate_cost メソッドを使用できます。

    target = workspace.get_targets(name="quantinuum.qpu.h1-1")
    cost = target.estimate_cost(circuit, shots=500)
    
    print(f"Estimated cost: {cost.estimated_total}")
    

    これにより、見積もりコストが H-System Quantum クレジット (HQC) で出力されます。

    Note

    Quantinuum targetに対してコスト見積もりを実行するには、最初に [qiskit] パラメーターを使用して azure-quantumPython パッケージを再読み込みし、Qiskit の最新バージョンがあることを確認する必要があります。 詳細については、「 azure-quantum Python パッケージの更新を参照してください。

    Note

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

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

Quil ジョブを送信する最も簡単な方法は、pyQuil ライブラリのツールとドキュメントを使用できるため、pyquil-for-azure-quantum パッケージを使用することです。 このパッケージがないと、pyQuil を使用して construct 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_qvmまたはget_qpu関数を使用して、QVM または QPU への接続を取得します。

    qc = get_qvm()  # For simulation
    # qc = get_qpu("Ankaa-2") for submitting to a QPU
    
  3. Quil プログラムを作成します。 任意の有効な Quil プログラムを受け入れますが、readout mustroという名前を付けます。

    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_shotnumpy 配列であるため、 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())