Как отправить схемы в специфическом формате в Azure Quantum

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

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

Предварительные требования

Для разработки и запуска схем в Visual Studio Code (VS Code) необходимо иметь следующее:

  • Учетная запись Azure с активной подпиской. Если у вас нет учетной записи Azure, зарегистрируйтесь бесплатно и зарегистрируйтесь для подписки pay-as-you-go.

  • Рабочая область Azure Quantum. Дополнительные сведения см. в разделе Создание рабочей области Azure Quantum.

  • Среда Python с установленными Python и Pip.

  • VS Code с установленными расширениями Microsoft Quantum Development Kit (QDK), Python и Jupyter.

  • Пакет qdkPython с дополнительным пакетом azure и пакетом ipykernel .

    python -m pip install --upgrade "qdk[azure]" ipykernel
    

Создайте новую записную книжку Jupyter и подключитесь к Quantum рабочей среде

Чтобы подключиться к рабочей области в записной книжке Jupyter в VS Code, выполните следующие действия:

  1. В VS Code откройте меню "Вид " и выберите палитру команд.

  2. Введите Create: New Jupyter Notebook. Пустой файл Jupyter Notebook открывается на новой вкладке.

  3. В первой ячейке записной книжки выполните следующий код. Идентификатор ресурса можно найти в области Overview для рабочей области на портале Azure.

    from qdk.azure import Workspace
    
    workspace = Workspace (resource_id="") # Add your resource ID 
    

Отправьте схемы с форматированием QIR

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

    target = workspace.get_targets(name="ionq.simulator") 
    result = submit_qir_job(target, QIR_routine, "QIR routine")
    result
    

Отправка схемы в формате, определяемом поставщиком, в Azure Quantum

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

Отправьте схему в IonQ в формате JSON

IonQ использует формат JSON для запуска квантовых схем на targets. Дополнительные сведения см. в IonQtargets и в документации по API IonQ.

В следующем примере создается суперпозиция между тремя кубитами в формате JSON.

  1. В новой ячейке создайте квантовый канал в формате JSON.

    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)
    

Отправьте схему в Pasqal в формате Pulser SDK

Пакет SDK для Pulser можно использовать для создания последовательностей импульсов и отправки их в Pasqal targets.

Установка SDK Pulser

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

Чтобы отправить пульсовые последовательности, сначала установите пакеты SDK Pulser.

try:
    import pulser
    import pulser_azure
except ImportError:
    !pip -q install pulser pulser-azure --index-url https://pypi.org/simple

Создание квантового регистра

Определите как регистр, так и макет. Регистр указывает, где упорядочивать атомы, а макет указывает позиции ловушек, которые захватывают и структурируют атомы в регистре.

Дополнительные сведения о макетах см. в документации по Pulser.

Создайте объект devices для импорта квантового компьютера Pasqal 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"]
Настройка макета

Используйте пользовательский макет, если предварительно откалиброванные макеты не соответствуют требованиям эксперимента.

Для заданного произвольного регистра, квантовый процессор на основе нейтральных атомов размещает ловушки в соответствии со схемой, которую затем необходимо откалибровать. Так как каждая калибровка занимает некоторое время, рекомендуется повторно использовать существующий макет калибровки, когда это возможно.

Чтобы создать произвольный макет, выберите один из следующих вариантов:

  • Автоматически создайте макет на основе указанного регистра. Для больших регистров этот процесс может создавать субоптимальные решения. Например:

    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.

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

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

  1. Определите атрибуты последовательности импульсов, объявив каналы, управляющие атомами. Чтобы создать Sequenceэкземпляр, укажите Register экземпляр вместе с устройством, где будет выполняться последовательность. Например, следующий код объявляет один канал: 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.

  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
    

    Примечание.

    Время, необходимое для выполнения задания на ЦП, зависит от текущего времени очереди. Среднее время target очереди можно просмотреть на панели "Поставщики " рабочей области.

  2. Отправьте программу в 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 помощью pulser-azure можно напрямую использовать серверную часть 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})

Отправьте схему 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];
    """
    

    Загрузите схему из файла OpenQASM.

    with open("my_teleport.qasm", "r") as f:
        circuit = f.read()
    
  2. Отправьте цепь в Quantinuum target. В следующем примере задание отправляется в один из симуляторов targets Quantinuum.

    target = workspace.get_targets(name="quantinuum.sim.h2-1sc")
    job = target.submit(circuit, shots=500)
    
  3. Дождитесь завершения задания и получите результаты.

    results = job.get_results()
    print(results)
    

Примечание.

Эти результаты возвращают 000 для каждого выстрела, который не является случайным. Это связано с тем, что валидатор API проверяет, может ли код выполняться на оборудовании Quantinuum, но всегда возвращает 0 для каждого квантового измерения. Для генератора по-настоящему случайных чисел необходимо запустить цепь на квантовом оборудовании.

Отправьте схему Quil в Rigetti

Чтобы отправить задание Quil в Rigetti target, используйте модуль qdk.azurePython.

  1. Загрузите необходимые импорты.

    from azure.quantum import Workspace
    from azure.quantum.target.rigetti import Result, Rigetti, RigettiTarget, InputParams
    
  2. target Создайте объект и передайте имя Rigettitarget, в которое вы хотите отправить задание. Например, следующий код выбирает QVMtarget.

    target = Rigetti(workspace=workspace, name=RigettiTarget.QVM)
    
  3. Создайте программу 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
    
  4. Можно индексировать результат с помощью имени считываемого параметра. В следующем коде 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
    
  5. Распечатайте все данные.

    print(f"Data from '{readout}' 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())