Pular para o conteúdo principal
Este projeto está em fase beta. Encontrou um bug? Abra uma issue no GitHub. Tem uma pergunta ou ideia? Pergunte no Discussions — adoraríamos receber seu feedback!
Como está a qualidade da tradução nesta página?

Otimização de circuito quântico

nota

Toshinari Itoko (21 de junho de 2024)

Baixe o PDF da aula original. Observe que alguns trechos de código podem ter sido descontinuados, pois são imagens estáticas.

O tempo estimado de QPU para executar este experimento é de 15 s.

(Observação: Algumas células da parte 2 foram copiadas do notebook "Qiskit Deep dive", escrito por Matthew Treinish, mantenedor do Qiskit.)

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"qiskit": "qiskit", "qiskit_aer": "qiskit-aer", "qiskit_ibm_runtime": "qiskit-ibm-runtime"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
# !pip install 'qiskit[visualization]'
# !pip install qiskit_ibm_runtime qiskit_aer
# !pip install jupyter
# !pip install matplotlib pylatexenc pydot pillow
import qiskit

qiskit.__version__
'2.0.2'
import qiskit_ibm_runtime

qiskit_ibm_runtime.__version__
'0.40.1'
import qiskit_aer

qiskit_aer.__version__
'0.17.1'

1. Introdução​

Esta aula aborda vários aspectos da otimização de circuitos em computação quântica. Especificamente, veremos o valor da otimização de circuitos usando as configurações de otimização integradas ao Qiskit. Em seguida, vamos nos aprofundar um pouco e ver o que você pode fazer como especialista na sua área de aplicação para construir circuitos de forma inteligente. Por fim, daremos uma olhada detalhada no que acontece durante a transpilação para nos ajudar a otimizar nossos circuitos.

2. A otimização de circuitos faz diferença​

Primeiro, comparamos os resultados de executar circuitos de preparação do estado GHZ de 5 qubits (12(∣00000⟩+∣11111⟩)\frac{1}{\sqrt{2}} \left( |00000\rangle + |11111\rangle \right)) com e sem otimização.

from qiskit.circuit import QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.primitives import BackendSamplerV2 as Sampler
from qiskit_ibm_runtime.fake_provider import FakeBrisbane

backend = FakeBrisbane()

Primeiro, usamos um circuito GHZ sintetizado de forma ingênua, como mostrado a seguir.

num_qubits = 5

ghz_circ = QuantumCircuit(num_qubits)
ghz_circ.h(0)
[ghz_circ.cx(0, i) for i in range(1, num_qubits)]
ghz_circ.measure_all()
ghz_circ.draw("mpl")

Saída da célula de código anterior

2.1 Nível de otimização​

optimization_level tem quatro opções, de 0 a 3. Quanto maior o nível de otimização, mais esforço computacional é gasto para otimizar o circuito. O nível 0 não realiza nenhuma otimização e apenas faz a quantidade mínima de trabalho para tornar o circuito executável no backend selecionado. O nível 3 gasta a maior quantidade de esforço (e normalmente de tempo de execução) para tentar otimizar o circuito. O nível 1 é o nível de otimização padrão. Transpilamos o circuito sem otimização (optimization_level=0) e com otimização (optimization_level=2). Vemos uma grande diferença no comprimento dos circuitos transpilados.

pm0 = generate_preset_pass_manager(
optimization_level=0, backend=backend, seed_transpiler=777
)
pm2 = generate_preset_pass_manager(
optimization_level=2, backend=backend, seed_transpiler=777
)
circ0 = pm0.run(ghz_circ)
circ2 = pm2.run(ghz_circ)
print("optimization_level=0:")
display(circ0.draw("mpl", idle_wires=False, fold=-1))
print("optimization_level=2:")
display(circ2.draw("mpl", idle_wires=False, fold=-1))
optimization_level=0:

Output of the previous code cell

optimization_level=2:

Output of the previous code cell

2.2 Exercício​

Experimente também o optimization_level=1 e compare o circuito resultante com os dois acima. Tente modificando o código acima.

Solução:

pm1 = generate_preset_pass_manager(
optimization_level=1, backend=backend, seed_transpiler=777
)
circ1 = pm1.run(ghz_circ)
print("optimization_level=1:")
display(circ1.draw("mpl", idle_wires=False, fold=-1))
optimization_level=1:

