Skip to content

Commit

Permalink
Remove unnecessary resets in generate_cutting_experiments (#458)
Browse files Browse the repository at this point in the history
* Remove unnecessary resets in `generate_cutting_experiments`

* Revert whitespace change
  • Loading branch information
garrison authored Nov 28, 2023
1 parent 190552d commit 8d0baf1
Show file tree
Hide file tree
Showing 6 changed files with 314 additions and 0 deletions.
28 changes: 28 additions & 0 deletions circuit_knitting/cutting/cutting_experiments.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@
import numpy as np
from qiskit.circuit import QuantumCircuit, ClassicalRegister
from qiskit.quantum_info import PauliList
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import RemoveResetInZeroState, DAGFixedPoint

from ..utils.iteration import strict_zip
from ..utils.transpiler_passes import RemoveFinalReset, ConsolidateResets
from ..utils.observable_grouping import ObservableCollection, CommutingObservableGroup
from .qpd import (
WeightType,
Expand Down Expand Up @@ -58,6 +61,12 @@ def generate_cutting_experiments(
The coefficients will always be returned as a 1D array -- one coefficient for each unique sample.
Note that this function also runs some transpiler passes on each generated
circuit, namely :class:`~qiskit.transpiler.passes.RemoveResetInZeroState`,
:class:`.RemoveFinalReset`, and :class:`.ConsolidateResets`, in order to
remove unnecessary :class:`~qiskit.circuit.library.Reset`\ s from the
circuit that are added by the subexperiment decompositions for cut wires.
Args:
circuits: The circuit(s) to partition and separate
observables: The observable(s) to evaluate for each unique sample
Expand Down Expand Up @@ -156,6 +165,25 @@ def generate_cutting_experiments(
_append_measurement_circuit(new_qc, cog, inplace=True)
subexperiments_dict[label].append(new_qc)

# Remove initial and final resets from the subexperiments. This will
# enable the `Move` operation to work on backends that don't support
# `Reset`, as long as qubits are not re-used. See
# https://github.com/Qiskit-Extensions/circuit-knitting-toolbox/issues/452.
# While we are at it, we also consolidate each run of multiple resets
# (which can arise when re-using qubits) into a single reset.
pass_manager = PassManager()
pass_manager.append(
[
RemoveResetInZeroState(),
RemoveFinalReset(),
ConsolidateResets(),
DAGFixedPoint(),
],
do_while=lambda property_set: not property_set["dag_fixed_point"],
)
for label, subexperiments in subexperiments_dict.items():
subexperiments_dict[label] = pass_manager.run(subexperiments)

# If the input was a single quantum circuit, return the subexperiments as a list
subexperiments_out: list[QuantumCircuit] | dict[
Hashable, list[QuantumCircuit]
Expand Down
6 changes: 6 additions & 0 deletions circuit_knitting/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@
=============================================================
.. automodule:: circuit_knitting.utils.transforms
===================================================================
Transpiler passes (:mod:`circuit_knitting.utils.transpiler_passes`)
===================================================================
.. automodule:: circuit_knitting.utils.transpiler_passes
"""

from .orbital_reduction import reduce_bitstrings
Expand Down
66 changes: 66 additions & 0 deletions circuit_knitting/utils/transpiler_passes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# This code is a Qiskit project.

# (C) Copyright IBM 2023.

# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""
Transpiler passes useful for circuit knitting.
.. currentmodule:: circuit_knitting.utils.transpiler_passes
.. autosummary::
:toctree: ../stubs/
RemoveFinalReset
ConsolidateResets
"""

from qiskit.circuit import Reset, Qubit
from qiskit.dagcircuit import DAGOpNode
from qiskit.transpiler.basepasses import TransformationPass


class RemoveFinalReset(TransformationPass):
"""Remove reset when it is the final instruction on a qubit wire."""

def run(self, dag):
"""Run the RemoveFinalReset pass on ``dag``.
Args:
dag (DAGCircuit): the DAG to be optimized.
Returns:
DAGCircuit: the optimized DAG.
"""
for output_node in dag.output_map.values():
if isinstance(output_node.wire, Qubit):
pred = next(dag.predecessors(output_node))
if isinstance(pred, DAGOpNode) and isinstance(pred.op, Reset):
dag.remove_op_node(pred)
return dag


class ConsolidateResets(TransformationPass):
"""Consolidate a run duplicate resets in to a single reset."""

def run(self, dag):
"""Run the ConsolidateResets pass on ``dag``.
Args:
dag (DAGCircuit): the DAG to be optimized.
Returns:
DAGCircuit: the optimized DAG.
"""
resets = dag.op_nodes(Reset)
for reset in resets:
successor = next(dag.successors(reset))
if isinstance(successor, DAGOpNode) and isinstance(successor.op, Reset):
dag.remove_op_node(reset)
return dag
12 changes: 12 additions & 0 deletions releasenotes/notes/remove-redundant-resets-1893a61a341e6ce8.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
upgrade:
- |
The :func:`.generate_cutting_experiments` function now performs
some optimizations on the generated circuits before returning them
to the user. In particular, it performs the
:class:`~qiskit.transpiler.passes.RemoveResetInZeroState`,
:class:`.RemoveFinalReset`, and :class:`.ConsolidateResets`
passes, so that circuits with cut wires and no re-used qubits are
transformed into subexperiments that contain no
:class:`~qiskit.circuit.library.Reset`\ s. This allows such circuits to
work on a greater variety of hardware backends.
70 changes: 70 additions & 0 deletions test/cutting/test_cutting_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import pytest
from copy import deepcopy

import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.circuit.library import EfficientSU2, CXGate
from qiskit.quantum_info import PauliList
Expand All @@ -23,10 +24,13 @@

from circuit_knitting.cutting.qpd.instructions import SingleQubitQPDGate
from circuit_knitting.cutting.qpd import QPDBasis
from circuit_knitting.cutting.instructions import CutWire, Move
from circuit_knitting.cutting import (
partition_problem,
generate_cutting_experiments,
reconstruct_expectation_values,
cut_wires,
expand_observables,
)


Expand Down Expand Up @@ -109,3 +113,69 @@ def test_workflow_with_unused_qubits():
subobservables,
num_samples=10,
)


def test_wire_cut_workflow_without_reused_qubits():
"""Test no resets in subexperiments when wire cut workflow has no re-used qubits."""
qc = QuantumCircuit(2)
qc.h(range(2))
qc.cx(0, 1)
qc.append(CutWire(), [0])
qc.cx(1, 0)

observables = PauliList(["IZ", "ZI", "ZZ", "XX"])

qc_1 = cut_wires(qc)
assert qc_1.num_qubits == 3

observables_1 = expand_observables(observables, qc, qc_1)

partitioned_problem = partition_problem(circuit=qc_1, observables=observables_1)

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

for subsystem_subexpts in subexperiments.values():
for subexpt in subsystem_subexpts:
assert "reset" not in subexpt.count_ops()


def test_wire_cut_workflow_with_reused_qubits():
"""Test at most a single reset in subexperiments when wire cut workflow has a single re-used qubit."""
qc = QuantumCircuit(8)
for i in [*range(4), *range(5, 8)]:
qc.rx(np.pi / 4, i)
qc.cx(0, 3)
qc.cx(1, 3)
qc.cx(2, 3)
qc.append(Move(), [3, 4])
qc.cx(4, 5)
qc.cx(4, 6)
qc.cx(4, 7)
qc.append(Move(), [4, 3])
qc.cx(0, 3)
qc.cx(1, 3)
qc.cx(2, 3)

observables = PauliList(["ZIIIIIII", "IIIIZIII", "IIIIIIIZ"])

partitioned_problem = partition_problem(
circuit=qc, partition_labels="AAAABBBB", observables=observables
)

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

# The initial circuit had a single instance of qubit re-use with a Move
# instruction. Each A subexperiment should have a single reset, and each B
# subexperiment should be free of resets.
for subexpt in subexperiments["A"]:
assert subexpt.count_ops()["reset"] == 1
for subexpt in subexperiments["B"]:
assert "reset" not in subexpt.count_ops()
132 changes: 132 additions & 0 deletions test/utils/test_transpiler_passes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# This code is a Qiskit project.

# (C) Copyright IBM 2023.

# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Tests for CKT transpilation passes."""

import unittest

from qiskit import QuantumRegister, QuantumCircuit
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import DAGFixedPoint
from qiskit.converters import circuit_to_dag

from circuit_knitting.utils.transpiler_passes import RemoveFinalReset, ConsolidateResets


class TestRemoveFinalReset(unittest.TestCase):
"""Test remove-reset-in-zero-state optimizations."""

def test_optimize_single_reset(self):
"""Remove a single final reset
qr0:--[H]--|0>-- ==> qr0:--[H]--
"""
qr = QuantumRegister(1, "qr")
circuit = QuantumCircuit(qr)
circuit.h(0)
circuit.reset(qr)
dag = circuit_to_dag(circuit)

expected = QuantumCircuit(qr)
expected.h(0)

pass_ = RemoveFinalReset()
after = pass_.run(dag)

self.assertEqual(circuit_to_dag(expected), after)

def test_dont_optimize_non_final_reset(self):
"""Do not remove reset if not final instruction
qr0:--|0>--[H]-- ==> qr0:--|0>--[H]--
"""
qr = QuantumRegister(1, "qr")
circuit = QuantumCircuit(qr)
circuit.reset(qr)
circuit.h(qr)
dag = circuit_to_dag(circuit)

expected = QuantumCircuit(qr)
expected.reset(qr)
expected.h(qr)

pass_ = RemoveFinalReset()
after = pass_.run(dag)

self.assertEqual(circuit_to_dag(expected), after)

def test_optimize_single_reset_in_diff_qubits(self):
"""Remove a single final reset in different qubits
qr0:--[H]--|0>-- qr0:--[H]--
==>
qr1:--[X]--|0>-- qr1:--[X]----
"""
qr = QuantumRegister(2, "qr")
circuit = QuantumCircuit(qr)
circuit.h(0)
circuit.x(1)
circuit.reset(qr)
dag = circuit_to_dag(circuit)

expected = QuantumCircuit(qr)
expected.h(0)
expected.x(1)

pass_ = RemoveFinalReset()
after = pass_.run(dag)

self.assertEqual(circuit_to_dag(expected), after)


class TestRemoveFinalResetFixedPoint(unittest.TestCase):
"""Test RemoveFinalReset in a transpiler, using fixed point."""

def test_two_resets(self):
"""Remove two final resets
qr0:--[H]-|0>-|0>-- ==> qr0:--[H]--
"""
qr = QuantumRegister(1, "qr")
circuit = QuantumCircuit(qr)
circuit.h(qr[0])
circuit.reset(qr[0])
circuit.reset(qr[0])

expected = QuantumCircuit(qr)
expected.h(qr[0])

pass_manager = PassManager()
pass_manager.append(
[RemoveFinalReset(), DAGFixedPoint()],
do_while=lambda property_set: not property_set["dag_fixed_point"],
)
after = pass_manager.run(circuit)

self.assertEqual(expected, after)


class TestConsolidateResets(unittest.TestCase):
"""Test consolidate-resets optimization."""

def test_consolidate_double_reset(self):
"""Consolidate a pair of resets.
qr0:--|0>--|0>-- ==> qr0:--|0>--
"""
qr = QuantumRegister(1, "qr")
circuit = QuantumCircuit(qr)
circuit.reset(qr)
circuit.reset(qr)
dag = circuit_to_dag(circuit)

expected = QuantumCircuit(qr)
expected.reset(qr)

pass_ = ConsolidateResets()
after = pass_.run(dag)

self.assertEqual(circuit_to_dag(expected), after)

0 comments on commit 8d0baf1

Please sign in to comment.