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

add DropNegligible transpiler pass #12384

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions qiskit/transpiler/passes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@
from .optimization import ElidePermutations
from .optimization import NormalizeRXAngle
from .optimization import OptimizeAnnotated
from .optimization import DropNegligible

# circuit analysis
from .analysis import ResourceEstimation
Expand Down
1 change: 1 addition & 0 deletions qiskit/transpiler/passes/optimization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@
from .elide_permutations import ElidePermutations
from .normalize_rx_angle import NormalizeRXAngle
from .optimize_annotated import OptimizeAnnotated
from .drop_negligible import DropNegligible
100 changes: 100 additions & 0 deletions qiskit/transpiler/passes/optimization/drop_negligible.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2024.
#
# 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 pass to drop gates with negligible effects."""

from __future__ import annotations

import math
from collections.abc import Iterable

from qiskit.circuit.library import (
CPhaseGate,
PhaseGate,
RXGate,
RXXGate,
RYGate,
RYYGate,
RZGate,
RZZGate,
XXMinusYYGate,
XXPlusYYGate,
)
from qiskit.dagcircuit import DAGCircuit
from qiskit.transpiler.basepasses import TransformationPass

# List of Gate classes with the property that if the gate's parameters are all
# (close to) zero then the gate has (close to) no effect.
DROP_NEGLIGIBLE_GATE_CLASSES = (
CPhaseGate,
PhaseGate,
RXGate,
RYGate,
RZGate,
RXXGate,
RYYGate,
RZZGate,
XXPlusYYGate,
XXMinusYYGate,
)


class DropNegligible(TransformationPass):
"""Drop gates with negligible effects.

Removes certain gates whose parameters are all close to zero up to the specified
tolerance. By default, the gates subject to removal are those present in a
hard-coded list, specified below. Additional gate types to consider can be passed
as an argument to the constructor of this class.

By default, the following gate classes are considered for removal:

- :class:`CPhaseGate`
- :class:`PhaseGate`
- :class:`RXGate`
- :class:`RYGate`
- :class:`RZGate`
- :class:`RXXGate`
- :class:`RYYGate`
- :class:`RZZGate`
- :class:`XXPlusYYGate`
- :class:`XXMinusYYGate`
"""

def __init__(
self, *, atol: float = 1e-8, additional_gate_types: Iterable[type] | None = None
) -> None:
"""Initialize the transpiler pass.

Args:
atol: Absolute numerical tolerance for determining whether a gate's effect
is negligible.
additional_gate_types: List of :class:`Gate` subclasses that should be
considered for dropping in addition to the built-in gates.
"""
self.atol = atol
self.gate_types = DROP_NEGLIGIBLE_GATE_CLASSES
if additional_gate_types is not None:
self.gate_types += tuple(additional_gate_types)
super().__init__()

def run(self, dag: DAGCircuit) -> DAGCircuit:
for node in dag.op_nodes():
if not isinstance(node.op, self.gate_types):
continue
if not all(isinstance(param, (int, float, complex)) for param in node.op.params):
continue
if all(
math.isclose(param, 0, rel_tol=0, abs_tol=self.atol) for param in node.op.params
):
dag.remove_op_node(node)
return dag
135 changes: 135 additions & 0 deletions test/python/transpiler/test_drop_negligible.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2024.
#
# 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 the DropNegligible transpiler pass."""

import numpy as np

from qiskit.circuit import Gate, Parameter, QuantumCircuit, QuantumRegister
from qiskit.circuit.library import (
CPhaseGate,
RXGate,
RXXGate,
RYGate,
RYYGate,
RZGate,
RZZGate,
XXMinusYYGate,
XXPlusYYGate,
)
from qiskit.quantum_info import Operator
from qiskit.transpiler.passes import DropNegligible

from test import QiskitTestCase # pylint: disable=wrong-import-order


class TestDropNegligible(QiskitTestCase):
"""Test the DropNegligible pass."""

