Pular para o conteúdo principal

Introdução às primitivas

Versões dos pacotes

O código nesta página foi desenvolvido com os seguintes requisitos. Recomendamos usar essas versões ou versões mais recentes.

qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
Novo modelo de execução, agora em versão beta

A versão beta de um novo modelo de execução está disponível. O modelo de execução direcionada oferece mais flexibilidade para personalizar seu fluxo de mitigação de erros. Consulte o guia Modelo de execução direcionada para mais informações.

Por que o Qiskit introduziu as primitivas?

De modo similar aos primórdios dos computadores clássicos, quando os desenvolvedores precisavam manipular registradores de CPU diretamente, a interface inicial com QPUs simplesmente retornava os dados brutos da eletrônica de controle. Isso não era um grande problema quando as QPUs ficavam em laboratórios e só permitiam acesso direto por pesquisadores. Reconhecendo que a maioria dos desenvolvedores não precisaria nem deveria se familiarizar com a conversão desses dados brutos em 0s e 1s, o Qiskit introduziu o backend.run, uma primeira abstração para acessar QPUs na nuvem. Isso permitiu que os desenvolvedores operassem em um formato de dados familiar e se concentrassem no panorama geral.

À medida que o acesso às QPUs se tornou mais difundido, e com mais algoritmos quânticos sendo desenvolvidos, surgiu novamente a necessidade de uma abstração de nível mais alto. Em resposta, o Qiskit introduziu a interface de primitivas, que é otimizada para duas tarefas centrais no desenvolvimento de algoritmos quânticos: estimativa de valor esperado (Estimator) e amostragem de circuitos (Sampler). O objetivo é, mais uma vez, ajudar os desenvolvedores a se concentrarem mais na inovação e menos na conversão de dados. A interface de primitivas substitui a interface backend.run, já que o Sampler oferece o mesmo acesso direto ao hardware que era fornecido pelo backend.run.

O que é uma primitiva?

Os sistemas computacionais são construídos em múltiplas camadas de abstração. As abstrações permitem que você se concentre em um nível específico de detalhe relevante para a tarefa em questão. Quanto mais próximo você está do hardware, menor é o nível de abstração necessário (por exemplo, talvez você precise mover ou manipular dados no nível de instrução da CPU). Quanto mais complexa a tarefa que você deseja realizar, mais alto será o nível das abstrações (por exemplo, você pode estar usando uma biblioteca de programação para realizar cálculos algébricos).

Nesse contexto, uma primitiva é a menor instrução de processamento, o bloco de construção mais simples a partir do qual é possível criar algo útil para um dado nível de abstração.

O progresso recente na computação quântica aumentou a necessidade de trabalhar em níveis mais altos de abstração. À medida que o campo avança em direção a unidades de processamento quântico (QPUs) maiores e fluxos de trabalho mais complexos, o foco muda de interagir com sinais de qubits individuais para encarar os dispositivos quânticos como sistemas que executam as tarefas necessárias.

As duas tarefas mais comuns para computadores quânticos são a amostragem de estados quânticos e o cálculo de valores esperados. Essas tarefas motivaram o design das primitivas do Qiskit: Estimator e Sampler.

  • O Estimator calcula os valores esperados de observáveis em relação a estados preparados por circuitos quânticos.
  • O Sampler amostra o registrador de saída da execução de circuitos quânticos.

Em resumo, o modelo computacional introduzido pelas primitivas do Qiskit aproxima a programação quântica um passo a mais de onde a programação clássica está hoje, onde o foco está menos nos detalhes do hardware e mais nos resultados que você está tentando alcançar.

Definição e implementações das primitivas

Existem dois tipos de primitivas Qiskit: as classes base e suas implementações. As primitivas do Qiskit são definidas por classes base de primitivas de código aberto que residem no SDK do Qiskit (no módulo qiskit.primitives). Provedores (como o Qiskit Runtime) podem usar essas classes base para derivar suas próprias implementações de Sampler e Estimator. A maioria dos usuários interagirá com as implementações dos provedores, não com as primitivas base.

Classes base

BaseEstimatorV2 e BaseSamplerV2 — Classes base abstratas que definem uma interface comum para implementar primitivas. Todas as outras classes no módulo qiskit.primitives herdam dessas classes base. Os desenvolvedores devem usá-las se tiverem interesse em criar seu próprio modelo de execução baseado em primitivas para um provedor específico. Essas classes também podem ser úteis para quem deseja fazer um processamento altamente personalizado e descobre que as implementações de primitivas existentes são simples demais para suas necessidades. Usuários em geral não utilizarão as classes base diretamente.

Implementações

Estas são implementações das classes base de primitivas:

  • As primitivas do Qiskit Runtime (EstimatorV2 e SamplerV2) oferecem uma implementação mais sofisticada (por exemplo, incluindo mitigação de erros) como um serviço baseado em nuvem. Essa implementação das primitivas base é usada para acessar o hardware do IBM Quantum®. Elas são acessadas por meio do IBM Qiskit Runtime.

  • StatevectorEstimator e StatevectorSampler — Implementações de referência das primitivas que usam o simulador embutido no Qiskit. Elas são construídas com o módulo quantum_info do Qiskit, produzindo resultados baseados em simulações ideais de vetor de estado. São acessadas por meio do Qiskit.

  • BackendEstimatorV2 e BackendSamplerV2 — Você pode usar essas classes para "envolver" qualquer recurso de computação quântica em uma primitiva. Isso permite escrever código no estilo de primitivas para provedores que ainda não possuem uma interface baseada em primitivas. Essas classes podem ser usadas exatamente como o Sampler e o Estimator regulares, exceto que devem ser inicializadas com um argumento adicional backend para selecionar em qual computador quântico executar. São acessadas usando o Qiskit.