Saída da célula de código anterior

Execute em um backend falso (simulação com ruído). Veja o Apêndice 1 para saber como executar em um backend real.

# run the circuits on the fake backend (noisy simulator)
sampler = Sampler(backend=backend)
job = sampler.run([circ0, circ2], shots=10000)
print(f"Job ID: {job.job_id()}")
Job ID: 93a4ac70-e3ea-44ad-aea9-5045840c9076
# get results
result = job.result()
unoptimized_result = result[0].data.meas.get_counts()
optimized_result = result[1].data.meas.get_counts()
from qiskit.visualization import plot_histogram

# plot
sim_result = {"0" * 5: 0.5, "1" * 5: 0.5}
plot_histogram(
[result for result in [sim_result, unoptimized_result, optimized_result]],
bar_labels=False,
legend=[
"ideal",
"no optimization",
"with optimization",
],
)

Saída da célula de código anterior

3. A síntese de circuitos faz diferença​

A seguir, comparamos os resultados da execução de dois circuitos de preparação de estado GHZ de 5 qubits sintetizados de formas diferentes (12(∣00000⟩+∣11111⟩)\frac{1}{\sqrt{2}} \left( |00000\rangle + |11111\rangle \right)).

# Original GHZ circuit (naive synthesis)
ghz_circ.draw("mpl")

Output of the previous code cell

# A cleverly-synthesized GHZ circuit
ghz_circ2 = QuantumCircuit(5)
ghz_circ2.h(2)
ghz_circ2.cx(2, 1)
ghz_circ2.cx(2, 3)
ghz_circ2.cx(1, 0)
ghz_circ2.cx(3, 4)
ghz_circ2.measure_all()
ghz_circ2.draw("mpl")

Output of the previous code cell

# transpile both with the same optimization level 2
circ_org = pm2.run(ghz_circ)
circ_new = pm2.run(ghz_circ2)
print("original synthesis:")
display(circ_org.draw("mpl", idle_wires=False, fold=-1))
print("new synthesis:")
display(circ_new.draw("mpl", idle_wires=False, fold=-1))
original synthesis:

Output of the previous code cell

new synthesis:

Output of the previous code cell

A nova síntese produz um circuito mais raso. Por quê?

Isso acontece porque o novo circuito pode ser mapeado em qubits conectados linearmente — e, portanto, também no grafo de acoplamento heavy-hexagon do IBM® Brisbane —, enquanto o circuito original requer conectividade em formato de estrela (um nó de grau 4) e, por isso, não pode ser mapeado no grafo de acoplamento heavy-hex, cujos nós têm grau no máximo 3. Como resultado, o circuito original exige roteamento de qubits, o que adiciona portas SWAP e aumenta a contagem de portas.

O que fizemos no novo circuito pode ser visto como uma síntese de circuito manual "com reconhecimento de restrição de acoplamento". Em outras palavras: resolver manualmente a síntese de circuito e o mapeamento de circuito ao mesmo tempo.

# run the circuits
sampler = Sampler(backend=backend)
job = sampler.run([circ_org, circ_new], shots=10000)
print(f"Job ID: {job.job_id()}")
Job ID: 19d635b0-4d8b-44c2-a76e-49e4b9078b1b
# get results
result = job.result()
synthesis_org_result = result[0].data.meas.get_counts()
synthesis_new_result = result[1].data.meas.get_counts()
# plot
sim_result = {"0" * 5: 0.5, "1" * 5: 0.5}
plot_histogram(
[
result
for result in [
sim_result,
unoptimized_result,
synthesis_org_result,
synthesis_new_result,
]
],
bar_labels=False,
legend=[
"ideal",
"no optimization",
"synthesis_org",
"synthesis_new",
],
)

Output of the previous code cell

Em geral, a síntese de circuito depende da aplicação e é difícil demais para um software cobrir todas as aplicações possíveis. Acontece que o transpiler do Qiskit não tem funções para sintetizar o circuito de preparação de estado GHZ. Nesse caso, vale a pena considerar a síntese manual de circuito, como mostrado acima. Nesta seção, examinamos os detalhes de como o transpiler do Qiskit funciona usando o seguinte circuito de exemplo simples.

