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 RemoveIdentityEquivalent transpiler pass #12384

Merged
merged 20 commits into from
Oct 29, 2024
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
1 change: 1 addition & 0 deletions crates/accelerate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub mod nlayout;
pub mod optimize_1q_gates;
pub mod pauli_exp_val;
pub mod remove_diagonal_gates_before_measure;
pub mod remove_identity_equiv;
pub mod results;
pub mod sabre;
pub mod sampled_exp_val;
Expand Down
149 changes: 149 additions & 0 deletions crates/accelerate/src/remove_identity_equiv.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// 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.

use num_complex::Complex64;
use num_complex::ComplexFloat;
use pyo3::prelude::*;
use rustworkx_core::petgraph::stable_graph::NodeIndex;

use crate::nlayout::PhysicalQubit;
use crate::target_transpiler::Target;
use qiskit_circuit::dag_circuit::DAGCircuit;
use qiskit_circuit::operations::Operation;
use qiskit_circuit::operations::OperationRef;
use qiskit_circuit::operations::Param;
use qiskit_circuit::operations::StandardGate;
use qiskit_circuit::packed_instruction::PackedInstruction;

#[pyfunction]
#[pyo3(signature=(dag, approx_degree=Some(1.0), target=None))]
fn remove_identity_equiv(
dag: &mut DAGCircuit,
approx_degree: Option<f64>,
target: Option<&Target>,
) {
let mut remove_list: Vec<NodeIndex> = Vec::new();

let get_error_cutoff = |inst: &PackedInstruction| -> f64 {
match approx_degree {
Some(degree) => {
if degree == 1.0 {
f64::EPSILON
} else {
match target {
Some(target) => {
let qargs: Vec<PhysicalQubit> = dag
.get_qargs(inst.qubits)
.iter()
.map(|x| PhysicalQubit::new(x.0))
.collect();
let error_rate = target.get_error(inst.op.name(), qargs.as_slice());
match error_rate {
Some(err) => err * degree,
None => f64::EPSILON.max(1. - degree),
}
}
None => f64::EPSILON.max(1. - degree),
}
}
}
None => match target {
Some(target) => {
let qargs: Vec<PhysicalQubit> = dag
.get_qargs(inst.qubits)
.iter()
.map(|x| PhysicalQubit::new(x.0))
.collect();
let error_rate = target.get_error(inst.op.name(), qargs.as_slice());
match error_rate {
Some(err) => err,
None => f64::EPSILON,
}
}
None => f64::EPSILON,
},
}
};

for op_node in dag.op_nodes(false) {
let inst = dag.dag()[op_node].unwrap_operation();
match inst.op.view() {
OperationRef::Standard(gate) => {
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
let (dim, trace) = match gate {
StandardGate::RXGate | StandardGate::RYGate | StandardGate::RZGate => {
if let Param::Float(theta) = inst.params_view()[0] {
let trace = (theta / 2.).cos() * 2.;
(2., trace)
} else {
continue;
}
}
StandardGate::RXXGate
| StandardGate::RYYGate
| StandardGate::RZZGate
| StandardGate::RZXGate => {
if let Param::Float(theta) = inst.params_view()[0] {
let trace = (theta / 2.).cos() * 4.;
(4., trace)
} else {
continue;
}
}
_ => {
// Skip global phase gate
if gate.num_qubits() < 1 {
continue;
}
if let Some(matrix) = gate.matrix(inst.params_view()) {
let dim = matrix.shape()[0] as f64;
let trace = matrix.diag().iter().sum::<Complex64>().abs();
(dim, trace)
} else {
continue;
}
}
};
let error = get_error_cutoff(inst);
let f_pro = (trace / dim).powi(2);
let gate_fidelity = (dim * f_pro + 1.) / (dim + 1.);
if (1. - gate_fidelity).abs() < error {
remove_list.push(op_node)
}
}
OperationRef::Gate(gate) => {
// Skip global phase like gate
if gate.num_qubits() < 1 {
continue;
}
if let Some(matrix) = gate.matrix(inst.params_view()) {
let error = get_error_cutoff(inst);
let dim = matrix.shape()[0] as f64;
let trace: Complex64 = matrix.diag().iter().sum();
let f_pro = (trace / dim).abs().powi(2);
let gate_fidelity = (dim * f_pro + 1.) / (dim + 1.);
if (1. - gate_fidelity).abs() < error {
remove_list.push(op_node)
}
}
}
_ => continue,
}
}
for node in remove_list {
dag.remove_op_node(node);
}
}

