Поделиться через


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

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

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

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

Чтобы запустить каналы в записной книжке в портал 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

Quantum Intermediate Representation (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. 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. Перед выполнением задания на ЦП необходимо оценить, сколько будет стоить выполнение.

    Примечание.

    Актуальные сведения о ценах см. в статье Цены на 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

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

Перед продолжением необходимо определить как регистр, так и макет. Регистр указывает, где будут упорядочены атомы, в то время как макет указывает расположение ловушек, необходимых для захвата и структуры этих атомов в регистре.

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

  • Сначала вы создадите объект "devices" для импорта квантового компьютера targetPASQAL , Fresnel.

    from pulser_pasqal import PasqalCloud
    
    devices = PasqalCloud().fetch_available_devices()
    QPU = devices["FRESNEL"]
    
Предварительно откалиброванные макеты

Устройство определяет список предварительно настроенных макетов. Вы можете создать регистрацию из одного из этих макетов.

Это рекомендуемый вариант, так как он улучшит производительность QPU.

  • Вариант 1. Определение регистрации с помощью предварительно настроенных макетов

    Проверьте макеты, доступные в Fresnel, и определите регистр из этого макета. Дополнительные сведения о том, как это сделать, см. в документации по пульсу.

    Пример:

    # 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()
    
Произвольные макеты

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

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

  • Вариант 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. Определение регистрации с помощью вручную определенного макета

    • Создание произвольного макета с 20 ловушками случайным образом, расположенным в плоскости 2D
    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")
    
    • Определение регистра с определенными идентификаторами ловушки
    trap_ids = [4, 8, 19, 0]
    reg = layout.define_register(*trap_ids, qubit_ids=["a", "b", "c", "d"])
    reg.draw()
    

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

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

  1. Во-первых, вы определяете атрибуты последовательности пульса, объявляя каналы, которые будут использоваться для управления атомами. Чтобы создать Sequenceэкземпляр, необходимо предоставить Register экземпляр вместе с устройством, где будет выполнена последовательность. Например, следующий код объявляет один канал: ch0

    Примечание.

    Вы можете использовать устройство или импортировать виртуальное 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
    

    Примечание.

    Время, необходимое для выполнения задания на ЦП, зависит от текущего времени очереди. Среднее время 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
    }
    

Отправка канала в 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. Перед выполнением задания на ЦП необходимо оценить, сколько будет стоить выполнение.

Отправка канала в 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-9Q-3") 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_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}")
    

Внимание

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

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

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