你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

如何将特定格式化线路提交到 Azure Quantum

了解如何使用 azure-quantumPython 包将特定格式的线路提交到 Azure Quantum 服务。 本文介绍如何使用以下格式提交线路:

有关详细信息,请参阅量子线路

先决条件

若要在 Azure 门户 的 Notebook 中运行线路,需要:

若要在 Visual Studio Code 中开发和运行线路,还需要:

  • Python安装了 Python Pip 的环境。

  • 安装了 Azure Quantum 开发工具包PythonJupyter 扩展的 VS Code。

  • Azure Quantum qsharpazure-quantumipykernel包。

    python -m pip install --upgrade qsharp azure-quantum ipykernel
    

创建新的 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) 是一种中间表示形式,充当量子编程语言/框架和目标量子计算平台之间的公共接口。 有关详细信息,请参阅量子中间表示

  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.v1microsoft.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

除了 QIR 语言(如 Q# 或 Qiskit),还可以将提供程序特定格式的量子线路提交到 Azure Quantum。 每个提供程序都有自己的表示量子线路的格式。

使用 JSON 格式将线路提交到 IonQ

  1. 使用 IonQ 支持的与语言无关的 JSON 格式创建量子线路,如 IonQ API 文档中所述targets 例如,以下示例在三个量子比特之间创建叠加:

    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 上运行作业之前,应估算运行的成本。

    注意

    有关最新定价详细信息,请参阅 IonQ 定价,或通过以下网页找到你的工作区并查看该工作区的“提供商”选项卡中的定价选项:aka.ms/aq/myworkspaces

使用 Pulser SDK 将线路提交到 PASQAL

若要将线路提交到 PASQAL,可以使用 Pulser SDK 创建脉冲序列并将其提交到 PASQAL target。

安装 Pulser SDK

Pulser 是一个框架,用于为中性原子量子设备组合、模拟和执行脉冲序列。 它由 PASQAL 设计为传递,用于将量子实验提交到其量子处理器。 有关详细信息,请参阅 Pulser 文档

若要提交脉冲序列,请先安装 Pulser SDK 包:

try:
    import pulser
except ImportError:
    !pip -q install pulser

创建量子寄存器

  1. 首先,创建一个“devices”对象来导入 PASQAL 量子计算机 targetFresnel。 Fresnel QPU 提供预定义的陷阱布局,可用于创建量子寄存器。 若要配置量子寄存器,请排列量子比特数组。

    from pulser_pasqal import PasqalCloud
    
    devices = PasqalCloud().fetch_available_devices()
    QPU = devices["FRESNEL"]
    
    # List all available calibrated register layouts
    for calibrated_register_layout in QPU.calibrated_register_layouts.keys():
        print(calibrated_register_layout)
    
  2. 接下来,定义量子比特寄存器的布局。 在此示例中,你将使用 a TriangularLatticeLayout(61, 5.0µm) 作为陷阱布局。

    layout = QPU.calibrated_register_layouts[
        "TriangularLatticeLayout(61, 5.0µm)"
    ]
    layout.draw()
    

    下图显示了所选布局的显示。

    为量子比特寄存器选择的布局示意图。布局在 40 倍 30 微米的数组中显示 60 磅。

  3. 然后,定义量子比特寄存器,从布局中选择一组陷阱。 在此示例中,布局有 60 个陷阱,使用其 ID 选择 7 个陷阱来定义 7 个量子位的量子寄存器。

    reg = layout.define_register(*[30, 21, 26, 35, 39, 34, 25])
    reg.draw()
    

    下图显示了量子比特寄存器的最终显示。

    显示从 60 点布局中选择 7 磅后的最终量子寄存器的关系图。

编写脉冲序列

中性原子由激光脉冲控制。 Pulser SDK 允许创建脉冲序列以应用于量子寄存器。

  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
    

    注意

    在 QPU 上运行作业所需的时间取决于当前的队列时间。 可以通过选择工作区的“提供程序”边栏选项卡来查看其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
    }
    

探索高级模拟器功能

PASQAL 的仿真器 提供弗雷内尔 QPU 尚不支持的高级功能。 你可以以自定义方式组织注册,而无需预先校准的布局中的任何限制。 例如,以下代码创建量子位的 4x4 平方格子:

import numpy as np
from pulser import Register, Sequence

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

seq = Sequence(custom_reg, QPU)

具有 16 个量子位的 4x4 平方格子的绘图。

定义自定义寄存器后,可以按照上一部分中概述的完全相同的步骤在模拟器上发送指定的序列。

注意

即将在 FRESNEL 上提供自定义注册功能。

使用 OpenQASM 将线路提交到 Quantinuum

  1. OpenQASM 表示形式创建量子线路。 例如,下面的示例创建 Teleportation 线路:

    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 上运行作业之前,应估算运行的成本。

    注意

    有关最新的定价详细信息,请参阅 Azure Quantum 定价,或通过: aka.ms/aq/myworkspaces 在工作区的“提供程序”选项卡中查找工作区并查看定价选项。

使用 Quil 将线路提交到 Rigetti

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