pub fn remove_identity_equiv_mod(m: &Bound<PyModule>) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(remove_identity_equiv))?;
Ok(())
}
1 change: 1 addition & 0 deletions crates/pyext/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ fn _accelerate(m: &Bound<PyModule>) -> PyResult<()> {
add_submodule(m, ::qiskit_accelerate::optimize_1q_gates::optimize_1q_gates, "optimize_1q_gates")?;
add_submodule(m, ::qiskit_accelerate::pauli_exp_val::pauli_expval, "pauli_expval")?;
add_submodule(m, ::qiskit_accelerate::remove_diagonal_gates_before_measure::remove_diagonal_gates_before_measure, "remove_diagonal_gates_before_measure")?;
add_submodule(m, ::qiskit_accelerate::remove_identity_equiv::remove_identity_equiv_mod, "remove_identity_equiv")?;
add_submodule(m, ::qiskit_accelerate::results::results, "results")?;
add_submodule(m, ::qiskit_accelerate::sabre::sabre, "sabre")?;
add_submodule(m, ::qiskit_accelerate::sampled_exp_val::sampled_exp_val, "sampled_exp_val")?;
Expand Down
1 change: 1 addition & 0 deletions qiskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
sys.modules["qiskit._accelerate.inverse_cancellation"] = _accelerate.inverse_cancellation
sys.modules["qiskit._accelerate.check_map"] = _accelerate.check_map
sys.modules["qiskit._accelerate.filter_op_nodes"] = _accelerate.filter_op_nodes
sys.modules["qiskit._accelerate.remove_identity_equiv"] = _accelerate.remove_identity_equiv

from qiskit.exceptions import QiskitError, MissingOptionalLibraryError

Expand Down
2 changes: 2 additions & 0 deletions qiskit/transpiler/passes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
NormalizeRXAngle
OptimizeAnnotated
Split2QUnitaries
RemoveIdentityEquivalent

Calibration
=============
Expand Down Expand Up @@ -247,6 +248,7 @@
from .optimization import ElidePermutations
from .optimization import NormalizeRXAngle
from .optimization import OptimizeAnnotated
from .optimization import RemoveIdentityEquivalent
from .optimization import Split2QUnitaries

# circuit analysis
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,5 +38,6 @@
from .elide_permutations import ElidePermutations
from .normalize_rx_angle import NormalizeRXAngle
from .optimize_annotated import OptimizeAnnotated
from .remove_identity_equiv import RemoveIdentityEquivalent
from .split_2q_unitaries import Split2QUnitaries
from .collect_and_collapse import CollectAndCollapse
69 changes: 69 additions & 0 deletions qiskit/transpiler/passes/optimization/remove_identity_equiv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# 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

from qiskit.dagcircuit import DAGCircuit
from qiskit.transpiler.target import Target
from qiskit.transpiler.basepasses import TransformationPass
from qiskit._accelerate.remove_identity_equiv import remove_identity_equiv


class RemoveIdentityEquivalent(TransformationPass):
r"""Remove gates with negligible effects.

Removes gates whose effect is close to an identity operation, up to the specified
tolerance. Zero qubit gates such as :class:`.GlobalPhaseGate` are not considered
by this pass.

For a cutoff fidelity :math:`f`, this pass removes gates whose average
gate fidelity with respect to the identity is below :math:`f`. Concretely,
a gate :math:`G` is removed if :math:`\bar F < f` where

.. math::

\bar{F} = \frac{1 + F_{\text{process}}{1 + d}

F_{\text{process}} = \frac{|\mathrm{Tr}(G)|^2}{d^2}

where :math:`d = 2^n` is the dimension of the gate for :math:`n` qubits.
"""
mtreinish marked this conversation as resolved.
Show resolved Hide resolved

def __init__(
self, *, approximation_degree: float | None = 1.0, target: None | Target = None
) -> None:
"""Initialize the transpiler pass.

Args:
approximation_degree: The degree to approximate for the equivalence check. This can be a
floating point value between 0 and 1, or ``None``. If the value is 1 this does not
approximate above floating point precision. For a value < 1 this is used as a scaling
factor for the cutoff fidelity. If the value is ``None`` this approximates up to the
fidelity for the gate specified in ``target``.

target: If ``approximation_degree`` is set to ``None`` and a :class:`.Target` is provided
for this field the tolerance for determining whether an operation is equivalent to
identity will be set to the reported error rate in the target. If
``approximation_degree`` (the default) this has no effect, if
``approximation_degree=None`` it uses the error rate specified in the ``Target`` for
the gate being evaluated, and a numeric value other than 1 with ``target`` set is
used as a scaling factor of the target's error rate.
"""
super().__init__()
self._approximation_degree = approximation_degree
self._target = target

def run(self, dag: DAGCircuit) -> DAGCircuit:
remove_identity_equiv(dag, self._approximation_degree, self._target)
return dag
29 changes: 29 additions & 0 deletions releasenotes/notes/remove_identity_equiv-9c627c8c35b2298a.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
features_transpiler:
- |
Added a new transpiler pass, :class:`.RemoveIdentityEquivalent` that is used
to remove gates that are equivalent to an identity up to some tolerance.
For example if you had a circuit like:

.. plot::

from qiskit.circuit import QuantumCircuit

qc = QuantumCircuit(2)
qc.cp(1e-20, 0, 1)
qc.draw("mpl")

running the pass would eliminate the :class:`.CPhaseGate`:

.. plot::
:include-source:

from qiskit.circuit import QuantumCircuit
from qiskit.transpiler.passes import RemoveIdentityEquivalent

qc = QuantumCircuit(2)
qc.cp(1e-20, 0, 1)

removal_pass = RemoveIdentityEquivalent()
result = removal_pass(qc)
result.draw("mpl")
Loading