Отправка определенных форматированных каналов в Azure Quantum

Узнайте, как использовать azure-quantumPython пакет для отправки каналов в определенных форматах в службу Azure Quantum. В этой статье показано, как отправлять каналы в следующих форматах:

Дополнительные сведения см. в разделе Квантовые цепи.

Примечание.

Пакет средств разработки Microsoft Quantum (классический QDK) больше не будет поддерживаться после 30 июня 2024 г. Если вы являетесь разработчиком QDK, рекомендуется перейти на новый пакет средств разработки Azure Quantum (современный QDK), чтобы продолжить разработку квантовых решений. Дополнительные сведения см. в разделе "Перенос кода Q# на современный QDK".

Необходимые компоненты

Чтобы запустить каналы в записной книжке в портал Azure, вам потребуется:

Для разработки и запуска каналов в Visual Studio Code также потребуется:

Создание новой записной книжки Jupyter Notebook

Записную книжку можно создать в VS Code или непосредственно на портале Azure Quantum.

  1. Войдите на портал Azure и выберите рабочую область, которую вы назначили на предыдущем шаге.
  2. В левой колонке выберите Записные книжки.
  3. Щелкните Мои записные книжки и щелкните Добавить новую.
  4. В поле Тип ядра выберите IPython.
  5. Введите имя файла и нажмите кнопку "Создать файл".

При открытии новой записной книжки автоматически создается код для первой ячейки на основе сведений о подписке и рабочей области.

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. Создайте вспомогательную submit_qir_job функцию для отправки канала QIR в 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 result
    
  3. target Выберите и отправьте канал QIR в Azure Quantum. Например, чтобы отправить канал QIR в симулятор targetIonQ:

    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

Помимо языков QIR, таких как Q# или Qiskit, можно отправлять квантовые каналы в форматы, относящиеся к поставщику, в Azure Quantum. Каждый поставщик имеет собственный формат для представления квантовых каналов.

Отправка канала в IonQ с помощью формата JSON

  1. Создайте квантовый канал с помощью формата JSON, не зависящего от языка, поддерживаемого IonQ targets, как описано в документации по API IonQ. Например, в приведенном ниже примере создается суперпозиция между тремя кубитами:

    circuit = {
        "qubits": 3,
        "circuit": [
            {
            "gate": "h",
            "target": 0
            },
            {
            "gate": "cnot",
            "control": 0,
            "target": 1
            },
            {
            "gate": "cnot",
            "control": 0,
            "target": 2
            },
        ]
    }
    
  2. Отправьте канал в IonQ target. В следующем примере используется симулятор IonQ, который возвращает объект Job.

    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}")
    

    Это действие выводит оценочные затраты в долларах США (USD).

    Примечание.

    Актуальные сведения о ценах см. в статье Цены на IonQ, или найдите рабочую область и просмотрите параметры ценообразования на вкладке "Поставщик" с помощью команды: aka.ms/aq/myworkspaces.

Отправка канала в PASQAL с помощью пакета SDK Для Пульса

Чтобы отправить канал в PASQAL, можно использовать пакет SDK Для Пульса для создания последовательностей импульсов и отправки их в PASQAL target.

Установка пакета SDK Для Pulser

Pulser — это платформа для создания, имитации и выполнения последовательностей импульсов для квантовых устройств нейтрального атома. Он разработан PASQAL в качестве сквозной передачи для отправки квантовых экспериментов на их квантовые процессоры. Дополнительные сведения см . в документации по Pulser.

Чтобы отправить последовательности импульсов, сначала установите пакеты ПАКЕТА SDK Для Pulser:

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 состоит из нейтральных атомов, захваченных на хорошо определенных позициях в решетке. Чтобы определить квантовые регистры, вы создадите массив кубитов в решетке. Например, следующий код создает 4x4 квадратных решеток кубитов:

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

    График 4x4 квадратной решетки с 16 кубитами.

Запись импульсной последовательности

Нейтральные атомы контролируются лазерными импульсами. Пакет SDK для Pulser позволяет создавать последовательности импульсов для применения к квантовому регистру.

  1. Сначала необходимо настроить последовательность импульсов и объявить каналы, которые будут использоваться для управления атомами. Например, следующий код объявляет два канала: ch0 и ch1.

    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)
    

    Несколько вещей, которые следует рассмотреть:

    • В Sequence Пульсе — это ряд операций, выполняемых в квантовом регистре.
    • Код настраивает последовательность операций, выполняемых на AnalogDevice устройстве. AnalogDevice — это предопределенное устройство в Pulser, представляющее квантовый компьютер Fresnel1.
  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
    

    Примечание.

    Требуемое время для выполнения цепи на QPU может оказаться разным в зависимости от текущего времени в очереди. Среднее время 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}
    

Отправка канала в Quantinuum с помощью OpenQASM

  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. В следующем примере используется проверяющий элемент управления API Quantinuum, который возвращает объект Job.

    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}")
    

    Будет отображена оценочная стоимость в кредитах HQC (H-System Quantum Credits).

    Примечание.

    Чтобы выполнить оценку затрат по quantinuum target, необходимо сначала перезагрузить пакет Azure-quantumPython с параметром [qiskit] и убедиться, что у вас есть последняя версия Qiskit. Дополнительные сведения см. в статье об обновлении пакета Azure-QuantumPython.

Отправка канала в Rigetti с помощью Quil

Самый простой способ отправки заданий Quil — использовать пакет pyquil-for-azure-quantum , так как он позволяет использовать инструменты и документацию библиотеки pyQuil . Без этого пакета 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_qvm Используйте или get_qpu функцию, чтобы получить подключение к QVM или QPU.

    qc = get_qvm()  # For simulation
    # qc = get_qpu("Ankaa-2") for submitting to a QPU
    
  3. Создайте программу Quil. Любая допустимая программа 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_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}")
    

Внимание

Отправка нескольких цепей в одном задании в настоящее время не поддерживается. В качестве обходного решения можно вызвать метод backend.run для асинхронной отправки каждой цепи, а затем получить результаты каждого задания. Например:

jobs = []
for circuit in circuits:
    jobs.append(backend.run(circuit, shots=N))

results = []
for job in jobs:
    results.append(job.result())