Feedforward clássico e fluxo de controle
Versões dos pacotes
O código nesta página foi desenvolvido usando os seguintes requisitos. Recomendamos usar estas versões ou mais recentes.
qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
A nova versão dos circuitos dinâmicos está agora disponível para todos os usuários em todos os backends. Você pode executar circuitos dinâmicos em escala de utilidade. Veja o anúncio para mais detalhes.
Os circuitos dinâmicos são ferramentas poderosas com as quais você pode medir qubits no meio da execução de um Circuit quântico e, em seguida, realizar operações de lógica clássica dentro do circuito, com base nos resultados dessas medições intermediárias. Esse processo também é conhecido como feedforward clássico. Embora ainda seja cedo para entender como melhor aproveitar os circuitos dinâmicos, a comunidade de pesquisa quântica já identificou vários casos de uso, como os seguintes:
- Preparação eficiente de estados quânticos, como o estado GHZ, estado W, (para mais informações sobre o estado W, consulte também "State preparation by shallow circuits using feed forward") e uma ampla classe de estados de produto matricial
- Emaranhamento eficiente de longo alcance entre qubits no mesmo chip usando circuitos rasos
- Amostragem eficiente de circuitos similares a IQP
Essas melhorias trazidas pelos circuitos dinâmicos, no entanto, vêm com compensações. As medições intermediárias e as operações clássicas normalmente têm um tempo de execução maior do que as gates de dois qubits, e esse aumento no tempo pode anular os benefícios da redução da profundidade do circuito. Portanto, reduzir a duração das medições intermediárias é uma área de foco de melhoria à medida que a IBM Quantum® lança a nova versão dos circuitos dinâmicos.
A especificação OpenQASM 3 define diversas estruturas de fluxo de controle, mas o Qiskit Runtime atualmente suporta apenas a instrução condicional if. No Qiskit SDK, isso corresponde ao método if_test no QuantumCircuit. Esse método retorna um gerenciador de contexto e normalmente é usado em uma instrução with. Este guia descreve como usar essa instrução condicional.
Os exemplos de código neste guia usam a instrução de medição padrão para medições intermediárias. No entanto, recomenda-se usar a instrução MidCircuitMeasure em vez disso, se o backend oferecer suporte. Consulte a documentação de medições intermediárias para mais detalhes.
Instrução if
A instrução if é usada para realizar operações condicionalmente com base no valor de um bit ou registrador clássico.
No exemplo abaixo, aplicamos uma gate Hadamard a um qubit e o medimos. Se o resultado for 1, aplicamos uma gate X no qubit, que tem o efeito de revertê-lo para o estado 0. Em seguida, medimos o qubit novamente. O resultado da medição deve ser 0 com 100% de probabilidade.
# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-ibm-runtime
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
qubits = QuantumRegister(1)
clbits = ClassicalRegister(1)
circuit = QuantumCircuit(qubits, clbits)
(q0,) = qubits
(c0,) = clbits
circuit.h(q0)
# Use MidCircuitMeasure() if it's supported by the backend.
# circuit.append(MidCircuitMeasure(), [q0], [c0])
circuit.measure(q0, c0)
with circuit.if_test((c0, 1)):
circuit.x(q0)
circuit.measure(q0, c0)
circuit.draw("mpl")
# example output counts: {'0': 1024}
A instrução with pode receber um alvo de atribuição que é, por si só, um gerenciador de contexto que pode ser armazenado e posteriormente usado para criar um bloco else, que é executado sempre que o conteúdo do bloco if não é executado.
No exemplo abaixo, inicializamos registradores com dois qubits e dois bits clássicos. Aplicamos uma gate Hadamard ao primeiro qubit e o medimos. Se o resultado for 1, aplicamos uma gate Hadamard no segundo qubit; caso contrário, aplicamos uma gate X no segundo qubit. Por fim, medimos o segundo qubit também.
qubits = QuantumRegister(2)
clbits = ClassicalRegister(2)
circuit = QuantumCircuit(qubits, clbits)
(q0, q1) = qubits
(c0, c1) = clbits
circuit.h(q0)
circuit.measure(q0, c0)
with circuit.if_test((c0, 1)) as else_:
circuit.h(q1)
with else_:
circuit.x(q1)
circuit.measure(q1, c1)
circuit.draw("mpl")
# example output counts: {'01': 260, '11': 272, '10': 492}
Além de condicionar em um único bit clássico, também é possível condicionar no valor de um registrador clássico composto por múltiplos bits.
No exemplo abaixo, aplicamos gates Hadamard a dois qubits e os medimos. Se o resultado for 01, ou seja, o primeiro qubit é 1 e o segundo qubit é 0, então aplicamos uma gate X a um terceiro qubit. Por fim, medimos o terceiro qubit. Observe que, para maior clareza, optamos por especificar o estado do terceiro bit clássico, que é 0, na condição if. No desenho do circuito, a condição é indicada pelos círculos nos bits clássicos sobre os quais a condição é aplicada. Um círculo preto indica condicionamento em 1, enquanto um círculo branco indica condicionamento em 0.
qubits = QuantumRegister(3)
clbits = ClassicalRegister(3)
circuit = QuantumCircuit(qubits, clbits)
(q0, q1, q2) = qubits
(c0, c1, c2) = clbits
circuit.h([q0, q1])
circuit.measure(q0, c0)
circuit.measure(q1, c1)
with circuit.if_test((clbits, 0b001)):
circuit.x(q2)
circuit.measure(q2, c2)
circuit.draw("mpl")
# example output counts: {'101': 269, '011': 260, '000': 252, '010': 243}
Expressões clássicas
O módulo de expressões clássicas do Qiskit qiskit.circuit.classical contém uma representação exploratória de operações em tempo de execução sobre valores clássicos durante a execução do circuito. Devido a limitações de hardware, apenas as condições QuantumCircuit.if_test() são atualmente suportadas.
O exemplo a seguir mostra que você pode usar o cálculo de paridade para criar um estado GHZ de n qubits usando circuitos dinâmicos. Primeiro, gere pares de Bell em qubits adjacentes. Em seguida, una esses pares usando uma camada de gates CNOT entre os pares. Você então mede o qubit alvo de todas as gates CNOT anteriores e reinicia cada qubit medido para o estado . Você aplica a cada site não medido para o qual a paridade de todos os bits precedentes é ímpar. Por fim, as gates CNOT são aplicadas aos qubits medidos para restabelecer o emaranhamento perdido na medição.
No cálculo de paridade, o primeiro elemento da expressão construída envolve elevar o objeto Python mr[0] a um nó Value (lift é usado para transformar objetos arbitrários em expressões clássicas). Isso não é necessário para mr[1] e o possível registrador clássico seguinte, pois eles são entradas para expr.bit_xor, e qualquer elevação necessária é feita automaticamente nesses casos. Tais expressões podem ser construídas em loops e outras estruturas.
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.circuit.classical import expr
num_qubits = 8
if num_qubits % 2 or num_qubits < 4:
raise ValueError("num_qubits must be an even integer ≥ 4")
meas_qubits = list(range(2, num_qubits, 2)) # qubits to measure and reset
qr = QuantumRegister(num_qubits, "qr")
mr = ClassicalRegister(len(meas_qubits), "m")
qc = QuantumCircuit(qr, mr)
# Create local Bell pairs
qc.reset(qr)
qc.h(qr[::2])
for ctrl in range(0, num_qubits, 2):
qc.cx(qr[ctrl], qr[ctrl + 1])
# Glue neighboring pairs
for ctrl in range(1, num_qubits - 1, 2):
qc.cx(qr[ctrl], qr[ctrl + 1])
# Measure boundary qubits between pairs,reset to 0
for k, q in enumerate(meas_qubits):
qc.measure(qr[q], mr[k])
qc.reset(qr[q])
# Parity-conditioned X corrections
# Each non-measured qubit gets flipped iff the parity (XOR) of all
# preceding measurement bits is 1
for tgt in range(num_qubits):
if tgt in meas_qubits: # skip measured qubits
continue
# all measurement registers whose physical qubit index < tgt
left_bits = [k for k, q in enumerate(meas_qubits) if q < tgt]
if not left_bits: # skip if list empty
continue
# build XOR-parity expression
parity = expr.lift(
mr[left_bits[0]]
) # lift the first bit to Value so it will be treated like a boolean.
for k in left_bits[1:]:
parity = expr.bit_xor(
mr[k], parity
) # calculate parity with all other bits
with qc.if_test(parity): # Add X if parity is 1
qc.x(qr[tgt])
# Re-entangle measured qubits
for ctrl in range(1, num_qubits - 1, 2):
qc.cx(qr[ctrl], qr[ctrl + 1])
qc.draw(output="mpl", style="iqp", idle_wires=False, fold=-1)
Encontrar backends que suportam circuitos dinâmicos
Para encontrar todos os backends que sua conta pode acessar e que suportam circuitos dinâmicos, execute um código como o seguinte. Este exemplo assume que você salvou suas credenciais de login. Você também pode especificar credenciais explicitamente ao inicializar sua conta de serviço do Qiskit Runtime. Isso permitiria, por exemplo, visualizar os backends disponíveis em uma instância ou tipo de plano específico.
- Os backends disponíveis para a conta dependem da instância especificada nas credenciais.
- A nova versão dos circuitos dinâmicos está agora disponível para todos os usuários em todos os backends. Veja o anúncio para mais detalhes.
from qiskit_ibm_runtime import QiskitRuntimeService
service = QiskitRuntimeService()
dc_backends = service.backends(dynamic_circuits=True)
print(dc_backends)
[<IBMBackend('ibm_pittsburgh')>, <IBMBackend('ibm_boston')>, <IBMBackend('ibm_fez')>, <IBMBackend('ibm_miami')>, <IBMBackend('ibm_marrakesh')>, <IBMBackend('ibm_torino')>, <IBMBackend('ibm_kingston')>]
Limitações do Qiskit Runtime
Esteja ciente das seguintes restrições ao executar circuitos dinâmicos no Qiskit Runtime.
-
Devido à memória física limitada na eletrônica de controle, há também um limite no número de instruções
ife no tamanho de seus operandos. Esse limite é uma função do número de transmissões (broadcasts) e do número de bits transmitidos em um job (não em um circuito).Ao processar uma condição
if, os dados de medição precisam ser transferidos para a lógica de controle para realizar essa avaliação. Uma transmissão é uma transferência de dados clássicos únicos, e bits transmitidos é o número de bits clássicos sendo transferidos. Considere o seguinte:c0 = ClassicalRegister(3)
c1 = ClassicalRegister(5)
...
with circuit.if_test((c0, 1)) ...
with circuit.if_test((c0, 3)) ...
with circuit.if_test((c1[2], 1)) ...No exemplo de código anterior, os dois primeiros objetos
if_testemc0são considerados uma única transmissão porque o conteúdo dec0não mudou e, portanto, não precisa ser retransmitido. Oif_testemc1é uma segunda transmissão. A primeira transmite todos os três bits dec0e a segunda transmite apenas um bit, totalizando quatro bits transmitidos.Atualmente, se você transmitir 60 bits por vez, o job pode ter aproximadamente 300 transmissões. Se você transmitir apenas um bit por vez, no entanto, o job pode ter 2400 transmissões.
-
O operando usado em uma instrução
if_testdeve ter 32 bits ou menos. Portanto, se você estiver comparando umClassicalRegisterinteiro, o tamanho desseClassicalRegisterdeve ser de 32 bits ou menos. Se você estiver comparando apenas um único bit de umClassicalRegister, no entanto, esseClassicalRegisterpode ter qualquer tamanho (já que o operando é apenas um bit).Por exemplo, o bloco de código "Não válido" não funciona porque
crtem mais de 32 bits. Você pode, no entanto, usar um registrador clássico com mais de 32 bits se estiver testando apenas um bit, como mostrado no bloco de código "Válido".- Não válido
- Válido
cr = ClassicalRegister(50)
qr = QuantumRegister(50)
circuit = QuantumCircuit(qr, cr)
...
circ.measure(qr, cr)
with circ.if_test((cr, 15)):
...cr = ClassicalRegister(50)
qr = QuantumRegister(50)
circuit = QuantumCircuit(qr, cr)
...
circ.measure(qr, cr)
with circ.if_test((cr[5], 1)):
... -
Condicionais aninhados não são permitidos. Por exemplo, o bloco de código a seguir não funcionará porque tem um
if_testdentro de outroif_test:- Não válido
- Válido
c1 = ClassicalRegister(1, "c1")
c2 = ClassicalRegister(2, "c2")
...
with circ.if_test((c1, 1)):
with circ.if_test(c2, 1)):
...cr = ClassicalRegister(2)
...
with circuit.if_test((cr, 0b11)):
... -
Ter
resetou medições dentro de condicionais não é suportado. -
Operações aritméticas não são suportadas.
-
Consulte a tabela de recursos do OpenQASM 3 para determinar quais recursos do OpenQASM 3 são suportados no Qiskit e no Qiskit Runtime.
-
Quando o OpenQASM 3 (em vez de
QuantumCircuit) é usado como formato de entrada para passar circuitos para as primitivas do Qiskit Runtime, apenas as instruções que podem ser carregadas no Qiskit são suportadas. Operações clássicas, por exemplo, não são suportadas porque não podem ser carregadas no Qiskit. Consulte Importar um programa OpenQASM 3 para o Qiskit para mais informações. -
As instruções
for,whileeswitchnão são suportadas.
Usar circuitos dinâmicos com o Estimator
Como o Estimator não suporta circuitos dinâmicos, você pode usar o Sampler e construir seus próprios circuitos de medição. Alternativamente, você pode usar a primitiva Executor, que suporta circuitos dinâmicos.
Para replicar o comportamento do Estimator, siga este processo:
- Agrupe os termos de todos os observáveis em uma partição. Isso pode ser feito usando a API
PauliList, por exemplo.notaVocê pode usar o atributo primitivo
BitArraypara calcular os valores esperados dos observáveis fornecidos. - Execute um circuito de mudança de base por partição (qualquer mudança de base que precise ser feita para cada partição). Consulte o módulo utilitário de bases de medição
measurement_basesmodule para mais informações. Comece a usar os utilitários. - Some novamente os resultados de cada partição.
Próximas etapas
- Aprenda a implementar desacoplamento dinâmico preciso usando stretch.
- Saiba mais sobre as medições intermediárias mais curtas que reduzem o tempo do circuito.
- Use a visualização do cronograma do circuito para depurar e otimizar seus circuitos dinâmicos.