# Build a toy example circuit
from math import pi
import itertools
from qiskit.circuit import QuantumCircuit
from qiskit.circuit.library import excitation_preserving

circuit = QuantumCircuit(4, name="Example circuit")
circuit.append(excitation_preserving(4, reps=1, flatten=True), range(4))
circuit.measure_all()

value_cycle = itertools.cycle([0, pi / 4, pi / 2, 3 * pi / 4, pi, 2 * pi])
circuit.assign_parameters(
[x[1] for x in zip(range(len(circuit.parameters)), value_cycle)], inplace=True
)
circuit.draw("mpl")

Saída da célula de código anterior

3.1 Visualizando o fluxo completo de transpilação do Qiskit​

Examinamos os passes do transpilador (tarefas) para optimization_level=1.

from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

# There is no need to read this entire image, but this outputs all the steps in the transpile() call
# for optimization level 1
pm = generate_preset_pass_manager(1, backend, seed_transpiler=42)
pm.draw()

Saída da célula de código anterior

O fluxo é composto por seis estágios:

print(pm.stages)
('init', 'layout', 'routing', 'translation', 'optimization', 'scheduling')

3.2 Visualizando um estágio individual​

Primeiro, vamos visualizar todas as tarefas (passes do transpilador) realizadas no estágio init.

pm.init.draw()

Saída da célula de código anterior

Podemos executar cada estágio individualmente. Vamos executar o estágio init para o nosso circuito. Ao habilitar o logger, podemos ver os detalhes da execução.

import logging

logger = logging.getLogger()
logger.setLevel("INFO")

init_out = pm.init.run(circuit)
init_out.draw("mpl", fold=-1)
INFO:qiskit.passmanager.base_tasks:Pass: UnitarySynthesis - 0.03576 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: HighLevelSynthesis - 0.16618 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: BasisTranslator - 0.07176 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: InverseCancellation - 0.27299 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: ContractIdleWiresInControlFlow - 0.00811 (ms)

Saída da célula de código anterior

3.3 Exercício​

Visualize os passes do estágio layout e execute o estágio para o circuito de saída do estágio init (init_out), modificando as células usadas acima.

Solução:

display(pm.layout.draw())
layout_out = pm.layout.run(init_out)
layout_out.draw("mpl", idle_wires=False, fold=-1)

Saída da célula de código anterior

INFO:qiskit.passmanager.base_tasks:Pass: SetLayout - 0.01001 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: TrivialLayout - 0.07129 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: CheckMap - 0.08917 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: VF2Layout - 1.24431 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: BarrierBeforeFinalMeasurements - 0.02599 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: SabreLayout - 5.11169 (ms)

Saída da célula de código anterior

Faça o mesmo para o estágio translation.

Solução:

display(pm.translation.draw())
basis_out = pm.translation.run(layout_out)
basis_out.draw("mpl", idle_wires=False, fold=-1)

Saída da célula de código anterior

INFO:qiskit.passmanager.base_tasks:Pass: UnitarySynthesis - 0.03386 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: HighLevelSynthesis - 0.02718 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: BasisTranslator - 2.64192 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: CheckGateDirection - 0.02217 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: GateDirection - 0.36502 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: BasisTranslator - 0.64778 (ms)

Saída da célula de código anterior

Observação: Nem todo estágio individual pode sempre ser executado de forma independente (pois alguns precisam carregar informações de um estágio anterior).

3.4 Estágio de otimização​

O último estágio padrão no pipeline é o de otimização. Depois de embutirmos o circuito para o alvo, o circuito cresceu bastante. A maior parte disso se deve a ineficiências nas relações de equivalência provenientes da tradução de base e da inserção de SWAPs. O estágio de otimização é utilizado para tentar minimizar o tamanho e a profundidade do circuito. Ele executa uma série de passes em um laço do while até atingir uma saída estável.

# pm.pre_optimization.draw()
pm.optimization.draw()

Saída da célula de código anterior

