次の方法で共有


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

  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 でジョブを実行する前に、実行するコストを見積もる必要があります。

    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 を使用すると、量子レジスタに適用するパルス シーケンスを作成できます。

  1. まず、原子の制御に使用するチャネルを宣言することで、パルス シーケンス属性を定義します。 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")
    
  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 でジョブを実行するために必要な時間は、現在のキュー時刻によって異なります。 ワークスペースの Providers ブレードを選択すると、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
    }
    

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 を使用して 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-9Q-3") for submitting to a QPU
    
  3. 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"]
    
  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())