Benefícios das primitivas do Qiskit

Com as primitivas, os usuários do Qiskit podem escrever código quântico para uma QPU específica sem precisar gerenciar explicitamente cada detalhe. Além disso, por causa da camada adicional de abstração, você pode ser capaz de acessar com mais facilidade as capacidades avançadas de hardware de um determinado provedor. Por exemplo, com as primitivas do Qiskit Runtime, você pode aproveitar os avanços mais recentes em mitigação e supressão de erros alternando opções como o resilience_level da primitiva, em vez de construir sua própria implementação dessas técnicas.

Para provedores de hardware, implementar primitivas nativamente significa que você pode oferecer aos seus usuários uma forma mais "pronta para uso" de acessar os recursos do seu hardware, como técnicas avançadas de pós-processamento. Portanto, fica mais fácil para seus usuários se beneficiarem das melhores capacidades do seu hardware.

Detalhes das primitivas

Como descrito anteriormente, todas as primitivas são criadas a partir das classes base; portanto, elas têm a mesma estrutura e uso gerais. Por exemplo, o formato de entrada para todas as primitivas do Estimator é o mesmo. No entanto, há diferenças nas implementações que as tornam únicas.

nota

Como a maioria dos usuários acessa as primitivas do Qiskit Runtime, os exemplos no restante desta seção são baseados nas primitivas do Qiskit Runtime.

Estimator

A primitiva Estimator calcula os valores esperados de um ou mais observáveis em relação a estados preparados por circuitos quânticos. Os circuitos podem ser parametrizados, desde que os valores dos parâmetros também sejam fornecidos como entrada para a primitiva.

A entrada é um array de PUBs. Cada PUB está no formato:

(<circuito único>, <um ou mais observáveis>, <valores de parâmetros opcionais>, <precisão opcional>),

onde os valores de parâmetros opcionais podem ser uma lista ou um único parâmetro. Diferentes implementações do Estimator suportam várias opções de configuração. Se a entrada contiver medições, elas serão ignoradas.

A saída é um PubResult que contém os valores esperados calculados por par, e seus erros padrão, na forma de PubResult. Cada PubResult contém tanto dados quanto metadados.

O Estimator combina elementos de observáveis e valores de parâmetros seguindo as regras de broadcasting do NumPy, conforme descrito no tópico Entradas e saídas de primitivas.

Exemplo:

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-ibm-runtime
# This cell is hidden from users, it creates the circuits and observables to run

from qiskit_ibm_runtime import EstimatorV2, SamplerV2, QiskitRuntimeService
from qiskit.circuit.random import random_circuit
from qiskit.circuit import Parameter
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager
import numpy as np

service = QiskitRuntimeService()
backend = service.least_busy()
phi = Parameter("phi")

circuit1 = random_circuit(10, 5, seed=12345)
circuit1.rzz(phi, 1, 2)
observable1 = SparsePauliOp.from_sparse_list(
[("ZXYZ", [1, 2, 3, 4], 1)], num_qubits=10
)
param_values1 = np.random.uniform(size=5).T

circuit2 = random_circuit(10, 5, seed=12345)
circuit2.rzz(phi, 1, 2)
observable2 = SparsePauliOp.from_sparse_list(
[("XZYX", [1, 2, 3, 4], 1)], num_qubits=10
)
param_values2 = np.random.uniform(size=5).T

shots1 = 164
shots2 = 1024

pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
circuit1 = pm.run(circuit1)
circuit2 = pm.run(circuit2)
observable1 = observable1.apply_layout(circuit1.layout)
observable2 = observable2.apply_layout(circuit2.layout)
estimator = EstimatorV2(mode=backend)
estimator_job = estimator.run(
[
(circuit1, observable1, param_values1),
(circuit2, observable2, param_values2),
]
)

Sampler

A tarefa central do Sampler é amostrar o registrador de saída da execução de um ou mais circuitos quânticos. Os circuitos de entrada podem ser parametrizados, desde que os valores dos parâmetros também sejam fornecidos como entrada para a primitiva.

A entrada é um ou mais PUBs, no formato:

(<circuito único>, <um ou mais valores de parâmetros opcionais>, <shots opcionais>),

onde pode haver múltiplos itens de valores de parâmetros, e cada item pode ser um array ou um único parâmetro, dependendo do circuito escolhido. Além disso, a entrada deve conter medições.

A saída são contagens ou medições por shot, como objetos PubResult, sem pesos. A classe de resultado, porém, possui métodos para retornar amostras ponderadas, como contagens. Consulte Entradas e saídas de primitivas para detalhes completos.

Exemplo:

# This cell is hidden from users, add measurement instructions to circuits
circuit1.measure_active()
circuit2.measure_active()
sampler = SamplerV2(mode=backend)
sampler_job = sampler.run(
[
(circuit1, param_values1, shots1),
(circuit2, param_values2, shots2),
]
)

Próximos passos

Recomendações