logger = logging.getLogger()
logger.setLevel("INFO")
opt_out = pm.optimization.run(basis_out)
INFO:qiskit.passmanager.base_tasks:Pass: Depth - 0.30112 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: FixedPoint - 0.03195 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: Size - 0.01216 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: FixedPoint - 0.01001 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: Optimize1qGatesDecomposition - 0.63729 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: InverseCancellation - 0.41723 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: ContractIdleWiresInControlFlow - 0.01192 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: GatesInBasis - 0.05484 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: Depth - 0.08583 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: FixedPoint - 0.20599 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: Size - 0.00787 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: FixedPoint - 0.00715 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: Optimize1qGatesDecomposition - 0.16809 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: InverseCancellation - 0.17190 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: ContractIdleWiresInControlFlow - 0.00691 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: GatesInBasis - 0.02408 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: Depth - 0.04935 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: FixedPoint - 0.00525 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: Size - 0.00620 (ms)
INFO:qiskit.passmanager.base_tasks:Pass: FixedPoint - 0.00286 (ms)
opt_out.draw("mpl", idle_wires=False, fold=-1)

Saída da célula de código anterior

4. Exemplos detalhados​

4.1 Otimização de blocos de dois qubits usando síntese unitária de dois qubits​

Para os níveis 2 e 3, temos mais passes (Collect2qBlocks, ConsolidateBlocks, UnitarySynthesis) para uma otimização mais avançada, especificamente a otimização de blocos de dois qubits. (Compare o fluxo do estágio de otimização do nível 2 com o do nível 1 mostrado acima)

A otimização de blocos de dois qubits é composta por duas etapas: coleta e consolidação dos blocos de 2 qubits, e síntese das matrizes unitárias de 2 qubits.

pm2 = generate_preset_pass_manager(2, backend, seed_transpiler=42)
pm2.optimization.draw()

Saída da célula de código anterior

from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import (
Collect2qBlocks,
ConsolidateBlocks,
UnitarySynthesis,
)

# Collect 2q blocks and consolidate to unitary when we expect that we can reduce the 2q gate count
# for that unitary
consolidate_pm = PassManager(
[
Collect2qBlocks(),
ConsolidateBlocks(target=backend.target),
]
)
display(basis_out.draw("mpl", idle_wires=False, fold=-1))

consolidated = consolidate_pm.run(basis_out)
consolidated.draw("mpl", idle_wires=False, fold=-1)

Saída da célula de código anterior

Saída da célula de código anterior

# Synthesize unitaries
UnitarySynthesis(target=backend.target)(consolidated).draw(
"mpl", idle_wires=False, fold=-1
)

Saída da célula de código anterior

logger.setLevel("WARNING")

Vimos na Parte 2 que o fluxo real do compilador quântico não é tão simples e é composto por muitos passes (tarefas). Isso se deve principalmente à engenharia de software necessária para garantir o desempenho em uma ampla variedade de circuitos de aplicação e a manutenibilidade do software. O transpilador do Qiskit funciona bem na maioria dos casos, mas se você perceber que seu circuito não está sendo bem otimizado por ele, seria uma boa oportunidade para pesquisar sua própria otimização de circuito específica para a aplicação, como mostrado na Parte 1. A tecnologia de transpilação está evoluindo — sua contribuição em P&D é bem-vinda.

from qiskit.circuit import QuantumCircuit
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler
service = QiskitRuntimeService()
backend = service.backend("ibm_brisbane")
sampler = Sampler(backend)
circ = QuantumCircuit(3)
circ.ccx(0, 1, 2)
circ.measure_all()
circ.draw("mpl")

Saída da célula de código anterior

sampler.run([circ]) # IBMInputValueError will be raised

4.2 A otimização de circuitos faz diferença​

Primeiro, comparamos os resultados de executar circuitos de preparação do estado GHZ de 5 qubits (12(∣00000⟩+∣11111⟩)\frac{1}{\sqrt{2}} \left( |00000\rangle + |11111\rangle \right)) com e sem otimização.

from qiskit.circuit import QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler
service = QiskitRuntimeService()
# backend = service.backend('ibm_brisbane')
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
) # Eagle
backend

Primeiro, usamos um circuito GHZ sintetizado de forma ingênua, como mostrado a seguir.

num_qubits = 5

