Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use automatic partition_labels in new CutWire how-to #370

Merged
merged 18 commits into from
Aug 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions circuit_knitting/cutting/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
:toctree: ../stubs/
:nosignatures:

transform_cuts_to_moves
cut_wires
expand_observables
partition_circuit_qubits
partition_problem
Expand Down Expand Up @@ -88,7 +88,7 @@
)
from .cutting_evaluation import execute_experiments, CuttingExperimentResults
from .cutting_reconstruction import reconstruct_expectation_values
from .cut_wire_to_move import transform_cuts_to_moves, expand_observables
from .wire_cutting_transforms import cut_wires, expand_observables

__all__ = [
"partition_circuit_qubits",
Expand All @@ -99,6 +99,6 @@
"reconstruct_expectation_values",
"PartitionedCuttingProblem",
"CuttingExperimentResults",
"transform_cuts_to_moves",
"cut_wires",
"expand_observables",
]
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,40 @@
"""Function to transform a :class:`.CutWire` instruction to a :class:`.Move` instruction."""
from __future__ import annotations

from typing import Callable
from itertools import groupby

import numpy as np
from qiskit.circuit import Qubit, QuantumCircuit
from qiskit.circuit import Qubit, QuantumCircuit, Operation
from qiskit.circuit.exceptions import CircuitError
from qiskit.quantum_info import PauliList

from .instructions.move import Move
from .instructions import Move
from .qpd.instructions import TwoQubitQPDGate


def transform_cuts_to_moves(circuit: QuantumCircuit, /) -> QuantumCircuit:
def cut_wires(circuit: QuantumCircuit, /) -> QuantumCircuit:
r"""Transform all :class:`.CutWire` instructions in a circuit to :class:`.Move` instructions marked for cutting.

The returned circuit will have one newly allocated qubit for every :class:`.CutWire` instruction.

See Sec. 3 and Appendix A of `2302.03366v1
<https://arxiv.org/abs/2302.03366v1>`__ for more information about the two
different representations of wire cuts: single-qubit (:class:`.CutWire`)
vs. two-qubit (:class:`.Move`).

Args:
circuit: Original circuit with :class:`.CutWire` instructions

Returns:
circuit: New circuit with :class:`.CutWire` instructions replaced by :class:`.Move` instructions wrapped in :class:`TwoQubitQPDGate`\ s
"""
return _transform_cut_wires(
circuit, lambda: TwoQubitQPDGate.from_instruction(Move())
)


def _transform_cuts_to_moves(circuit: QuantumCircuit, /) -> QuantumCircuit:
"""Transform all :class:`.CutWire` instructions in a circuit to :class:`.Move` instructions.

Args:
Expand All @@ -32,6 +55,12 @@ def transform_cuts_to_moves(circuit: QuantumCircuit, /) -> QuantumCircuit:
Returns:
circuit: New circuit with :class:`.CutWire` instructions replaced by :class`.Move` instructions
"""
return _transform_cut_wires(circuit, Move)


def _transform_cut_wires(
circuit: QuantumCircuit, factory: Callable[[], Operation], /
) -> QuantumCircuit:
new_circuit, mapping = _circuit_structure_mapping(circuit)

for instructions in circuit.data:
Expand All @@ -40,7 +69,7 @@ def transform_cuts_to_moves(circuit: QuantumCircuit, /) -> QuantumCircuit:
if instructions in circuit.get_instructions("cut_wire"):
# Replace cut_wire with move instruction
new_circuit.compose(
other=Move(),
other=factory(),
qubits=[mapping[gate_index[0]], mapping[gate_index[0]] + 1],
inplace=True,
)
Expand Down
30 changes: 14 additions & 16 deletions docs/circuit_cutting/how-tos/how_to_specify_cut_wires.ipynb

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,11 @@
"source": [
"### Create a new circuit where `Move` instructions have been placed at the desired cut locations\n",
"\n",
"Given the above circuit, we would like to place two wire cuts on the middle qubit line, so that the circuit can separate into two circuits of four qubits each. One way to do this (currently, the only way) is to place two-qubit `Move` instructions that move the state from one qubit wire to another. A `Move` instruction is conceptually equivalent to a reset operation on the second qubit, followed by a SWAP gate. The effect of this instruction is to transfer the state of the first (source) qubit to the second (detination) qubit, while discarding the incoming state of the second qubit. For this to work as intended, it is important that the second (destination) qubit share no entanglement with the remainder of the system; otherwise, the reset operation will cause the state of the remainder of the system to be partially collapsed.\n",
"Given the above circuit, we would like to place two wire cuts on the middle qubit line, so that the circuit can separate into two circuits of four qubits each. One way to do this is to manually place two-qubit `Move` instructions that move the state from one qubit wire to another. A `Move` instruction is conceptually equivalent to a reset operation on the second qubit, followed by a SWAP gate. The effect of this instruction is to transfer the state of the first (source) qubit to the second (detination) qubit, while discarding the incoming state of the second qubit. For this to work as intended, it is important that the second (destination) qubit share no entanglement with the remainder of the system; otherwise, the reset operation will cause the state of the remainder of the system to be partially collapsed.\n",
"\n",
"Here, we build a new circuit with one additional qubit and the `Move` operations in place. In this example, we are able to reuse a qubit: the source qubit of the first `Move` becomes the destination qubit of the second `Move` operation."
"Here, we build a new circuit with one additional qubit and the `Move` operations in place. In this example, we are able to reuse a qubit: the source qubit of the first `Move` becomes the destination qubit of the second `Move` operation.\n",
"\n",
"Note: As an alternative to working directly with `Move` instructions, one may choose to mark wire cuts using a single-qubit `CutWire` instruction. The `cut_wires` function exists to transform `CutWire`s to `Move` instructions on newly allocated qubits. However, in contrast to the manual method, this automatic method does not allow for the re-use of qubit wires. See the `CutWire` [how-to guide](../how-tos/how_to_specify_cut_wires.ipynb) for details."
]
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit, ClassicalRegister
from qiskit.quantum_info import PauliList
from circuit_knitting.cutting.instructions import Move, CutWire
from circuit_knitting.cutting import transform_cuts_to_moves, expand_observables
from circuit_knitting.cutting.qpd.instructions import TwoQubitQPDGate
from circuit_knitting.cutting import cut_wires, expand_observables
from circuit_knitting.cutting.wire_cutting_transforms import _transform_cuts_to_moves


