Lumaktaw sa pangunahing nilalaman

Wire Cutting bilang Two-Qubit `Move` Instruction

Sa tutorial na ito, mag-re-reconstruct tayo ng expectation values ng isang seven-qubit circuit sa pamamagitan ng paghati nito sa dalawang four-qubit circuit gamit ang wire cutting.

Ito ang mga hakbang na gagawin natin sa Qiskit pattern:

  • Hakbang 1: I-map ang problema sa quantum circuits at operators:
    • I-map ang hamiltonian sa isang quantum circuit.
  • Hakbang 2: I-optimize para sa target hardware [Ginagamit ang cutting addon]:
    • Putulin ang circuit at observable.
    • I-transpile ang mga subexperiment para sa hardware.
  • Hakbang 3: Isagawa sa target hardware:
    • Patakbuhin ang mga subexperiment na nakuha sa Hakbang 2 gamit ang isang Sampler primitive.
  • Hakbang 4: I-post-process ang mga resulta [Ginagamit ang cutting addon]:
    • Pagsamahin ang mga resulta ng Hakbang 3 upang ma-reconstruct ang expectation value ng observable na pinag-uusapan.

Hakbang 1: Map​

Gumawa ng circuit na puputulin​

Una, magsisimula tayo sa isang circuit na hango sa Fig. 1(a) ng arXiv:2302.03366v1.

# 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 import QuantumCircuit

qc_0 = QuantumCircuit(7)
for i in range(7):
qc_0.rx(np.pi / 4, i)
qc_0.cx(0, 3)
qc_0.cx(1, 3)
qc_0.cx(2, 3)
qc_0.cx(3, 4)
qc_0.cx(3, 5)
qc_0.cx(3, 6)
qc_0.cx(0, 3)
qc_0.cx(1, 3)
qc_0.cx(2, 3)
<qiskit.circuit.instructionset.InstructionSet at 0x7f16ab191a80>
qc_0.draw("mpl")

Quantum circuit diagram

Tukuyin ang isang observable​

from qiskit.quantum_info import SparsePauliOp

observable = SparsePauliOp(["ZIIIIII", "IIIZIII", "IIIIIIZ"])

Hakbang 2: Optimize​

Gumawa ng bagong circuit kung saan ang mga Move instruction ay inilagay sa nais na mga cut location​

Dahil sa circuit sa itaas, gusto natin maglagay ng dalawang wire cut sa middle qubit line, upang ang circuit ay mahati sa dalawang circuit na may apat na qubit bawat isa. Isang paraan upang gawin ito ay ang manu-manong paglalagay ng two-qubit Move instruction na naglilipat ng state mula sa isang qubit wire patungo sa isa pa. Ang Move instruction ay konseptuwal na katumbas ng reset operation sa pangalawang qubit, na sinusundan ng SWAP gate. Ang epekto ng instruction na ito ay ilipat ang state ng una (source) qubit sa pangalawang (destination) qubit, habang itinatapon ang papasok na state ng pangalawang qubit. Para gumana ito ayon sa nilalayon, mahalaga na ang pangalawang (destination) qubit ay walang ibinabahaging entanglement sa natitirang bahagi ng system; kung hindi, ang reset operation ay magdudulot na ang state ng natitirang bahagi ng system ay bahagyang ma-collapse.

Dito, gumagawa tayo ng bagong circuit na may isang karagdagang qubit at ang mga Move operation ay nakapuwesto. Sa halimbawang ito, magagamit natin muli ang isang qubit: ang source qubit ng unang Move ay nagiging destination qubit ng pangalawang Move operation.

Tandaan: Bilang alternatibo sa direktang pagtatrabaho gamit ang mga Move instruction, maaaring piliin na markahan ang mga wire cut gamit ang isang single-qubit CutWire instruction. Ang cut_wires function ay umiiral upang i-transform ang mga CutWire patungo sa Move instruction sa mga bagong inilaang qubit. Gayunpaman, kabaligtaran sa manu-manong paraan, ang awtomatikong paraan na ito ay hindi nagpapahintulot sa muling paggamit ng mga qubit wire. Tingnan ang CutWire how-to guide para sa mga detalye.

from qiskit_addon_cutting.instructions import Move