ghz_circ = QuantumCircuit(num_qubits)
ghz_circ.h(0)
[ghz_circ.cx(0, i) for i in range(1, num_qubits)]
ghz_circ.measure_all()
ghz_circ.draw("mpl")

Output of the previous code cell

Transpilamos o circuito sem otimização (optimization_level=0) e com otimização (optimization_level=2). Como você pode ver, há uma grande diferença no comprimento dos circuitos transpilados.

pm0 = generate_preset_pass_manager(
optimization_level=0, backend=backend, seed_transpiler=777
)
pm2 = generate_preset_pass_manager(
optimization_level=2, backend=backend, seed_transpiler=777
)
circ0 = pm0.run(ghz_circ)
circ2 = pm2.run(ghz_circ)
print("optimization_level=0:")
display(circ0.draw("mpl", idle_wires=False, fold=-1))
print("optimization_level=2:")
display(circ2.draw("mpl", idle_wires=False, fold=-1))
optimization_level=0:

Output of the previous code cell

optimization_level=2:

Output of the previous code cell

# run the circuits
sampler = Sampler(backend)
job = sampler.run([circ0, circ2], shots=10000)
job_id = job.job_id()
print(f"Job ID: {job_id}")
Job ID: d13rnnemya70008ek1zg
# REPLACE WITH YOUR OWN JOB IDS
job = service.job(job_id)
# get results
result = job.result()
unoptimized_result = result[0].data.meas.get_counts()
optimized_result = result[1].data.meas.get_counts()
from qiskit.visualization import plot_histogram

# plot
sim_result = {"0" * 5: 0.5, "1" * 5: 0.5}
plot_histogram(
[result for result in [sim_result, unoptimized_result, optimized_result]],
bar_labels=False,
legend=[
"ideal",
"no optimization",
"with optimization",
],
)

Output of the previous code cell

4.3 A síntese de circuitos faz diferença​

A seguir, comparamos os resultados da execução de dois circuitos de preparação de estado GHZ de 5 qubits sintetizados de formas diferentes (12(∣00000⟩+∣11111⟩)\frac{1}{\sqrt{2}} \left( |00000\rangle + |11111\rangle \right)).

# Original GHZ circuit (naive synthesis)
ghz_circ.draw("mpl")

Saída da célula de código anterior

# A better GHZ circuit (smarter synthesis), you learned in a previous lecture
ghz_circ2 = QuantumCircuit(5)
ghz_circ2.h(2)
ghz_circ2.cx(2, 1)
ghz_circ2.cx(2, 3)
ghz_circ2.cx(1, 0)
ghz_circ2.cx(3, 4)
ghz_circ2.measure_all()
ghz_circ2.draw("mpl")

Saída da célula de código anterior

circ_org = pm2.run(ghz_circ)
circ_new = pm2.run(ghz_circ2)
print("original synthesis:")
display(circ_org.draw("mpl", idle_wires=False, fold=-1))
print("new synthesis:")
display(circ_new.draw("mpl", idle_wires=False, fold=-1))
original synthesis:

Saída da célula de código anterior

new synthesis:

Saída da célula de código anterior

# run the circuits
sampler = Sampler(backend)
job = sampler.run([circ_org, circ_new], shots=10000)
job_id = job.job_id()
print(f"Job ID: {job_id}")
Job ID: d13rp283grvg008j12fg
# REPLACE WITH YOUR OWN JOB IDS
job = service.job(job_id)
# get results
result = job.result()
synthesis_org_result = result[0].data.meas.get_counts()
synthesis_new_result = result[1].data.meas.get_counts()
# plot
sim_result = {"0" * 5: 0.5, "1" * 5: 0.5}
plot_histogram(
[result for result in [sim_result, synthesis_org_result, synthesis_new_result]],
bar_labels=False,
legend=[
"ideal",
"synthesis_org",
"synthesis_new",
],
)

Saída da célula de código anterior

4.4 Decomposição geral de portas de 1 qubit​

from qiskit import QuantumCircuit, transpile
from qiskit.circuit import Parameter
from qiskit.circuit.library.standard_gates import UGate

phi, theta, lam = Parameter("φ"), Parameter("θ"), Parameter("λ")
qc = QuantumCircuit(1)
qc.append(UGate(theta, phi, lam), [0])
qc.draw(output="mpl")

