Compartilhar via


Como enviar circuitos formatados específicos para o Azure Quantum

Saiba como usar o azure-quantumPython pacote para enviar circuitos em formatos específicos para o serviço do Azure Quantum. Este artigo mostra como enviar circuitos nos seguintes formatos:

Para mais informações, consulte Circuitos quânticos.

Pré-requisitos

Para executar seus circuitos em um Notebook no portal do Azure, você precisa:

  • Uma conta do Azure com uma assinatura ativa. Se você não tiver uma conta do Azure, registre-se gratuitamente e inscreva-se para uma assinatura paga conforme o uso.
  • Um workspace do Azure Quantum. Para obter mais informações, confira Criar um workspace do Azure Quantum.

Para desenvolver e executar seus circuitos no Visual Studio Code, você também precisa:

  • Um Python ambiente com Python e Pip instalado.

  • VS Code com o Kit de Desenvolvimento do Azure Quantum, Pythono , e as extensões Jupyter instaladas.

  • Os pacotes do Azure Quantum qsharp, azure-quantum, e .ipykernel

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

Criar um novo Jupyter Notebook

Você pode criar um notebook no VS Code ou diretamente no portal do Azure Quantum.

  1. Faça logon no portal do Azure e selecione o workspace da etapa anterior.
  2. No painel à esquerda, selecione Notebooks.
  3. Clique em Meus Notebooks e em Adicionar Novo.
  4. Em Tipo de Kernel, selecione IPython.
  5. Digite um nome para o arquivo e clique em Criar arquivo.

Quando o novo Notebook é aberto, ele cria automaticamente o código para a primeira célula com base na sua assinatura e nas informações do workspace.

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

Enviar circuitos formatados em QIR

A QIR (Representação Intermediária Quântica) é uma representação intermediária que serve como uma interface comum entre linguagens/estruturas de programação quântica e plataformas de computação quântica de destino. Para obter mais informações, veja Representação intermediária quântica.

  1. Crie o circuito QIR. Por exemplo, o código a seguir cria um circuito de emaranhamento simples.

    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. Crie uma submit_qir_job função auxiliar para enviar o circuito QIR para um target. Observe que os formatos de dados de entrada e saída são especificados como qir.v1 e microsoft.quantum-results.v1, respectivamente.

    # 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. Selecione a target e envie o circuito QIR para o Azure Quantum. Por exemplo, para enviar o circuito QIR para o simulador 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]}
    

Enviar um circuito com um formato específico do provedor para o Azure Quantum

Além das linguagens QIR, como Q# ou Qiskit, você pode enviar circuitos quânticos em formatos específicos do provedor para o Azure Quantum. Cada provedor tem seu próprio formato para representar circuitos quânticos.

Enviar um circuito para o IonQ usando o formato JSON

  1. Crie um circuito quântico usando o formato JSON independente de linguagem compatível com o IonQtargets, conforme descrito na documentação da API do IonQ. Veja o exemplo a seguir que cria uma superposição entre três qubits:

    circuit = {
        "qubits": 3,
        "circuit": [
            {
            "gate": "h",
            "target": 0
            },
            {
            "gate": "cnot",
            "control": 0,
            "target": 1
            },
            {
            "gate": "cnot",
            "control": 0,
            "target": 2
            },
        ]
    }
    
  2. Envie o circuito para o IonQ target. O exemplo a seguir usa o simulador do IonQ, que retorna um objeto Job.

    target = workspace.get_targets(name="ionq.simulator")
    job = target.submit(circuit)
    
  3. Aguarde até que o trabalho seja concluído e busque os resultados.

    results = job.get_results()
    print(results)
    
    .....
    {'duration': 8240356, 'histogram': {'0': 0.5, '7': 0.5}}
    
  4. Em seguida, você pode visualizar os resultados usando a 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")
    

    Saída do trabalho do IonQ

  5. Antes de executar um trabalho na QPU, você deve estimar quanto custará para ser executado.

    Observação

    Para obter os detalhes de preços mais atuais, consulte Preços do IonQ ou localize seu workspace e exiba as opções de preço na guia "Provedor" do workspace por meio de: aka.ms/aq/myworkspaces.