@fixture
Expand Down Expand Up @@ -207,7 +209,7 @@ def resulting_circuit4() -> tuple[QuantumCircuit, list[int]]:
)
def test_transform_cuts_to_moves(request, sample_circuit, resulting_circuit):
"""Tests the transformation of CutWire to Move instruction."""
assert request.getfixturevalue(resulting_circuit)[0] == transform_cuts_to_moves(
assert request.getfixturevalue(resulting_circuit)[0] == _transform_cuts_to_moves(
request.getfixturevalue(sample_circuit)
)

Expand All @@ -226,7 +228,7 @@ def test_circuit_mapping(request, sample_circuit, resulting_circuit):
sample_circuit = request.getfixturevalue(sample_circuit)
resulting_mapping = request.getfixturevalue(resulting_circuit)[1]

final_circuit = transform_cuts_to_moves(sample_circuit)
final_circuit = _transform_cuts_to_moves(sample_circuit)
final_mapping = [
final_circuit.find_bit(qubit).index for qubit in sample_circuit.qubits
]
Expand All @@ -249,7 +251,7 @@ def test_circuit_mapping(request, sample_circuit, resulting_circuit):
def test_qreg_name_num(request, sample_circuit):
"""Tests the number and name of qregs in initial and final circuits."""
sample_circuit = request.getfixturevalue(sample_circuit)
final_circuit = transform_cuts_to_moves(sample_circuit)
final_circuit = _transform_cuts_to_moves(sample_circuit)

# Tests number of qregs in initial and final circuits
assert len(sample_circuit.qregs) == len(final_circuit.qregs)
Expand All @@ -273,7 +275,7 @@ def test_qreg_name_num(request, sample_circuit):
def test_qreg_size(request, sample_circuit):
"""Tests the size of qregs in initial and final circuits."""
sample_circuit = request.getfixturevalue(sample_circuit)
final_circuit = transform_cuts_to_moves(sample_circuit)
final_circuit = _transform_cuts_to_moves(sample_circuit)

# Tests size of qregs in initial and final circuits
for sample_qreg, final_qreg in zip(
Expand All @@ -295,7 +297,7 @@ def test_qreg_size(request, sample_circuit):
def test_circuit_width(request, sample_circuit):
"""Tests the width of the initial and final circuits."""
sample_circuit = request.getfixturevalue(sample_circuit)
final_circuit = transform_cuts_to_moves(sample_circuit)
final_circuit = _transform_cuts_to_moves(sample_circuit)
total_cut_wire = len(sample_circuit.get_instructions("cut_wire"))

# Tests width of initial and final circuit
Expand All @@ -314,7 +316,7 @@ def test_circuit_width(request, sample_circuit):
def test_creg(request, sample_circuit):
"""Tests the number and size of cregs in the initial and final circuits."""
sample_circuit = request.getfixturevalue(sample_circuit)
final_circuit = transform_cuts_to_moves(sample_circuit)
final_circuit = _transform_cuts_to_moves(sample_circuit)

# Tests number of cregs in initial and final circuits
assert len(sample_circuit.cregs) == len(final_circuit.cregs)
Expand All @@ -326,6 +328,19 @@ def test_creg(request, sample_circuit):
assert sample_creg.size == final_creg.size


def test_cut_wires():
qc = QuantumCircuit(2)
qc.h(0)
qc.h(1)
qc.append(CutWire(), [1])
qc.s(0)
qc.s(1)
qc_out = cut_wires(qc)
qpd_gate = qc_out.data[2].operation
assert isinstance(qpd_gate, TwoQubitQPDGate)
assert qpd_gate.label == "cut_move"


class TestExpandObservables:
def test_expand_observables(self):
qc0 = QuantumCircuit(3)
Expand Down