Saída da célula de código anterior

transpile(qc, basis_gates=["rz", "sx"]).draw(output="mpl")

Saída da célula de código anterior

4.5 Otimização de blocos de 1 qubit​

from qiskit import QuantumCircuit

qc = QuantumCircuit(1)
qc.x(0)
qc.y(0)
qc.z(0)
qc.rx(1.23, 0)
qc.ry(1.23, 0)
qc.rz(1.23, 0)
qc.h(0)
qc.s(0)
qc.t(0)
qc.sx(0)
qc.sdg(0)
qc.tdg(0)
qc.draw(output="mpl")

Saída da célula de código anterior

from qiskit.quantum_info import Operator

Operator(qc)
Operator([[ 0.45292511-0.57266982j, -0.66852684-0.14135058j],
[ 0.14135058+0.66852684j, -0.57266982+0.45292511j]],
input_dims=(2,), output_dims=(2,))
from qiskit import transpile

qc_opt = transpile(qc, basis_gates=["rz", "sx"])
qc_opt.draw(output="mpl")

Saída da célula de código anterior

Operator(qc_opt)
Operator([[ 0.45292511-0.57266982j, -0.66852684-0.14135058j],
[ 0.14135058+0.66852684j, -0.57266982+0.45292511j]],
input_dims=(2,), output_dims=(2,))
Operator(qc).equiv(Operator(qc_opt))
True

4.6 Decomposição de Toffoli​

qc = QuantumCircuit(3)
qc.ccx(0, 1, 2)
qc.draw(output="mpl")

Saída da célula de código anterior

from qiskit import QuantumCircuit, transpile

qc = QuantumCircuit(3)
qc.ccx(0, 1, 2)
qc = transpile(qc, basis_gates=["rz", "sx", "cx"])
qc.draw(output="mpl")

Saída da célula de código anterior

4.7 Decomposição da porta CU​

from qiskit.circuit.library.standard_gates import CUGate

phi, theta, lam, gamma = Parameter("φ"), Parameter("θ"), Parameter("λ"), Parameter("γ")
qc = QuantumCircuit(2)
# qc.cu(theta, phi, lam, gamma, 0, 1)
qc.append(CUGate(theta, phi, lam, gamma), [0, 1])
qc.draw(output="mpl")

Saída da célula de código anterior

from qiskit.circuit.library.standard_gates import CUGate

phi, theta, lam, gamma = Parameter("φ"), Parameter("θ"), Parameter("λ"), Parameter("γ")
qc = QuantumCircuit(2)
qc.append(CUGate(theta, phi, lam, gamma), [0, 1])
qc = transpile(qc, basis_gates=["rz", "sx", "cx"])
qc.draw(output="mpl")

Saída da célula de código anterior

4.8 CX, ECR e CZ são iguais a menos de Cliffords locais​

Note que HH (Hadamard), SS (rotação Z de π/2\pi/2), S†S^\dagger (rotação Z de −π/2-\pi/2) e XX (Pauli X) são todas portas de Clifford.

qc = QuantumCircuit(2)
qc.cx(0, 1)
qc.draw(output="mpl", style="bw")

Saída da célula de código anterior

qc = QuantumCircuit(2)
qc.cx(0, 1)
transpile(qc, basis_gates=["x", "s", "h", "sdg", "ecr"]).draw(output="mpl", style="bw")

Saída da célula de código anterior

qc = QuantumCircuit(2)
qc.cx(0, 1)
transpile(qc, basis_gates=["h", "cz"]).draw(output="mpl", style="bw")

Saída da célula de código anterior

Usando as portas de base de 1 qubit do backend IBM: "rz", "sx" e "x".

qc = QuantumCircuit(2)
qc.cx(0, 1)
transpile(qc, basis_gates=["rz", "sx", "x", "ecr"]).draw(output="mpl", style="bw")

Saída da célula de código anterior

qc = QuantumCircuit(2)
qc.cx(0, 1)
transpile(qc, basis_gates=["rz", "sx", "x", "cz"]).draw(output="mpl", style="bw")

Saída da célula de código anterior

# Check Qiskit version
import qiskit

qiskit.__version__
'2.0.2'