Skip to main content
IBM Quantum Platform

Corte de fios para estimativa de valores esperados

Estimativa de tempo de execução: 22 segundos em um processador Heron (NOTA: Trata-se apenas de uma estimativa. (O tempo de execução pode variar.)


Resultados do aprendizado

Após concluir este tutorial, os usuários deverão compreender:

  • Como usar qiskit-addon-cutting para dividir um circuito grande em subcircuitos menores, reduzindo assim o efeito do ruído

Pré-requisitos

Recomendamos que os usuários estejam familiarizados com o seguinte tópico antes de seguir com este tutorial:

  • Utilizando a primitiva Sampler, que é usada neste fluxo de trabalho

Segundo plano

A “tricotagem de circuitos” é um termo genérico que abrange vários métodos de divisão de um circuito em vários subcircuitos menores, envolvendo menos portas lógicas ou qubits. Cada um dos subcircuitos pode ser executado de forma independente, e o resultado final é obtido por meio de um pós-processamento clássico aplicado aos resultados de cada subcircuito. Essa técnica está disponível no complemento Circuit cutting do Qiskit; consulte a documentação e outros materiais introdutórios para obter uma explicação detalhada sobre a técnica.

Este tutorial aborda um método chamado “corte por fio ”, no qual o circuito é dividido ao longo do fio [1], [2]. Observe que a divisão em partes é simples nos circuitos clássicos, uma vez que o resultado no ponto de divisão pode ser determinado de forma determinística e é 0 ou 1. No entanto, o estado do qubit no momento do corte é, em geral, um estado misto. Portanto, cada subcircuito precisa ser medido várias vezes em diferentes bases (geralmente uma base tomograficamente completa, como a base de Pauli [3], [4] ) e, consequentemente, preparado em seu estado próprio. A figura abaixo (fonte: [7] ) mostra um exemplo de divisão de um estado GHZ de quatro qubits em três subcircuitos. Aqui, MjM_j denota um conjunto de bases (geralmente Pauli X, Y e Z), e PiP_i denota um conjunto de estados próprios (geralmente 0|0\rangle, 1|1\rangle, +|+\rangle e +i|+i\rangle ).

wc-1.png wc-2.png

Como cada subcircuito possui menos qubits e portas lógicas, espera-se que sejam menos suscetíveis ao ruído. Este tutorial mostra um exemplo em que esse método pode ser usado para suprimir eficazmente o ruído no sistema.


Requisitos

Antes de iniciar este tutorial, verifique se você tem os seguintes itens instalados:

  • Qiskit SDK v2.0 ou posterior, com suporte para visualização
  • Qiskit Runtime v0.22 ou mais tarde ( pip install qiskit-ibm-runtime )
  • Complemento Qiskit para criação de circuitos v0.10.0 ou versão posterior (pip install qiskit-addon-cutting)
  • Complemento Qiskit utils 0.3 ou posterior (pip install qiskit-addon-utils)
  • Qiskit Aer (pip install qiskit-aer )

Instalação

import numpy as np
import matplotlib.pyplot as plt

from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit
from qiskit.quantum_info import PauliList, SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_aer import AerSimulator
from qiskit.result import sampled_expectation_value

from qiskit_addon_cutting.instructions import CutWire
from qiskit_addon_cutting import (
    cut_wires,
    expand_observables,
    partition_problem,
    generate_cutting_experiments,
    reconstruct_expectation_values,
)

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2, Batch

Exemplo de simulador em pequena escala

Este tutorial implementa um padrão do Qiskit para simular um circuito de localização de muitos corpos (MBL) unidimensional ( 1D ). O circuito MBL é um circuito eficiente em termos de hardware e é parametrizado por dois parâmetros: θ\theta e ϕ\vec{\phi}. Quando θ\theta é definido como 00 e o estado inicial é preparado em 0|0\rangle para todos os qubits, o valor esperado ideal de Zi\langle Z_i \rangle é +1+1 para cada local de qubit ii, independentemente dos valores de ϕ\vec{\phi}. Mais detalhes sobre este circuito estão disponíveis neste artigo.

Observe que, em um simulador sem ruído, o valor esperado obtido com e sem o corte do circuito será o mesmo.

Passo 1: Mapear entradas clássicas para um problema quântico

Construa o circuito MBL “ 1D ”

Primeiro, apresentamos uma função para a construção do circuito MBL de " 1D ".

class MBLChainCircuit(QuantumCircuit):
    def __init__(
        self, num_qubits: int, depth: int, use_cut: bool = False
    ) -> None:
        super().__init__(
            num_qubits, name=f"MBLChainCircuit<{num_qubits}, {depth}>"
        )
        evolution = MBLChainEvolution(num_qubits, depth, use_cut)
        self.compose(evolution, inplace=True)


class MBLChainEvolution(QuantumCircuit):
    def __init__(self, num_qubits: int, depth: int, use_cut) -> None:
        super().__init__(
            num_qubits, name=f"MBLChainEvolution<{num_qubits}, {depth}>"
        )

        theta = Parameter("θ")
        phis = ParameterVector("φ", num_qubits)

        for layer in range(depth):
            layer_parity = layer % 2
            # print("layer parity", layer_parity)
            for qubit in range(layer_parity, num_qubits - 1, 2):
                # print(qubit)
                self.cz(qubit, qubit + 1)
                self.u(theta, 0, np.pi, qubit)
                self.u(theta, 0, np.pi, qubit + 1)
                if (
                    use_cut
                    and layer_parity == 0
                    and (
                        qubit == num_qubits // 2 - 1
                        or qubit == num_qubits // 2
                    )
                ):
                    self.append(CutWire(), [num_qubits // 2])
                if use_cut and layer < depth - 1 and layer_parity == 1:
                    if qubit == num_qubits // 2:
                        self.append(CutWire(), [qubit])
            for qubit in range(num_qubits):
                self.p(phis[qubit], qubit)
num_qubits = 10
depth = 2
mbl = MBLChainCircuit(num_qubits, depth)
mbl.draw("mpl", fold=-1)

Output:

Output of the previous code cell

Calculamos o valor médio esperado O=1niZiO = \frac{1}{n} \sum_i Z_i para todos os qubits em θ=0\theta = 0. Como o valor esperado ideal de Zi=1\langle Z_i \rangle = 1 \forall ii, o valor esperado ideal de OO também é 11. Os parâmetros ϕ\phi são selecionados aleatoriamente.

np.random.seed(42)
phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis

É necessário anotar o circuito inserindo " CutWire " nos locais desejados para dividi-lo. Para este tutorial, optamos por uma divisão igualitária. O circuito MBL foi projetado de forma que a configuração use_cut=True na função insira a anotação corretamente após n2\frac{n}{2} qubits, sendo nn o número de qubits no circuito original. Também atribuímos os parâmetros gerados aleatoriamente ao circuito.

mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_cut.draw("mpl", fold=-1)

Output:

Output of the previous code cell

Etapa 2: Otimizar o problema para execução em hardware quântico

Divida o circuito em subcircuitos menores

Agora dividimos o circuito em dois subcircuitos menores usando qiskit-addon-cutting. qiskit-addon-cutting insere uma porta virtual Move para dividir o ponto de corte do fio, ajustando adequadamente o número de qubits. Agora vamos criar o circuito com essa porta virtual. Como há um fio cortado, o número de qubits associados aumentará em 1.

mbl_move = cut_wires(mbl_cut)
mbl_move.draw("mpl", fold=-1)

Output:

Output of the previous code cell

Construir e expandir o observável

A observável, conforme definido anteriormente, será a média de ZZ em cada qubit. No entanto, ao inserir a porta virtual Move , o número efetivo de qubits no circuito aumenta. O observável também deve ser expandido de forma adequada para levar em conta essa mudança no número de qubits. Observe que o observável sempre age de forma trivial (como em II ) sobre o qubit adicional adicionado para a porta virtual Move .

observable = PauliList(
    ["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
observable

Output:

PauliList(['ZIIIIIIIII', 'IZIIIIIIII', 'IIZIIIIIII', 'IIIZIIIIII',
           'IIIIZIIIII', 'IIIIIZIIII', 'IIIIIIZIII', 'IIIIIIIZII',
           'IIIIIIIIZI', 'IIIIIIIIIZ'])
new_obs = expand_observables(observable, mbl, mbl_move)
new_obs

Output:

PauliList(['ZIIIIIIIIII', 'IZIIIIIIIII', 'IIZIIIIIIII', 'IIIZIIIIIII',
           'IIIIZIIIIII', 'IIIIIIZIIII', 'IIIIIIIZIII', 'IIIIIIIIZII',
           'IIIIIIIIIZI', 'IIIIIIIIIIZ'])

Agora, o circuito pode ser dividido ao longo da Move porta, obtendo-se assim os subcircuitos, bem como o subobservável, que é a parte do observável original associada a cada subcircuito.

partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables

Aqui, visualizamos os dois subcircuitos:

subcircuits[0].draw("mpl", fold=-1)

Output:

Output of the previous code cell
subcircuits[1].draw("mpl", fold=-1)

Output:

Output of the previous code cell

Para expandir o observável utilizando a Move operação, é necessária uma PauliList estrutura de dados. Para reconstruir o valor esperado do circuito original, precisamos da observável no SparsePauliOp formato.

M_z = SparsePauliOp(
    ["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
    coeffs=[1 / num_qubits] * num_qubits,
)

Conforme discutido anteriormente, para cada corte, o circuito a montante deve ser medido em uma base de Pauli, e o circuito a jusante deve ser preparado no estado próprio dessa base. A função generate_cutting_experiments cria todos esses circuitos necessários e os coeficientes associados a cada circuito, essenciais para a reconstrução. Veja mais detalhes neste artigo.

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

Compilar os circuitos no backend

Para o primeiro exemplo, que envolve apenas simulação, compilamos o circuito para o conjunto de portas de base do backend:

service = QiskitRuntimeService()
backend = service.least_busy(
    operational=True, simulator=False, min_num_qubits=133
)

print(backend)

Output:

<IBMBackend('ibm_fez')>

Passo 3: Execute usando Qiskit primitives

Agora, execute cada subexperimento:

pm_basis = generate_preset_pass_manager(
    optimization_level=2, basis_gates=backend.configuration().basis_gates
)
basis_subexperiments = {
    label: pm_basis.run(partition_subexpts)
    for label, partition_subexpts in subexperiments.items()
}
sampler = SamplerV2(mode=AerSimulator())
jobs = {
    label: sampler.run(subsystem_subexpts, shots=2**12)
    for label, subsystem_subexpts in basis_subexperiments.items()
}

Etapa 4: Pós-processamento e retorno do resultado no formato clássico desejado

Agora, recuperamos o resultado de cada execução do sub-experimento e reconstruímos o valor esperado do circuito não cortado:

# Retrieve results
results = {label: job.result() for label, job in jobs.items()}
reconstructed_expval_terms = reconstruct_expectation_values(
    results,
    coefficients,
    subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, M_z.coeffs).real
reconstructed_expval

Output:

np.float64(0.9953821063041687)
methods = [
    "Uncut",
    "Wire cut",
]
values = [
    1,
    reconstructed_expval,
]  # since the ideal expectation value in noiseless simulation is +1

ax = plt.gca()
plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
ax.set_ylabel(r"$M_Z$", fontsize=12)

Output:

Text(0, 0.5, '$M_Z$')
Output of the previous code cell

Exemplo de hardware em grande escala

Agora, demonstraremos o corte de fios para um circuito MBL de 60 qubits. Tanto os circuitos não cortados quanto os cortados serão executados em um hardware d IBM Quantum® :

num_qubits = 60
depth = 2

# construct the circuit
mbl = MBLChainCircuit(num_qubits, depth)

# create parameters
phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis

# construct the cut circuit
mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_move = cut_wires(mbl_cut)

# Define observable and expand to account for the wire cut
observable = PauliList(
    ["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
new_obs = expand_observables(observable, mbl, mbl_move)

# Construct a SparsePauliOp version of the observable for later use in reconstruction
M_z = SparsePauliOp(
    ["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
    coeffs=[1 / num_qubits] * num_qubits,
)

# Partition the circuit and get subcircuits and subobservables
partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables

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

# Transpile the subexperiments to the backend
pm = generate_preset_pass_manager(optimization_level=2, backend=backend)
isa_subexperiments = {
    label: pm.run(partition_subexpts)
    for label, partition_subexpts in subexperiments.items()
}

# Execute the subexperiments and retrieve results
with Batch(backend=backend) as batch:
    sampler = SamplerV2(mode=batch)
    sampler.options.environment.job_tags = ["TUT_WC"]
    jobs = {
        label: sampler.run(subsystem_subexpts, shots=2**12)
        for label, subsystem_subexpts in isa_subexperiments.items()
    }
results = {label: job.result() for label, job in jobs.items()}

# Reconstruct the expectation value of the original observable
reconstructed_expval_terms = reconstruct_expectation_values(
    results,
    coefficients,
    subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, M_z.coeffs).real

# Compute the uncut circuit to obtain the noisy expectation value for comparison
sampler = SamplerV2(mode=backend)
sampler.options.environment.job_tags = ["TUT_WC"]

if mbl.num_clbits == 0:
    mbl.measure_all()
isa_mbl = pm.run(mbl)

pub = (isa_mbl, params)
uncut_job = sampler.run([pub])

uncut_counts = uncut_job.result()[0].data.meas.get_counts()
uncut_expval = sampled_expectation_value(uncut_counts, M_z)

# visualize the results
ax = plt.gca()
methods = ["uncut", "cut"]
values = [uncut_expval, reconstructed_expval]

plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
plt.axhline(y=1, color="k", linestyle="--")
plt.text(0.3, 0.95, "Exact result")
plt.show()

Output:

Output of the previous code cell
uncut_expval

Output:

0.9202473958333336

Próximas etapas

Recomendações

Se você achou este trabalho interessante, talvez se interesse pelo seguinte material:


Referências

[1] Peng, T., Harrow, A. W., Ozols, M., & Wu, X. (2020). Simulação de grandes circuitos quânticos em um pequeno computador quântico. Physical Review Letters, 125(15), 150504.

[2] Tang, W., Tomesh, T., Suchara, M., Larson, J., & Martonosi, M. (2021, abril). Cutqc: uso de computadores quânticos pequenos para avaliações de circuitos quânticos grandes. Em Proceedings of the 26th ACM International conference on architectural support for programming languages and operating systems (pp. 473-486).

[3] Perlin, M. A., Saleem, Z. H., Suchara, M., & Osborn, J. C. (2021). Corte de circuito quântico com tomografia de máxima verossimilhança. npj Quantum Information, 7(1), 64.

[4] Majumdar, R., & Wood, C. J. (2022). Corte de circuito quântico com atenuação de erros. arXiv pré-impressão arXiv:2211.13431.

[5] Khare, T., Majumdar, R., Sangle, R., Ray, A., Seshadri, P. V., & Simmhan, Y. (2023). Paralelização de cargas de trabalho clássicas-quânticas: Perfilando o impacto das técnicas de divisão. Em 2023, Conferência Internacional do IEEE sobre Engenharia e Computação Quântica (QCE) (Vol. 1, pp. 990-1000). IEEE.

[6] Bhoumik, D., Majumdar, R., Saha, A., & Sur-Kolay, S. (2023). Distributed Scheduling of Quantum Circuits with Noise and Time Optimization (Programação distribuída de circuitos quânticos com ruído e otimização de tempo). arXiv pré-impressão arXiv:2309.06005.

[7] Majumdar, R. (2024). Redução eficiente de recursos e ruído em circuitos de computação quântica discreta (Tese de doutorado, Instituto Indiano de Estatística - Calcutá). https://www.proquest.com/openview/b481def90b1cc80e6b58a77c99e8385c/1?pq-origsite=gscholar&cbl=2026366&diss=y

Esta página foi útil?
Relate um bug, erro de digitação ou solicite conteúdo no GitHub.