Lumaktaw sa pangunahing nilalaman

Magsimula sa circuit cutting gamit ang gate cuts

Mga bersyon ng package

Ang code sa pahinang ito ay ginawa gamit ang mga sumusunod na kinakailangan. Inirerekomenda naming gamitin ang mga bersyong ito o mas bago.

qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
qiskit-aer~=0.17
qiskit-addon-cutting~=0.10.0

Ipinapakita ng gabay na ito ang dalawang gumaganang halimbawa ng gate cuts gamit ang qiskit-addon-cutting package. Ang unang halimbawa ay nagpapakita kung paano bawasan ang lalim ng circuit (ang bilang ng mga instruksyon sa circuit) sa pamamagitan ng pagputol ng mga entangling gate sa mga hindi magkaratig na qubit na kung hindi ay magdadagdag ng SWAP overhead kapag na-transpile sa hardware. Ang pangalawang halimbawa ay sumasaklaw sa kung paano gamitin ang gate cutting para bawasan ang lapad ng circuit (ang bilang ng mga qubit) sa pamamagitan ng paghahati ng isang circuit sa ilang circuit na may mas kaunting qubit.

Gagamitin ng parehong halimbawa ang efficient_su2 ansatz at muling itatayo ang parehong observable.

Gate cutting para bawasan ang lalim ng circuit​

Ang sumusunod na workflow ay nagbabawas ng lalim ng circuit sa pamamagitan ng pagputol ng mga malalayong gate, iniiwasan ang isang malaking serye ng SWAP gate na kung hindi ay ipapakilala.

Magsimula sa efficient_su2 ansatz, na may "circular" na entanglement para magpakilala ng mga malalayong gate.

# Added by doQumentation β€” required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-cutting qiskit-aer qiskit-ibm-runtime
import numpy as np
from qiskit.circuit.library import efficient_su2
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime.fake_provider import FakeManilaV2
from qiskit_ibm_runtime import SamplerV2, Batch
from qiskit_aer.primitives import EstimatorV2
from qiskit_addon_cutting import (
cut_gates,
partition_problem,
generate_cutting_experiments,
reconstruct_expectation_values,
)

circuit = efficient_su2(num_qubits=4, entanglement="circular")
circuit.assign_parameters([0.4] * len(circuit.parameters), inplace=True)

observable = SparsePauliOp(["ZZII", "IZZI", "-IIZZ", "XIXI", "ZIZZ", "IXIX"])
print(f"Observable: {observable}")
circuit.draw("mpl", scale=0.8)
Observable: SparsePauliOp(['ZZII', 'IZZI', 'IIZZ', 'XIXI', 'ZIZZ', 'IXIX'],
coeffs=[ 1.+0.j, 1.+0.j, -1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])

Output of the previous code cell

Bawat isa sa mga CNOT gate sa pagitan ng mga qubit q0q_0 at q3q_3 ay nagdadagdag ng dalawang SWAP gate pagkatapos ng transpilation (inaakala na ang mga qubit ay nakakonekta sa isang tuwid na linya). Para maiwasan ang pagtaas ng lalim na ito, maaari mong palitan ang mga malalayong gate na ito ng mga TwoQubitQPDGate object gamit ang cut_gates() na paraan. Ang function na ito ay nagbabalik din ng listahan ng mga QPDBasis instance β€” isa para sa bawat decomposition.

# Find the indices of the distant gates
cut_indices = [
i
for i, instruction in enumerate(circuit.data)
if {circuit.find_bit(q)[0] for q in instruction.qubits} == {0, 3}
]

# Decompose distant CNOTs into TwoQubitQPDGate instances
qpd_circuit, bases = cut_gates(circuit, cut_indices)

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

Output of the previous code cell

Ngayong naidagdag na ang mga cut gate instruction, ang mga subexperiment ay magkakaroon ng mas maliit na lalim pagkatapos ng transpilation kumpara sa orihinal na circuit. Ang code snippet sa ibaba ay gumagawa ng mga subexperiment gamit ang generate_cutting_experiments, na tumatanggap ng circuit at observable para muling itayo.

Paalala tungkol sa bilang ng samples

Ang num_samples na argumento ay tinutukoy kung ilang sample ang kukunin mula sa quasi-probability distribution at tinutukoy ang katumpakan ng mga coefficient na ginagamit para sa reconstruction. Ang pagpasa ng infinity (np.inf) ay titiyaking eksaktong malalkulahin ang lahat ng coefficient. Basahin ang mga API docs sa paglikha ng weights at paglikha ng cutting experiments para sa karagdagang impormasyon.

Kapag nagawa na ang mga subexperiment, maaari mo na itong i-transpile at gamitin ang Sampler primitive para samplahan ang distribution at muling itayo ang tinatayang expectation values. Ang sumusunod na code block ay gumagawa, nagta-transpile, at nagpapatakbo ng mga subexperiment. Pagkatapos ay muling itatayo ang mga resulta at ikukumpara sa eksaktong expectation value.

# Generate the subexperiments and sampling coefficients
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit, observables=observable.paulis, num_samples=np.inf
)

