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

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

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

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

先决条件

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

  • 具有活动订阅的 Azure 帐户。 如果没有 Azure 帐户,请免费注册并注册 即用即付订阅

  • Azure Quantum 工作区。 有关详细信息,请参阅创建 Azure Quantum 工作区

  • Python安装了 Python Pip 的环境。

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

  • 带有azure-quantum额外功能和ipykernel包的qdkPython库。

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

创建新的 Jupyter 笔记本

若要在 VS Code 中创建笔记本,请执行以下步骤:

  1. 在 VS Code 中,打开 “视图 ”菜单,然后选择 “命令面板”。

  2. 输入并选择“ 创建:新建 Jupyter Notebook”。

  3. 若要连接到 Azure Quantum 服务,程序需要 Azure Quantum 工作区的资源 ID 和位置。

    1. 登录到 Azure 帐户, https://portal.azure.com

    2. 选择 Azure Quantum 工作区,然后导航到 “概述”。

    3. 复制字段中的参数。

      Visual Studio Code 的屏幕截图,其中显示了如何展开 Quantum 工作区的概述窗格。

  4. 在笔记本的第一个单元格中,将值粘贴到以下 Workspace 构造函数中,以创建 workspace 连接到 Azure Quantum 工作区的对象。

    from qdk.azure import Workspace
    workspace = Workspace ( 
        resource_id = "", # Add your resource_id 
        location = ""  # Add 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
    import pulser_pasqal
except ImportError:
    !pip -q install pulser pulser-pasqal --index-url https://pypi.org/simple

创建量子寄存器

在继续操作之前,需要同时定义寄存器和布局。 寄存器指定将排列原子的位置,而布局指定捕获和构造寄存器中这些原子所需的陷阱的位置。

有关布局的详细信息,请参阅 Pulser 文档

  • 首先,创建一个“devices”对象来导入 PASQAL 量子计算机 targetFresnel

    from pulser_pasqal import PasqalCloud
    
    devices = PasqalCloud().fetch_available_devices()
    QPU = devices["FRESNEL"]
    
预先校准的布局

设备定义预先校准的布局列表。 可以从其中一种布局生成注册。

这是建议的选项,因为它将提高 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:从定义的寄存器自动派生布局

    此选项允许基于指定寄存器自动生成布局。 但是,对于大型寄存器,此过程可能会产生亚最佳解决方案,因为用于创建布局的算法存在限制。

    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(device) 
    
  • 选项 3:使用手动定义的布局定义寄存器

    • 创建一个任意布局,其中 20 个陷阱随机定位在 2D 平面中

      import numpy as np
      from pulser.register.register_layout import RegisterLayout
      
      # 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 以及将执行序列的设备。 例如,以下代码声明一个通道: 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
    }
    

使用 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.h2-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-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())