Envie um circuito para o PASQAL usando o Pulser SDK

Para enviar um circuito para o PASQAL, você pode usar o SDK do Pulser para criar sequências de pulso e enviá-las ao PASQAL target.

Instalar o SDK do Pulsser

Pulser é uma estrutura para compor, simular e executar sequências de pulso para dispositivos quânticos de átomos neutros. Ele foi projetado pela PASQAL como uma passagem para enviar experimentos quânticos para seus processadores quânticos. Para obter mais informações, consulte a documentação do Pulser.

Para enviar as sequências de pulso, primeiro instale os pacotes do SDK do Pulser:

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

Criar um registro quântico

Você precisa definir um registro e um layout antes de continuar. O registrador especifica onde os átomos serão organizados, enquanto o layout especifica o posicionamento das armadilhas necessárias para capturar e estruturar esses átomos dentro do registrador.

Para obter detalhes sobre layouts, consulte a documentação do Pulser.

  • Primeiro, você cria um objeto 'dispositivos' para importar o computador targetquântico PASQAL , Fresnel.

    from pulser_pasqal import PasqalCloud
    
    devices = PasqalCloud().fetch_available_devices()
    QPU = devices["FRESNEL"]
    
Layouts pré-calibrados

O dispositivo define uma lista de layouts pré-calibrados. Você pode criar seu registro a partir de um desses layouts.

Essa é a opção recomendada porque melhorará o desempenho da QPU.

  • Opção 1: Defina seu registro usando layouts pré-calibrados

    Inspecione os layouts disponíveis no Fresnel e defina seu registro a partir deste layout. Verifique a documentação do pulsador para obter mais informações sobre como fazer isso.

    Exemplo:

    # 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()
    
Layouts arbitrários

Se os layouts pré-calibrados não atenderem aos requisitos do seu experimento, você poderá criar um layout personalizado.

Para qualquer registro arbitrário, uma QPU de átomo neutro colocará armadilhas de acordo com o layout, que devem então passar por calibração. Como cada calibração requer tempo, geralmente é aconselhável reutilizar um layout calibrado existente sempre que possível

  • Opção 2: derivar automaticamente um layout do seu registro definido

    Esta opção permite a geração automática de um layout com base em um registro especificado. No entanto, para registros grandes, esse processo pode produzir soluções abaixo do ideal devido a limitações no algoritmo usado para criar o layout.

    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) 
    
  • Opção 3: Defina seu registro usando um layout definido manualmente

    • Crie um layout arbitrário com 20 armadilhas posicionadas aleatoriamente em um plano 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")
    
    • Defina seu registro com IDs de interceptação específicas
    trap_ids = [4, 8, 19, 0]
    reg = layout.define_register(*trap_ids, qubit_ids=["a", "b", "c", "d"])
    reg.draw()
    

Escreva uma sequência de pulso

Os átomos neutros são controlados com pulsos de laser. O SDK do Pulsser permite que você crie sequências de pulso para aplicar ao registro quântico.

  1. Primeiro, você define os atributos da sequência de pulsos declarando os canais que serão usados para controlar os átomos. Para criar um Sequence, você precisa fornecer uma Register instância junto com o dispositivo em que a sequência será executada. Por exemplo, o código a seguir declara um canal: ch0.

    Observação

    Você pode usar o QPU = devices["FRESNEL"] dispositivo ou importar um dispositivo virtual do Pulser para obter mais flexibilidade. O uso de a VirtualDevice permite a criação de sequências menos restritas pelas especificações do dispositivo, tornando-o adequado para execução em um emulador. Para obter mais informações, consulte a documentação do 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. Adicione pulsos à sua sequência. Para fazer isso, você cria e adiciona pulsos aos canais declarados. Por exemplo, o código a seguir cria um pulso e o adiciona ao canal 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()
    

    A imagem a seguir mostra a sequência de pulsos. Sequência de pulso

