다음을 통해 공유


Azure Quantum에 특정 형식 회로를 제출하는 방법

패키지를 사용하여 특정 형식의 azure-quantumPython 회로를 Azure Quantum 서비스에 제출하는 방법을 알아봅니다. 이 문서에서는 다음 형식으로 회로를 제출하는 방법을 보여 줍니다.

자세한 내용은 Quantum 회로를 참조 하세요.

필수 조건

Azure Portal의 Notebook에서 회로를 실행하려면 다음이 필요합니다.

  • 활성 구독이 있는 Azure 계정. Azure 계정이 없는 경우 무료로 등록하고 종량제 구독등록합니다.
  • Azure Quantum 작업 영역 자세한 내용은 Azure Quantum 작업 영역 만들기를 참조하세요.

Visual Studio Code에서 회로를 개발하고 실행하려면 다음이 필요합니다.

  • Python PipPython 설치된 환경입니다.

  • Azure Quantum Development Kit PythonJupyter 확장이 설치된 VS Code

  • Azure Quantum qsharpazure-quantumipykernel 패키지.

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

새 Jupyter Notebook 만들기

VS Code 또는 Azure Quantum 포털에서 직접 Notebook을 만들 수 있습니다.

  1. Azure Portal에 로그인하고 이전 단계에서 만든 작업 영역을 선택합니다.
  2. 왼쪽 블레이드에서 Notebooks를 선택합니다.
  3. 내 Notebooks를 클릭하고 새로 추가를 클릭합니다.
  4. 커널 형식에서 IPython을 선택합니다.
  5. 파일의 이름을 입력하고 파일 만들기를 클릭합니다.

새 Notebook이 열리면 구독 및 작업 영역 정보에 따라 첫 번째 셀에 대한 코드가 자동으로 만들어집니다.

from azure.quantum import Workspace
workspace = Workspace ( 
    resource_id = "", # Your resource_id 
    location = ""  # Your workspace location (for example, "westus") 
)

QIR 형식 회로 제출

QIR(Quantum Intermediate Representation)은 양자 프로그래밍 언어/프레임워크와 대상 양자 계산 플랫폼 간의 공통 인터페이스 역할을 하는 중간 표현입니다. 자세한 내용은 양자 중간 표현을 참조하세요.

  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. QIR 회로를 submit_qir_job 제출하는 도우미 함수를 만듭니다 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 회로를 target 선택하고 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에 제출

Q# 또는 Qiskit과 같은 QIR 언어 외에도 공급자별 형식의 양자 회로를 Azure Quantum에 제출할 수 있습니다. 각 공급자에는 양자 회로를 나타내는 고유한 형식이 있습니다.

JSON 형식을 사용하여 IonQ에 회로 제출

  1. IonQ API 설명서에 설명된 대로 IonQtargets에서 지원하는 언어 독립적 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)
    
    .....
    {'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 설명서를 참조 하세요.

미리 보정된 레이아웃

디바이스는 미리 보정된 레이아웃 목록을 정의합니다. 이러한 레이아웃 중 하나에서 레지스터를 빌드할 수 있습니다.

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()
    
임의 레이아웃

미리 보정된 레이아웃이 실험의 요구 사항을 충족하지 않는 경우 사용자 지정 레이아웃을 만들 수 있습니다.

지정된 임의 레지스터의 경우 중립 원자 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: 수동으로 정의된 레이아웃을 사용하여 레지스터 정의

    • 2D 평면에 임의로 배치된 20개의 트랩이 있는 임의 레이아웃 만들기
    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 표현에서 양자 회로를 만듭니다. 예를 들어 다음 예제에서는 순간 이동 회로를 만듭니다.

    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에서 작업을 실행하기 전에 실행하는 데 드는 비용을 예측해야 합니다.

Quil을 사용하여 Rigetti에 회로 제출

Quil 작업을 제출하는 가장 쉬운 방법은 pyQuil 라이브러리의 도구 및 설명서를 사용할 수 있으므로 pyquil-for-azure-quantum 패키지를 사용하는 것입니다. 이 패키지가 없으면 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_qpu 함수를 get_qvm 사용하여 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}")
    

Important

단일 작업에서 여러 회로 제출은 현재 지원되지 않습니다. 해결 방법으로, 메서드를 backend.run 호출하여 각 회로를 비동기적으로 제출한 다음, 각 작업의 결과를 가져올 수 있습니다. 예시:

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

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