你当前正在访问 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

创建量子寄存器

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

有关布局的详细信息,请参阅 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:从定义的寄存器自动派生布局

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

    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")
    
    • 使用特定陷阱 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.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())