def test_drops_negligible_gates(self):
"""Test that negligible gates are dropped."""
qubits = QuantumRegister(2)
circuit = QuantumCircuit(qubits)
a, b = qubits
circuit.append(CPhaseGate(1e-5), [a, b])
circuit.append(CPhaseGate(1e-8), [a, b])
circuit.append(RXGate(1e-5), [a])
circuit.append(RXGate(1e-8), [a])
circuit.append(RYGate(1e-5), [a])
circuit.append(RYGate(1e-8), [a])
circuit.append(RZGate(1e-5), [a])
circuit.append(RZGate(1e-8), [a])
circuit.append(RXXGate(1e-5), [a, b])
circuit.append(RXXGate(1e-8), [a, b])
circuit.append(RYYGate(1e-5), [a, b])
circuit.append(RYYGate(1e-8), [a, b])
circuit.append(RZZGate(1e-5), [a, b])
circuit.append(RZZGate(1e-8), [a, b])
circuit.append(XXPlusYYGate(1e-5, 1e-8), [a, b])
circuit.append(XXPlusYYGate(1e-8, 1e-8), [a, b])
circuit.append(XXMinusYYGate(1e-5, 1e-8), [a, b])
circuit.append(XXMinusYYGate(1e-8, 1e-8), [a, b])
transpiled = DropNegligible()(circuit)
self.assertEqual(circuit.count_ops()["cp"], 2)
self.assertEqual(transpiled.count_ops()["cp"], 1)
self.assertEqual(circuit.count_ops()["rx"], 2)
self.assertEqual(transpiled.count_ops()["rx"], 1)
self.assertEqual(circuit.count_ops()["ry"], 2)
self.assertEqual(transpiled.count_ops()["ry"], 1)
self.assertEqual(circuit.count_ops()["rz"], 2)
self.assertEqual(transpiled.count_ops()["rz"], 1)
self.assertEqual(circuit.count_ops()["rxx"], 2)
self.assertEqual(transpiled.count_ops()["rxx"], 1)
self.assertEqual(circuit.count_ops()["ryy"], 2)
self.assertEqual(transpiled.count_ops()["ryy"], 1)
self.assertEqual(circuit.count_ops()["rzz"], 2)
self.assertEqual(transpiled.count_ops()["rzz"], 1)
self.assertEqual(circuit.count_ops()["xx_plus_yy"], 2)
self.assertEqual(transpiled.count_ops()["xx_plus_yy"], 1)
self.assertEqual(circuit.count_ops()["xx_minus_yy"], 2)
self.assertEqual(transpiled.count_ops()["xx_minus_yy"], 1)
np.testing.assert_allclose(
np.array(Operator(circuit)), np.array(Operator(transpiled)), atol=1e-7
)

def test_handles_parameters(self):
"""Test that gates with parameters are ignored gracefully."""
qubits = QuantumRegister(2)
circuit = QuantumCircuit(qubits)
a, b = qubits
theta = Parameter("theta")
circuit.append(CPhaseGate(theta), [a, b])
circuit.append(CPhaseGate(1e-5), [a, b])
circuit.append(CPhaseGate(1e-8), [a, b])
transpiled = DropNegligible()(circuit)
self.assertEqual(circuit.count_ops()["cp"], 3)
self.assertEqual(transpiled.count_ops()["cp"], 2)

def test_handles_number_types(self):
"""Test that gates with different types of numbers are handled correctly."""
qubits = QuantumRegister(2)
circuit = QuantumCircuit(qubits)
a, b = qubits
circuit.append(CPhaseGate(np.float32(1e-6)), [a, b])
circuit.append(CPhaseGate(1e-3), [a, b])
circuit.append(CPhaseGate(1e-8), [a, b])
transpiled = DropNegligible(atol=1e-5)(circuit)
self.assertEqual(circuit.count_ops()["cp"], 3)
self.assertEqual(transpiled.count_ops()["cp"], 1)

def test_additional_gate_types(self):
"""Test passing additional gate types."""

class TestGateA(Gate):
"""A gate class."""
pass

class TestGateB(Gate):
"""Another gate class."""
pass

qubits = QuantumRegister(2)
circuit = QuantumCircuit(qubits)
a, b = qubits
circuit.append(CPhaseGate(1e-5), [a, b])
circuit.append(CPhaseGate(1e-8), [a, b])
circuit.append(TestGateA("test_gate_a", 1, [1e-5, 1e-5]), [a])
circuit.append(TestGateA("test_gate_a", 1, [1e-8, 1e-8]), [a])
circuit.append(TestGateB("test_gate_b", 1, [1e-5, 1e-5]), [a])
circuit.append(TestGateB("test_gate_b", 1, [1e-8, 1e-8]), [a])
transpiled = DropNegligible(additional_gate_types=[TestGateA])(circuit)
self.assertEqual(circuit.count_ops()["cp"], 2)
self.assertEqual(transpiled.count_ops()["cp"], 1)
self.assertEqual(circuit.count_ops()["test_gate_a"], 2)
self.assertEqual(transpiled.count_ops()["test_gate_a"], 1)
self.assertEqual(circuit.count_ops()["test_gate_b"], 2)
self.assertEqual(transpiled.count_ops()["test_gate_b"], 2)