Pular para o conteúdo principal

Gate Cutting para Reduzir a Largura do Circuito

Neste notebook, percorreremos as etapas de um padrão Qiskit usando circuit cutting para reduzir o número de qubits em um circuito. Vamos cortar portas para nos permitir reconstruir o valor esperado de um circuito de quatro qubits usando apenas experimentos de dois qubits.

Estas são as etapas que vamos seguir:

  • Etapa 1: Mapear o problema para circuitos quânticos e operadores:
    • Mapear o hamiltoniano em um circuito quântico.
  • Etapa 2: Otimizar para o hardware alvo [Usa o cutting addon]:
    • Cortar o circuito e o observável.
    • Transpilar os subexperimentos para o hardware.
  • Etapa 3: Executar no hardware alvo:
    • Executar os subexperimentos obtidos na Etapa 2 usando uma primitiva Sampler.
  • Etapa 4: Pós-processar os resultados [Usa o cutting addon]:
    • Combinar os resultados da Etapa 3 para reconstruir o valor esperado do observável em questão.

Etapa 1: Mapear

Crie um circuito para cortar

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-cutting qiskit-aer qiskit-ibm-runtime
from qiskit.circuit.library import efficient_su2

qc = efficient_su2(4, entanglement="linear", reps=2)
qc.assign_parameters([0.4] * len(qc.parameters), inplace=True)

qc.draw("mpl", scale=0.8)

Diagrama de circuito quântico

Especifique um observável

from qiskit.quantum_info import SparsePauliOp

observable = SparsePauliOp(["ZZII", "IZZI", "-IIZZ", "XIXI", "ZIZZ", "IXIX"])

Etapa 2: Otimizar

Separe o circuito e o observável de acordo com um particionamento de qubits especificado

Cada rótulo em partition_labels corresponde ao qubit do circuit no mesmo índice. Qubits que compartilham um rótulo de partição comum serão agrupados, e portas não locais que abrangem mais de uma partição serão cortadas.

Nota: O kwarg observables de partition_problem é do tipo PauliList. Os coeficientes e fases dos termos do observável são ignorados durante a decomposição do problema e a execução dos subexperimentos. Eles podem ser reaplicados durante a reconstrução do valor esperado.

from qiskit_addon_cutting import partition_problem

partitioned_problem = partition_problem(
circuit=qc, partition_labels="AABB", observables=observable.paulis
)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
bases = partitioned_problem.bases

Visualize o problema decomposto

subobservables
{'A': PauliList(['II', 'ZI', 'ZZ', 'XI', 'ZZ', 'IX']),
'B': PauliList(['ZZ', 'IZ', 'II', 'XI', 'ZI', 'IX'])}
subcircuits["A"].draw("mpl", scale=0.8)

Diagrama de circuito quântico

subcircuits["B"].draw("mpl", scale=0.8)

Diagrama de circuito quântico

Calcule o sampling overhead para os cortes escolhidos

Aqui cortamos duas portas CNOT, resultando em um sampling overhead de 929^2.

Para mais detalhes sobre o sampling overhead incorrido pelo circuit cutting, consulte o material explicativo.

import numpy as np

print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
Sampling overhead: 81.0

Gere os subexperimentos para executar no backend

generate_cutting_experiments aceita os argumentos circuits/observables como dicionários que mapeiam rótulos de partição de qubits para os respectivos subcircuit/subobservables.

Para simular o valor esperado do circuito de tamanho completo, muitos subexperimentos são gerados a partir da distribuição de quasiprobabilidade conjunta das portas decompostas e então executados em um ou mais backends. O número de amostras retiradas da distribuição é controlado por num_samples, e um coeficiente combinado é dado para cada amostra única. Para mais informações sobre como os coeficientes são calculados, consulte o material explicativo.

from qiskit_addon_cutting import generate_cutting_experiments

subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits, observables=subobservables, num_samples=np.inf
)

Escolha um backend

Aqui estamos usando um backend simulado (fake), o que fará com que o Qiskit Runtime seja executado em modo local (ou seja, em um simulador local).

from qiskit_ibm_runtime.fake_provider import FakeManilaV2

backend = FakeManilaV2()

Prepare os subexperimentos para o backend

Devemos transpilar os circuitos com nosso backend como alvo antes de submetê-los ao Qiskit Runtime.

from qiskit.transpiler import generate_preset_pass_manager

# Transpile the subexperiments to ISA circuits
pass_manager = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_subexperiments = {
label: pass_manager.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}

Etapa 3: Executar

Execute os subexperimentos usando a primitiva Sampler do Qiskit Runtime

from qiskit_ibm_runtime import SamplerV2, Batch

# Submit each partition's subexperiments to the Qiskit Runtime Sampler
# primitive, in a single batch so that the jobs will run back-to-back.
with Batch(backend=backend) as batch:
sampler = SamplerV2(mode=batch)
jobs = {
label: sampler.run(subsystem_subexpts, shots=2**12)
for label, subsystem_subexpts in isa_subexperiments.items()
}
/home/garrison/Qiskit/qiskit-ibm-runtime/qiskit_ibm_runtime/session.py:157: UserWarning: Session is not supported in local testing mode or when using a simulator.
warnings.warn(
# Retrieve results
results = {label: job.result() for label, job in jobs.items()}

Etapa 4: Pós-processar

Reconstrua o valor esperado

Reconstrua os valores esperados para cada termo do observável e combine-os para reconstruir o valor esperado do observável original.

from qiskit_addon_cutting import reconstruct_expectation_values

# Get expectation values for each observable term
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)

# Reconstruct final expectation value
reconstructed_expval = np.dot(reconstructed_expval_terms, observable.coeffs)

Compare o valor esperado reconstruído com o valor esperado exato do circuito e observável originais

from qiskit_aer.primitives import EstimatorV2

estimator = EstimatorV2()
exact_expval = estimator.run([(qc, observable)]).result()[0].data.evs
print(f"Reconstructed expectation value: {np.real(np.round(reconstructed_expval, 8))}")
print(f"Exact expectation value: {np.round(exact_expval, 8)}")
print(f"Error in estimation: {np.real(np.round(reconstructed_expval-exact_expval, 8))}")
print(
f"Relative error in estimation: {np.real(np.round((reconstructed_expval-exact_expval) / exact_expval, 8))}"
)
Reconstructed expectation value: 0.6991539
Exact expectation value: 0.56254612
Error in estimation: 0.13660778
Relative error in estimation: 0.24283836