Simulação exata com as primitivas do Qiskit SDK
Versões dos pacotes
O código nesta página foi desenvolvido usando os seguintes requisitos. Recomendamos usar essas versões ou versões mais recentes.
qiskit[all]~=2.3.0
As primitivas de referência do Qiskit SDK realizam simulações locais de vetor de estado. Essas simulações não suportam a modelagem de ruído de dispositivos, mas são úteis para prototipagem rápida de algoritmos antes de explorar técnicas de simulação mais avançadas (usando o Qiskit Aer) ou executar em dispositivos reais (primitivas do Qiskit Runtime).
A primitiva Estimator pode calcular valores esperados de circuitos, e a primitiva Sampler pode amostrar das distribuições de saída de circuitos.
As seções a seguir mostram como usar as primitivas de referência para executar seu fluxo de trabalho localmente.
Use o Estimator de referência
A implementação de referência do EstimatorV2 em qiskit.primitives que roda em simuladores locais de vetor de estado
é a classe StatevectorEstimator. Ela aceita circuitos, observáveis e parâmetros como entradas e retorna os valores esperados calculados localmente.
O código a seguir prepara as entradas que serão usadas nos exemplos posteriores. O tipo de entrada esperado para os
observáveis é qiskit.quantum_info.SparsePauliOp. Note que
o circuito do exemplo é parametrizado, mas você também pode executar o Estimator em circuitos não parametrizados.
Qualquer circuito passado para um Estimator não deve incluir nenhuma medição.
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit
from qiskit import QuantumCircuit
from qiskit.circuit import Parameter
# circuit for which you want to obtain the expected value
circuit = QuantumCircuit(2)
circuit.ry(Parameter("theta"), 0)
circuit.h(0)
circuit.cx(0, 1)
circuit.draw("mpl", style="iqp")
from qiskit.quantum_info import SparsePauliOp
import numpy as np
# observable(s) whose expected values you want to compute
observable = SparsePauliOp(["II", "XX", "YY", "ZZ"], coeffs=[1, 1, -1, 1])
# value(s) for the circuit parameter(s)
parameter_values = [[0], [np.pi / 6], [np.pi / 2]]
O fluxo de trabalho das primitivas do Qiskit Runtime exige que circuitos e observáveis sejam transformados para usar apenas instruções suportadas pelo QPU (denominados circuitos e observáveis de arquitetura de conjunto de instruções (ISA)). As primitivas de referência ainda aceitam instruções abstratas, pois dependem de simulações locais de vetor de estado, mas transpilar o circuito ainda pode ser benéfico em termos de otimização.
# Generate a pass manager without providing a backend
from qiskit.transpiler import generate_preset_pass_manager
pm = generate_preset_pass_manager(optimization_level=1)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
Inicializar o Estimator
Instancie um qiskit.primitives.StatevectorEstimator.
from qiskit.primitives import StatevectorEstimator
estimator = StatevectorEstimator()
Executar e obter resultados
Este exemplo usa apenas um circuito (do tipo QuantumCircuit) e um
observável.
Execute a estimativa chamando o método StatevectorEstimator.run, que retorna uma instância de um objeto PrimitiveJob. Você pode obter os resultados do job (como um objeto qiskit.primitives.PrimitiveResult)
com o método qiskit.primitives.PrimitiveJob.result.
job = estimator.run([(circuit, observable, parameter_values)])
result = job.result()
print(f" > Result class: {type(result)}")
> Result class: <class 'qiskit.primitives.containers.primitive_result.PrimitiveResult'>
Obter o valor esperado do resultado
Os resultados das primitivas retornam um array de objetos PubResult, onde cada item do array é um objeto PubResult que contém em seus dados o array de avaliações correspondente a cada combinação circuito-observável no PUB.
Para recuperar os valores esperados e os metadados da primeira (e, neste caso, única) avaliação do circuito, precisamos acessar os data de avaliação do PUB 0:
print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")
> Expectation value: [4. 3.73205081 2. ]
> Metadata: {'target_precision': 0.0, 'circuit_metadata': {}}
Definir opções de execução do Estimator
Por padrão, o Estimator de referência realiza um cálculo exato de vetor de estado baseado na
classe quantum_info.Statevector.
No entanto, isso pode ser modificado para introduzir o efeito da sobrecarga de amostragem (também conhecida como "ruído de shot").
O Estimator aceita um argumento precision que expressa as barras de erro que a
implementação da primitiva deve almejar para estimativas de valores esperados. Isso é a sobrecarga de amostragem e é definida exclusivamente no método .run(). Isso permite ajustar a opção até o nível do PUB.
# Estimate expectation values for two PUBs, both with 0.05 precision.
precise_job = estimator.run(
[(circuit, observable, parameter_values)], precision=0.05
)
Para um exemplo completo, consulte a página de exemplos de Primitivas.
Use o Sampler de referência
A implementação de referência do SamplerV2 em qiskit.primitives é a classe StatevectorSampler. Ela aceita circuitos e parâmetros como entradas e retorna os resultados da amostragem das distribuições de probabilidade de saída como uma distribuição quasi-probabilística dos estados de saída.
O código a seguir prepara as entradas usadas nos exemplos posteriores. Note que esses exemplos executam um único circuito parametrizado, mas você também pode executar o Sampler em circuitos não parametrizados.
from qiskit import QuantumCircuit
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
circuit.draw("mpl", style="iqp")
Qualquer circuito quântico passado para um Sampler deve incluir medições.
O fluxo de trabalho das primitivas do Qiskit Runtime exige que os circuitos sejam transformados para usar apenas instruções suportadas pelo QPU (denominados circuitos ISA). As primitivas de referência ainda aceitam instruções abstratas, pois dependem de simulações locais de vetor de estado, mas transpilar o circuito ainda pode ser benéfico em termos de otimização.
# Generate a pass manager without providing a backend
from qiskit.transpiler import generate_preset_pass_manager
pm = generate_preset_pass_manager(optimization_level=1)
isa_circuit = pm.run(qc)
Inicializar o SamplerV2
Instancie qiskit.primitives.StatevectorSampler:
from qiskit.primitives import StatevectorSampler
sampler = StatevectorSampler()
Executar e obter resultados
# execute 1 circuit with Sampler
job = sampler.run([circuit])
pub_result = job.result()[0]
print(f" > Result class: {type(pub_result)}")
> Result class: <class 'qiskit.primitives.containers.sampler_pub_result.SamplerPubResult'>
As primitivas aceitam múltiplos PUBs como entradas, e cada PUB recebe seu próprio resultado. Portanto, você pode executar circuitos diferentes com várias combinações de parâmetros/observáveis e recuperar os resultados dos PUBs:
from qiskit.transpiler import generate_preset_pass_manager
# create two circuits
circuit1 = circuit.copy()
circuit2 = circuit.copy()
# transpile circuits
pm = generate_preset_pass_manager(optimization_level=1)
isa_circuit1 = pm.run(circuit1)
isa_circuit2 = pm.run(circuit2)
# execute 2 circuits using Sampler
job = sampler.run([(isa_circuit1), (isa_circuit2)])
pub_result_1 = job.result()[0]
pub_result_2 = job.result()[1]
print(f" > Result class: {type(pub_result)}")
> Result class: <class 'qiskit.primitives.containers.sampler_pub_result.SamplerPubResult'>
Obter a distribuição de probabilidade ou resultado de medição
Amostras de resultados de medição são retornadas como bitstrings ou counts. As bitstrings mostram os resultados das medições, preservando a ordem dos shots em que foram medidas. Os objetos de resultado do Sampler organizam os dados em termos dos nomes dos registradores clássicos dos circuitos de entrada, para compatibilidade com circuitos dinâmicos.
O nome do registrador clássico é "meas" por padrão. Esse nome será usado mais adiante para acessar as bitstrings de medição.
# Define quantum circuit with 2 qubits
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
circuit.draw()
┌───┐ ░ ┌─┐
q_0: ┤ H ├──■───░─┤M├───
└───┘┌─┴─┐ ░ └╥┘┌─┐
q_1: ─────┤ X ├─░──╫─┤M├
└───┘ ░ ║ └╥┘
meas: 2/══════════════╩══╩═
0 1
# Transpile circuit
pm = generate_preset_pass_manager(optimization_level=1)
isa_circuit = pm.run(circuit)
# Run using sampler
result = sampler.run([circuit]).result()
# Access result data for PUB 0
data_pub = result[0].data
# Access bitstring for the classical register "meas"
bitstrings = data_pub.meas.get_bitstrings()
print(f"The number of bitstrings is: {len(bitstrings)}")
# Get counts for the classical register "meas"
counts = data_pub.meas.get_counts()
print(f"The counts are: {counts}")
The number of bitstrings is: 1024
The counts are: {'11': 515, '00': 509}
Alterar opções de execução
Por padrão, o Sampler de referência realiza um cálculo exato de vetor de estado baseado na
classe quantum_info.Statevector.
No entanto, isso pode ser modificado para introduzir o efeito da sobrecarga de amostragem (também conhecida como "ruído de shot"). Para ajudar a gerenciar essa sobrecarga, a interface do Sampler aceita um argumento shots que pode ser definido no nível do PUB.
Este exemplo assume que você definiu dois circuitos.
# Sample two circuits at 128 shots each.
sampler.run([isa_circuit1, isa_circuit2], shots=128)
# Sample two circuits at different amounts of shots. The "None"s are necessary
# as placeholders
# for the lack of parameter values in this example.
sampler.run([(isa_circuit1, None, 123), (isa_circuit2, None, 456)])
<qiskit.primitives.primitive_job.PrimitiveJob at 0x7fa430e39dd0>
Para um exemplo completo, consulte a página de exemplos de Primitivas.
Próximos passos
- Para simulação de maior desempenho que suporte circuitos maiores ou para incorporar modelos de ruído à sua simulação, consulte Simulação exata e com ruído usando primitivas do Qiskit Aer.
- Para aprender como usar o Quantum Composer para simulação, consulte o guia IBM Quantum Composer.
- Leia a referência da API do Qiskit Estimator.
- Leia a referência da API do Qiskit Sampler.
- Leia Migrar para primitivas V2.