Converter a sequência em uma string JSON

Para enviar as sequências de pulso, você precisa converter os objetos Pulser em uma string JSON que pode ser usada como dados de entrada.

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

Envie a sequência de pulso para o PASQAL target

  1. Primeiro, você precisa definir os formatos de dados de entrada e saída adequados. Por exemplo, o código a seguir define o formato de dados de entrada e pasqal.pulser.v1 o formato de dados de saída como 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
    

    Observação

    O tempo necessário para executar um trabalho na QPU depende dos tempos de fila atuais. Você pode exibir o tempo médio de fila para um target selecionando a folha Provedores do seu workspace.

  2. Envie o programa para o PASQAL. Antes de enviar seu código para um hardware quântico real, você pode testá-lo usando o emulador pasqal.sim.emu-tn como um 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
    }
    

Enviar um circuito para o Quantinuum usando o OpenQASM

  1. Crie um circuito quântico na representação OpenQASM. Veja o exemplo abaixo que cria um circuito de Teletransporte:

    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];
    """
    

    Opcionalmente, você pode carregar o circuito de um arquivo:

    with open("my_teleport.qasm", "r") as f:
        circuit = f.read()
    
  2. Envie o circuito para o Quantinuum target. O exemplo a seguir usa o validador de API do Quantinuum, que retorna um objeto Job.

    target = workspace.get_targets(name="quantinuum.sim.h1-1sc")
    job = target.submit(circuit, shots=500)
    
  3. Aguarde até que o trabalho seja concluído e busque os resultados.

    results = job.get_results()
    print(results)
    
    ........
    {'c0': ['000',
    '000',
    '000',
    '000',
    '000',
    '000',
    '000',
    ...
    ]}
    
  4. Em seguida, você pode visualizar os resultados usando a Matplotlib.

    import pylab as pl
    pl.hist(results["c0"])
    pl.ylabel("Counts")
    pl.xlabel("Bitstring")
    

    Saída de trabalho do Quantinuum

    Observando o histograma, você pode notar que o gerador de número aleatório retornou 0 todas as vezes, o que não é muito aleatório. Isso acontece, pois enquanto o validador de API garante que o código será executado com êxito no hardware Quantinuum, ele também retorna 0 para cada medida quântica. Para um gerador de número aleatório verdadeiro, você precisa executar o circuito no hardware quântico.

  5. Antes de executar um trabalho na QPU, você deve estimar quanto custará para ser executado.

    Observação

    Para obter os detalhes de preços mais atuais, consulte Preços do Azure Quantum ou localize seu workspace e exiba as opções de preços na guia "Provedor" do workspace por meio de: aka.ms/aq/myworkspaces.

Enviar um circuito para Rigetti usando Quil

A maneira mais fácil de enviar trabalhos Quil é usando o pacote pyquil-for-azure-quantum , pois ele permite que você use as ferramentas e a documentação da biblioteca pyQuil . Sem esse pacote, o pyQuil pode ser usado para construir programas Quil, mas não para enviá-los ao Azure Quantum.

Você também pode construir programas Quil manualmente e enviá-los usando diretamente o pacote azure-quantum.

  1. Primeiro, carregue as importações necessárias.

    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. Use a get_qvm função ou get_qpu para obter uma conexão com a QVM ou QPU.

    qc = get_qvm()  # For simulation
    # qc = get_qpu("Ankaa-9Q-3") for submitting to a QPU
    
  3. Crie um programa Quil. Qualquer programa Quil válido é aceito, mas a leitura deve ser nomeada 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 Aqui está uma numpy matriz, para que você possa usar numpy métodos.

    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. Imprima todos os dados.

    print("Data from 'ro' register:")
    for i, shot in enumerate(data_per_shot):
        print(f"Shot {i}: {shot}")
    

Importante

Atualmente, não há suporte para o envio de vários circuitos em apenas um trabalho. Como solução alternativa, você pode chamar o método backend.run para enviar cada circuito de modo assíncrono e buscar os resultados de cada trabalho. Por exemplo:

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

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