# Set a backend to use and transpile the subexperiments
backend = FakeManilaV2()
pass_manager = generate_preset_pass_manager(
optimization_level=1, backend=backend
)
isa_subexperiments = pass_manager.run(subexperiments)

# Set up the Qiskit Runtime Sampler primitive, submit the subexperiments, and retrieve the results
sampler = SamplerV2(backend)
job = sampler.run(isa_subexperiments, shots=4096 * 3)
results = job.result()

# Reconstruct the results
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
observable.paulis,
)

# Apply the coefficients of the original observable
reconstructed_expval = np.dot(reconstructed_expval_terms, observable.coeffs)

estimator = EstimatorV2()
exact_expval = (
estimator.run([(circuit, observable, [0.4] * len(circuit.parameters))])
.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.49812826
Exact expectation value: 0.50497603
Error in estimation: -0.00684778
Relative error in estimation: -0.0135606
Paalala tungkol sa mga coefficient ng observable

Para tumpak na maitayo muli ang expectation value, ang mga coefficient ng orihinal na observable (na iba sa mga coefficient sa output ng generate_cutting_experiments()) ay dapat ilapat sa output ng reconstruction, dahil nawala ang impormasyong ito nang nabuo ang mga cutting experiment o nang mapalawak ang observable.

Karaniwang maaaring ilapat ang mga coefficient na ito sa pamamagitan ng numpy.dot() gaya ng ipinapakita sa itaas.

Gate cutting para bawasan ang lapad ng circuit​

Ipinapakita ng seksyong ito kung paano gamitin ang gate cutting para bawasan ang lapad ng circuit. Magsimula sa parehong efficient_su2 ngunit gamitin ang "linear" na entanglement.

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

observable = SparsePauliOp(["ZZII", "IZZI", "-IIZZ", "XIXI", "ZIZZ", "IXIX"])
print(f"Observable: {observable}")

qc.draw("mpl", scale=0.8)
Observable: SparsePauliOp(['ZZII', 'IZZI', 'IIZZ', 'XIXI', 'ZIZZ', 'IXIX'],
coeffs=[ 1.+0.j, 1.+0.j, -1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])

Output of the previous code cell

Pagkatapos ay gumawa ng mga subcircuit at subobservable na ipapatakbo gamit ang partition_problem() function. Tinatanggap ng function na ito ang circuit, observable, at isang opsyonal na partitioning scheme at ibinabalik ang mga cut circuit at observable sa anyo ng isang dictionary.

Ang partitioning ay tinukoy ng isang label string ng anyo "AABB" kung saan ang bawat label sa string na ito ay tumutugma sa qubit sa parehong index ng circuit na argumento. Ang mga qubit na nagbabahagi ng parehong partition label ay pinagsama, at anumang hindi-lokal na gate na sumasaklaw sa higit sa isang partition ay puputulin.

Paalala

Ang observables kwarg sa partition_problem ay may uri na PauliList. Ang mga coefficient at phase ng observable term ay hindi pinapansin sa panahon ng decomposition ng problema at pagpapatakbo ng mga subexperiment. Maaaring muling ilapat ang mga ito sa panahon ng reconstruction ng expectation value.

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

print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
print(f"Subobservables: {subobservables}")
subcircuits["A"].draw("mpl", scale=0.8)
Sampling overhead: 81.0
Subobservables: {'A': PauliList(['II', 'ZI', 'ZZ', 'XI', 'ZZ', 'IX']), 'B': PauliList(['ZZ', 'IZ', 'II', 'XI', 'ZI', 'IX'])}

Output of the previous code cell

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

Output of the previous code cell

Ang susunod na hakbang ay ang paggamit ng mga subcircuit at subobservable para gumawa ng mga subexperiment na ipapatakbo sa isang QPU gamit ang generate_cutting_experiments na paraan.

Para matantya ang expectation value ng buong circuit, maraming subexperiment ang ginagawa mula sa joint quasi-probability distribution ng mga decomposed gate at pagkatapos ay pinapatakbo sa isa o higit pang QPU. Ang bilang ng mga sample na kukunin mula sa distribution na ito ay kinokontrol ng num_samples na argumento.

Ang sumusunod na code block ay gumagawa ng mga subexperiment at pinapatakbo ang mga ito gamit ang Sampler primitive sa isang lokal na simulator. (Para patakbuhin ang mga ito sa isang QPU, palitan ang backend ng iyong piniling QPU resource.)

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

# Set a backend to use and transpile the subexperiments
backend = FakeManilaV2()
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()
}

# 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=4096 * 3)
for label, subsystem_subexpts in isa_subexperiments.items()
}

# Retrieve results
results = {label: job.result() for label, job in jobs.items()}

Sa huli, ang expectation value ng buong circuit ay muling itinatayo gamit ang reconstruct_expectation_values na paraan.

Ang code block sa ibaba ay muling itinatayo ang mga resulta at ikukumpara sa eksaktong expectation value.

# 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)

estimator = EstimatorV2()
exact_expval = (
estimator.run([(qc, observable, [0.4] * len(qc.parameters))])
.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.53571896
Exact expectation value: 0.56254612
Error in estimation: -0.02682716
Relative error in estimation: -0.04768882

Mga susunod na hakbang​

Mga rekomendasyon