qc_1 = QuantumCircuit(8)
for i in [*range(4), *range(5, 8)]:
qc_1.rx(np.pi / 4, i)
qc_1.cx(0, 3)
qc_1.cx(1, 3)
qc_1.cx(2, 3)
qc_1.append(Move(), [3, 4])
qc_1.cx(4, 5)
qc_1.cx(4, 6)
qc_1.cx(4, 7)
qc_1.append(Move(), [4, 3])
qc_1.cx(0, 3)
qc_1.cx(1, 3)
qc_1.cx(2, 3)

qc_1.draw("mpl")

Quantum circuit diagram

Gumawa ng observable na sasama sa bagong circuit​

Ang observable na ito ay tumutugma sa observable, ngunit dapat nating wastong isaalang-alang ang dagdag na qubit wire na naidagdag (i.e., naglalagay tayo ng "I" sa index 4). Tandaan na sa Qiskit, ang string representation ng qubit-0 ay tumutugma sa pinakakanang Pauli character.

observable_expanded = SparsePauliOp(["ZIIIIIII", "IIIIZIII", "IIIIIIIZ"])

Paghiwalayin ang circuit at observables​

Tulad sa nakaraang mga tutorial, ang mga qubit na naghahati ng karaniwang partition label ay pagsasama-samahin, at ang mga non-local gate na sumasaklaw sa higit sa isang partition ay puputulin.

from qiskit_addon_cutting import partition_problem

partitioned_problem = partition_problem(
circuit=qc_1, partition_labels="AAAABBBB", observables=observable_expanded.paulis
)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
bases = partitioned_problem.bases

I-visualize ang decomposed na problema​

subobservables
{'A': PauliList(['IIII', 'ZIII', 'IIIZ']),
'B': PauliList(['ZIII', 'IIII', 'IIII'])}
subcircuits["A"].draw("mpl")

Quantum circuit diagram

subcircuits["B"].draw("mpl")

Quantum circuit diagram

Kalkulahin ang sampling overhead para sa napiling mga cut​

Dito, pinuputol natin ang dalawang wire, na nagreresulta sa sampling overhead na 444^4.

Para sa higit pa tungkol sa sampling overhead na nakukuha mula sa circuit cutting, sumangguni sa explanatory material.

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

Bumuo ng mga subexperiment na patatakbuhin sa backend​

Ang generate_cutting_experiments ay tumatanggap ng circuits/observables args bilang mga dictionary na nagma-map sa qubit partition labels patungo sa mga kaukulang subcircuit/subobservables.

Upang i-simulate ang expectation value ng full-sized na circuit, maraming subexperiment ang nabubuo mula sa joint quasiprobability distribution ng mga decomposed na gate at pagkatapos ay isinasagawa sa isa o higit pang backend. Ang bilang ng mga sample na kinukuha mula sa distribution ay kinokontrol ng num_samples, at isang combined coefficient ang ibinibigay para sa bawat unique sample. Para sa karagdagang impormasyon kung paano kinakalkula ang mga coefficient, sumangguni sa explanatory material.

from qiskit_addon_cutting import generate_cutting_experiments

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

Pumili ng backend​

Dito ay gumagamit tayo ng fake backend, na magreresulta sa Qiskit Runtime na tumatakbo sa local mode (i.e., sa isang local simulator).

from qiskit_ibm_runtime.fake_provider import FakeManilaV2

backend = FakeManilaV2()

Ihanda ang mga subexperiment para sa backend​

Kailangan nating i-transpile ang mga circuit gamit ang ating backend bilang target bago isumite ang mga ito sa 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()
}

Hakbang 3: Execute​

Patakbuhin ang mga subexperiment gamit ang Qiskit Runtime Sampler primitive​

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()}

Hakbang 4: Post-process​

I-reconstruct ang expectation value​

I-reconstruct ang expectation values para sa bawat observable term at pagsamahin ang mga ito upang ma-reconstruct ang expectation value para sa orihinal na observable.

from qiskit_addon_cutting import reconstruct_expectation_values

reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, observable.coeffs)

Ikumpara ang reconstructed expectation value sa exact expectation value mula sa orihinal na circuit at observable​

from qiskit_aer.primitives import EstimatorV2

estimator = EstimatorV2()
exact_expval = estimator.run([(qc_0, 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: 1.51319069
Exact expectation value: 1.59099026
Error in estimation: -0.07779957
Relative error in estimation: -0.04890009