From 254ba83dc637a22a91cee0a0cd1c453f89ca55da Mon Sep 17 00:00:00 2001
From: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com>
Date: Thu, 5 Sep 2024 18:36:03 -0400
Subject: [PATCH 01/11] Fix documentation for utils.optionals data values
(#13098)
---
qiskit/utils/optionals.py | 323 ++++++++++++++++++++------------------
1 file changed, 173 insertions(+), 150 deletions(-)
diff --git a/qiskit/utils/optionals.py b/qiskit/utils/optionals.py
index f2e6c860faaa..c5d0d69d11a1 100644
--- a/qiskit/utils/optionals.py
+++ b/qiskit/utils/optionals.py
@@ -25,172 +25,195 @@
Qiskit Components
-----------------
-.. list-table::
- :widths: 25 75
+.. py:data:: HAS_AER
- * - .. py:data:: HAS_AER
- - `Qiskit Aer ` provides high-performance simulators for
- the quantum circuits constructed within Qiskit.
+ `Qiskit Aer ` provides high-performance simulators for
+ the quantum circuits constructed within Qiskit.
- * - .. py:data:: HAS_IBMQ
- - The :mod:`Qiskit IBMQ Provider ` is used for accessing IBM Quantum
- hardware in the IBM cloud.
+.. py:data:: HAS_IBMQ
- * - .. py:data:: HAS_IGNIS
- - :mod:`Qiskit Ignis ` provides tools for quantum hardware verification, noise
- characterization, and error correction.
+ The :mod:`Qiskit IBMQ Provider ` is used for accessing IBM Quantum
+ hardware in the IBM cloud.
- * - .. py:data:: HAS_TOQM
- - `Qiskit TOQM `__ provides transpiler passes
- for the `Time-optimal Qubit mapping algorithm `__.
+.. py:data:: HAS_IGNIS
+
+ :mod:`Qiskit Ignis ` provides tools for quantum hardware verification, noise
+ characterization, and error correction.
+
+.. py:data:: HAS_TOQM
+
+ `Qiskit TOQM `__ provides transpiler passes
+ for the `Time-optimal Qubit mapping algorithm `__.
External Python Libraries
-------------------------
-.. list-table::
- :widths: 25 75
-
- * - .. py:data:: HAS_CONSTRAINT
- - `python-constraint `__ is a
- constraint satisfaction problem solver, used in the :class:`~.CSPLayout` transpiler pass.
-
- * - .. py:data:: HAS_CPLEX
- - The `IBM CPLEX Optimizer `__ is a
- high-performance mathematical programming solver for linear, mixed-integer and quadratic
- programming. This is no longer by Qiskit, but it weas historically and the optional
- remains for backwards compatibility.
-
- * - .. py:data:: HAS_CVXPY
- - `CVXPY `__ is a Python package for solving convex optimization
- problems. It is required for calculating diamond norms with
- :func:`.quantum_info.diamond_norm`.
-
- * - .. py:data:: HAS_DOCPLEX
- - `IBM Decision Optimization CPLEX Modelling
- `__ is a library for prescriptive
- analysis. Like CPLEX, this is no longer by Qiskit, but it weas historically and the
- optional remains for backwards compatibility.
-
- * - .. py:data:: HAS_FIXTURES
- - The test suite has additional features that are available if the optional `fixtures
- `__ module is installed. This generally also needs
- :data:`HAS_TESTTOOLS` as well. This is generally only needed for Qiskit developers.
-
- * - .. py:data:: HAS_IPYTHON
- - If `the IPython kernel `__ is available, certain additional
- visualizations and line magics are made available.
-
- * - .. py:data:: HAS_IPYWIDGETS
- - Monitoring widgets for jobs running on external backends can be provided if `ipywidgets
- `__ is available.
-
- * - .. py:data:: HAS_JAX
- - Some methods of gradient calculation within :mod:`.opflow.gradients` require `JAX
- `__ for autodifferentiation.
-
- * - .. py:data:: HAS_JUPYTER
- - Some of the tests require a complete `Jupyter `__ installation to test
- interactivity features.
-
- * - .. py:data:: HAS_MATPLOTLIB
- - Qiskit provides several visualization tools in the :mod:`.visualization` module.
- Almost all of these are built using `Matplotlib `__, which must
- be installed in order to use them.
-
- * - .. py:data:: HAS_NETWORKX
- - No longer used by Qiskit. Internally, Qiskit now uses the high-performance `rustworkx
- `__ library as a core dependency, and during the
- change-over period, it was sometimes convenient to convert things into the Python-only
- `NetworkX `__ format. Some tests of application modules, such as
- `Qiskit Nature `__ still use NetworkX.
-
- * - .. py:data:: HAS_NLOPT
- - `NLopt `__ is a nonlinear optimization library,
- used by the global optimizers in the :mod:`.algorithms.optimizers` module.
-
- * - .. py:data:: HAS_PIL
- - PIL is a Python image-manipulation library. Qiskit actually uses the `pillow
- `__ fork of PIL if it is available when generating
- certain visualizations, for example of both :class:`.QuantumCircuit` and
- :class:`.DAGCircuit` in certain modes.
-
- * - .. py:data:: HAS_PYDOT
- - For some graph visualizations, Qiskit uses `pydot `__ as an
- interface to GraphViz (see :data:`HAS_GRAPHVIZ`).
-
- * - .. py:data:: HAS_PYGMENTS
- - Pygments is a code highlighter and formatter used by many environments that involve rich
- display of code blocks, including Sphinx and Jupyter. Qiskit uses this when producing rich
- output for these environments.
-
- * - .. py:data:: HAS_PYLATEX
- - Various LaTeX-based visualizations, especially the circuit drawers, need access to the
- `pylatexenc `__ project to work correctly.
-
- * - .. py:data:: HAS_QASM3_IMPORT
- - The functions :func:`.qasm3.load` and :func:`.qasm3.loads` for importing OpenQASM 3 programs
- into :class:`.QuantumCircuit` instances use `an external importer package
- `__.
-
- * - .. py:data:: HAS_SEABORN
- - Qiskit provides several visualization tools in the :mod:`.visualization` module. Some
- of these are built using `Seaborn `__, which must be installed
- in order to use them.
-
- * - .. py:data:: HAS_SKLEARN
- - Some of the gradient functions in :mod:`.opflow.gradients` use regularisation methods from
- `Scikit Learn `__.
-
- * - .. py:data:: HAS_SKQUANT
- - Some of the optimisers in :mod:`.algorithms.optimizers` are based on those found in `Scikit
- Quant `__, which must be installed to use
- them.
-
- * - .. py:data:: HAS_SQSNOBFIT
- - `SQSnobFit `__ is a library for the "stable noisy
- optimization by branch and fit" algorithm. It is used by the :class:`.SNOBFIT` optimizer.
-
- * - .. py:data:: HAS_SYMENGINE
- - `Symengine `__ is a fast C++ backend for the
- symbolic-manipulation library `Sympy `__. Qiskit uses
- special methods from Symengine to accelerate its handling of
- :class:`~.circuit.Parameter`\\ s if available.
-
- * - .. py:data:: HAS_TESTTOOLS
- - Qiskit's test suite has more advanced functionality available if the optional
- `testtools `__ library is installed. This is generally
- only needed for Qiskit developers.
-
- * - .. py:data:: HAS_TWEEDLEDUM
- - `Tweedledum `__ is an extension library for
- synthesis and optimization of circuits that may involve classical oracles. Qiskit's
- :class:`.PhaseOracle` uses this, which is used in turn by amplification algorithms via
- the :class:`.AmplificationProblem`.
-
- * - .. py:data:: HAS_Z3
- - `Z3 `__ is a theorem prover, used in the
- :class:`.CrosstalkAdaptiveSchedule` and :class:`.HoareOptimizer` transpiler passes.
+.. py:data:: HAS_CONSTRAINT
+
+ `python-constraint `__ is a
+ constraint satisfaction problem solver, used in the :class:`~.CSPLayout` transpiler pass.
+
+.. py:data:: HAS_CPLEX
+
+ The `IBM CPLEX Optimizer `__ is a
+ high-performance mathematical programming solver for linear, mixed-integer and quadratic
+ programming. This is no longer by Qiskit, but it weas historically and the optional
+ remains for backwards compatibility.
+
+.. py:data:: HAS_CVXPY
+
+ `CVXPY `__ is a Python package for solving convex optimization
+ problems. It is required for calculating diamond norms with
+ :func:`.quantum_info.diamond_norm`.
+
+.. py:data:: HAS_DOCPLEX
+
+ `IBM Decision Optimization CPLEX Modelling
+ `__ is a library for prescriptive
+ analysis. Like CPLEX, this is no longer by Qiskit, but it weas historically and the
+ optional remains for backwards compatibility.
+
+.. py:data:: HAS_FIXTURES
+
+ The test suite has additional features that are available if the optional `fixtures
+ `__ module is installed. This generally also needs
+ :data:`HAS_TESTTOOLS` as well. This is generally only needed for Qiskit developers.
+
+.. py:data:: HAS_IPYTHON
+
+ If `the IPython kernel `__ is available, certain additional
+ visualizations and line magics are made available.
+
+.. py:data:: HAS_IPYWIDGETS
+
+ Monitoring widgets for jobs running on external backends can be provided if `ipywidgets
+ `__ is available.
+
+.. py:data:: HAS_JAX
+
+ Some methods of gradient calculation within :mod:`.opflow.gradients` require `JAX
+ `__ for autodifferentiation.
+
+.. py:data:: HAS_JUPYTER
+
+ Some of the tests require a complete `Jupyter `__ installation to test
+ interactivity features.
+
+.. py:data:: HAS_MATPLOTLIB
+
+ Qiskit provides several visualization tools in the :mod:`.visualization` module.
+ Almost all of these are built using `Matplotlib `__, which must
+ be installed in order to use them.
+
+.. py:data:: HAS_NETWORKX
+
+ No longer used by Qiskit. Internally, Qiskit now uses the high-performance `rustworkx
+ `__ library as a core dependency, and during the
+ change-over period, it was sometimes convenient to convert things into the Python-only
+ `NetworkX `__ format. Some tests of application modules, such as
+ `Qiskit Nature `__ still use NetworkX.
+
+.. py:data:: HAS_NLOPT
+
+ `NLopt `__ is a nonlinear optimization library,
+ used by the global optimizers in the :mod:`.algorithms.optimizers` module.
+
+.. py:data:: HAS_PIL
+
+ PIL is a Python image-manipulation library. Qiskit actually uses the `pillow
+ `__ fork of PIL if it is available when generating
+ certain visualizations, for example of both :class:`.QuantumCircuit` and
+ :class:`.DAGCircuit` in certain modes.
+
+.. py:data:: HAS_PYDOT
+
+ For some graph visualizations, Qiskit uses `pydot `__ as an
+ interface to GraphViz (see :data:`HAS_GRAPHVIZ`).
+
+.. py:data:: HAS_PYGMENTS
+
+ Pygments is a code highlighter and formatter used by many environments that involve rich
+ display of code blocks, including Sphinx and Jupyter. Qiskit uses this when producing rich
+ output for these environments.
+
+.. py:data:: HAS_PYLATEX
+
+ Various LaTeX-based visualizations, especially the circuit drawers, need access to the
+ `pylatexenc `__ project to work correctly.
+
+.. py:data:: HAS_QASM3_IMPORT
+
+ The functions :func:`.qasm3.load` and :func:`.qasm3.loads` for importing OpenQASM 3 programs
+ into :class:`.QuantumCircuit` instances use `an external importer package
+ `__.
+
+.. py:data:: HAS_SEABORN
+
+ Qiskit provides several visualization tools in the :mod:`.visualization` module. Some
+ of these are built using `Seaborn `__, which must be installed
+ in order to use them.
+
+.. py:data:: HAS_SKLEARN
+
+ Some of the gradient functions in :mod:`.opflow.gradients` use regularisation methods from
+ `Scikit Learn `__.
+
+.. py:data:: HAS_SKQUANT
+
+ Some of the optimisers in :mod:`.algorithms.optimizers` are based on those found in `Scikit
+ Quant `__, which must be installed to use
+ them.
+
+.. py:data:: HAS_SQSNOBFIT
+
+ `SQSnobFit `__ is a library for the "stable noisy
+ optimization by branch and fit" algorithm. It is used by the :class:`.SNOBFIT` optimizer.
+
+.. py:data:: HAS_SYMENGINE
+
+ `Symengine `__ is a fast C++ backend for the
+ symbolic-manipulation library `Sympy `__. Qiskit uses
+ special methods from Symengine to accelerate its handling of
+ :class:`~.circuit.Parameter`\\ s if available.
+
+.. py:data:: HAS_TESTTOOLS
+
+ Qiskit's test suite has more advanced functionality available if the optional
+ `testtools `__ library is installed. This is generally
+ only needed for Qiskit developers.
+
+.. py:data:: HAS_TWEEDLEDUM
+
+ `Tweedledum `__ is an extension library for
+ synthesis and optimization of circuits that may involve classical oracles. Qiskit's
+ :class:`.PhaseOracle` uses this, which is used in turn by amplification algorithms via
+ the :class:`.AmplificationProblem`.
+
+.. py:data:: HAS_Z3
+
+ `Z3 `__ is a theorem prover, used in the
+ :class:`.CrosstalkAdaptiveSchedule` and :class:`.HoareOptimizer` transpiler passes.
External Command-Line Tools
---------------------------
-.. list-table::
- :widths: 25 75
+.. py:data:: HAS_GRAPHVIZ
+
+ For some graph visualizations, Qiskit uses the `GraphViz `__
+ visualization tool via its ``pydot`` interface (see :data:`HAS_PYDOT`).
+
+.. py:data:: HAS_PDFLATEX
- * - .. py:data:: HAS_GRAPHVIZ
- - For some graph visualizations, Qiskit uses the `GraphViz `__
- visualization tool via its ``pydot`` interface (see :data:`HAS_PYDOT`).
+ Visualization tools that use LaTeX in their output, such as the circuit drawers, require
+ ``pdflatex`` to be available. You will generally need to ensure that you have a working
+ LaTeX installation available, and the ``qcircuit.tex`` package.
- * - .. py:data:: HAS_PDFLATEX
- - Visualization tools that use LaTeX in their output, such as the circuit drawers, require
- ``pdflatex`` to be available. You will generally need to ensure that you have a working
- LaTeX installation available, and the ``qcircuit.tex`` package.
+.. py:data:: HAS_PDFTOCAIRO
- * - .. py:data:: HAS_PDFTOCAIRO
- - Visualization tools that convert LaTeX-generated files into rasterized images use the
- ``pdftocairo`` tool. This is part of the `Poppler suite of PDF tools
- `__.
+ Visualization tools that convert LaTeX-generated files into rasterized images use the
+ ``pdftocairo`` tool. This is part of the `Poppler suite of PDF tools
+ `__.
Lazy Checker Classes
From 8982dbde157eafa8e104495472115f4257a502fa Mon Sep 17 00:00:00 2001
From: Matthew Treinish
Date: Thu, 5 Sep 2024 19:00:08 -0400
Subject: [PATCH 02/11] Fully port FilterOpNodes to Rust (#13052)
* Fully port FilterOpNodes to Rust
This commit ports the FilterOpNodes pass to rust. This pass is
exceedingly simple and just runs a filter function over all the op
nodes and removes nodes that match the filter. However, the API for
the class exposes that filter function interface as a user provided
Python callable. So for the current pass we need to retain that python
callback. This limits the absolute performance of this pass because
we're bottlenecked by calling python.
Looking to the future, this commit adds a rust native method to
DAGCircuit to perform this filtering with a rust predicate FnMut. It
isn't leveraged by the python implementation because of layer mismatch
for the efficient rust interface and Python working with `DAGOpNode`
objects. A function using that interface is added to filter labeled
nodes. In the preset pass manager we only use FilterOpNodes to remove
nodes with a specific label (which is used to identify temporary
barriers created by qiskit). In a follow up we should consider
leveraging this new function to build a new pass specifically for
this use case.
Fixes #12263
Part of #12208
* Make filter_op_nodes() infallible
The filter_op_nodes() method originally returned a Result<()> to handle
a predicate that was fallible. This was because the original intent for
the method was to use it with Python callbacks in the predicate. But
because of differences between the rust API and the Python API this
wasn't feasible as was originally planned. So this Result<()> return
wasn't used anymore. This commit reworks it to make the
filter_op_nodes() infallible and the predicate a user provides also only
returns `bool` and not `Result`.
* Rename filter_labelled_op to filter_labeled_op
---
crates/accelerate/src/filter_op_nodes.rs | 63 +++++++++++++++++++
crates/accelerate/src/lib.rs | 1 +
.../remove_diagonal_gates_before_measure.rs | 4 +-
crates/circuit/src/dag_circuit.rs | 19 ++++++
crates/circuit/src/packed_instruction.rs | 7 +++
crates/pyext/src/lib.rs | 6 +-
qiskit/__init__.py | 1 +
.../passes/utils/filter_op_nodes.py | 6 +-
8 files changed, 101 insertions(+), 6 deletions(-)
create mode 100644 crates/accelerate/src/filter_op_nodes.rs
diff --git a/crates/accelerate/src/filter_op_nodes.rs b/crates/accelerate/src/filter_op_nodes.rs
new file mode 100644
index 000000000000..7c41391f3788
--- /dev/null
+++ b/crates/accelerate/src/filter_op_nodes.rs
@@ -0,0 +1,63 @@
+// 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 pyo3::prelude::*;
+use pyo3::wrap_pyfunction;
+
+use qiskit_circuit::dag_circuit::DAGCircuit;
+use qiskit_circuit::packed_instruction::PackedInstruction;
+use rustworkx_core::petgraph::stable_graph::NodeIndex;
+
+#[pyfunction]
+#[pyo3(name = "filter_op_nodes")]
+pub fn py_filter_op_nodes(
+ py: Python,
+ dag: &mut DAGCircuit,
+ predicate: &Bound,
+) -> PyResult<()> {
+ let callable = |node: NodeIndex| -> PyResult {
+ let dag_op_node = dag.get_node(py, node)?;
+ predicate.call1((dag_op_node,))?.extract()
+ };
+ let mut remove_nodes: Vec = Vec::new();
+ for node in dag.op_nodes(true) {
+ if !callable(node)? {
+ remove_nodes.push(node);
+ }
+ }
+ for node in remove_nodes {
+ dag.remove_op_node(node);
+ }
+ Ok(())
+}
+
+/// Remove any nodes that have the provided label set
+///
+/// Args:
+/// dag (DAGCircuit): The dag circuit to filter the ops from
+/// label (str): The label to filter nodes on
+#[pyfunction]
+pub fn filter_labeled_op(dag: &mut DAGCircuit, label: String) {
+ let predicate = |node: &PackedInstruction| -> bool {
+ match node.label() {
+ Some(inst_label) => inst_label != label,
+ None => false,
+ }
+ };
+ dag.filter_op_nodes(predicate);
+}
+
+pub fn filter_op_nodes_mod(m: &Bound) -> PyResult<()> {
+ m.add_wrapped(wrap_pyfunction!(py_filter_op_nodes))?;
+ m.add_wrapped(wrap_pyfunction!(filter_labeled_op))?;
+ Ok(())
+}
diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs
index e8760ee2c616..78eea97faad0 100644
--- a/crates/accelerate/src/lib.rs
+++ b/crates/accelerate/src/lib.rs
@@ -22,6 +22,7 @@ pub mod dense_layout;
pub mod edge_collections;
pub mod error_map;
pub mod euler_one_qubit_decomposer;
+pub mod filter_op_nodes;
pub mod isometry;
pub mod nlayout;
pub mod optimize_1q_gates;
diff --git a/crates/accelerate/src/remove_diagonal_gates_before_measure.rs b/crates/accelerate/src/remove_diagonal_gates_before_measure.rs
index 10916a77fca8..cf2c738f131a 100644
--- a/crates/accelerate/src/remove_diagonal_gates_before_measure.rs
+++ b/crates/accelerate/src/remove_diagonal_gates_before_measure.rs
@@ -49,7 +49,9 @@ fn run_remove_diagonal_before_measure(dag: &mut DAGCircuit) -> PyResult<()> {
let mut nodes_to_remove = Vec::new();
for index in dag.op_nodes(true) {
let node = &dag.dag[index];
- let NodeType::Operation(inst) = node else {panic!()};
+ let NodeType::Operation(inst) = node else {
+ panic!()
+ };
if inst.op.name() == "measure" {
let predecessor = (dag.quantum_predecessors(index))
diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs
index 381ef25b7a7e..32b3a77ed24c 100644
--- a/crates/circuit/src/dag_circuit.rs
+++ b/crates/circuit/src/dag_circuit.rs
@@ -5794,6 +5794,25 @@ impl DAGCircuit {
}
}
+ // Filter any nodes that don't match a given predicate function
+ pub fn filter_op_nodes(&mut self, mut predicate: F)
+ where
+ F: FnMut(&PackedInstruction) -> bool,
+ {
+ let mut remove_nodes: Vec = Vec::new();
+ for node in self.op_nodes(true) {
+ let NodeType::Operation(op) = &self.dag[node] else {
+ unreachable!()
+ };
+ if !predicate(op) {
+ remove_nodes.push(node);
+ }
+ }
+ for node in remove_nodes {
+ self.remove_op_node(node);
+ }
+ }
+
pub fn op_nodes_by_py_type<'a>(
&'a self,
op: &'a Bound,
diff --git a/crates/circuit/src/packed_instruction.rs b/crates/circuit/src/packed_instruction.rs
index 77ca0c6c02dd..df8f9801314a 100644
--- a/crates/circuit/src/packed_instruction.rs
+++ b/crates/circuit/src/packed_instruction.rs
@@ -553,6 +553,13 @@ impl PackedInstruction {
.and_then(|extra| extra.condition.as_ref())
}
+ #[inline]
+ pub fn label(&self) -> Option<&str> {
+ self.extra_attrs
+ .as_ref()
+ .and_then(|extra| extra.label.as_deref())
+ }
+
/// Build a reference to the Python-space operation object (the `Gate`, etc) packed into this
/// instruction. This may construct the reference if the `PackedInstruction` is a standard
/// gate with no already stored operation.
diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs
index 1478fb367a13..49e44bffa2ec 100644
--- a/crates/pyext/src/lib.rs
+++ b/crates/pyext/src/lib.rs
@@ -16,8 +16,9 @@ use qiskit_accelerate::{
circuit_library::circuit_library, commutation_analysis::commutation_analysis,
commutation_checker::commutation_checker, convert_2q_block_matrix::convert_2q_block_matrix,
dense_layout::dense_layout, error_map::error_map,
- euler_one_qubit_decomposer::euler_one_qubit_decomposer, isometry::isometry, nlayout::nlayout,
- optimize_1q_gates::optimize_1q_gates, pauli_exp_val::pauli_expval,
+ euler_one_qubit_decomposer::euler_one_qubit_decomposer, filter_op_nodes::filter_op_nodes_mod,
+ isometry::isometry, nlayout::nlayout, optimize_1q_gates::optimize_1q_gates,
+ pauli_exp_val::pauli_expval,
remove_diagonal_gates_before_measure::remove_diagonal_gates_before_measure, results::results,
sabre::sabre, sampled_exp_val::sampled_exp_val, sparse_pauli_op::sparse_pauli_op,
star_prerouting::star_prerouting, stochastic_swap::stochastic_swap, synthesis::synthesis,
@@ -46,6 +47,7 @@ fn _accelerate(m: &Bound) -> PyResult<()> {
add_submodule(m, dense_layout, "dense_layout")?;
add_submodule(m, error_map, "error_map")?;
add_submodule(m, euler_one_qubit_decomposer, "euler_one_qubit_decomposer")?;
+ add_submodule(m, filter_op_nodes_mod, "filter_op_nodes")?;
add_submodule(m, isometry, "isometry")?;
add_submodule(m, nlayout, "nlayout")?;
add_submodule(m, optimize_1q_gates, "optimize_1q_gates")?;
diff --git a/qiskit/__init__.py b/qiskit/__init__.py
index d9979c9d4d92..3cc10bf96a31 100644
--- a/qiskit/__init__.py
+++ b/qiskit/__init__.py
@@ -92,6 +92,7 @@
sys.modules["qiskit._accelerate.commutation_checker"] = _accelerate.commutation_checker
sys.modules["qiskit._accelerate.commutation_analysis"] = _accelerate.commutation_analysis
sys.modules["qiskit._accelerate.synthesis.linear_phase"] = _accelerate.synthesis.linear_phase
+sys.modules["qiskit._accelerate.filter_op_nodes"] = _accelerate.filter_op_nodes
from qiskit.exceptions import QiskitError, MissingOptionalLibraryError
diff --git a/qiskit/transpiler/passes/utils/filter_op_nodes.py b/qiskit/transpiler/passes/utils/filter_op_nodes.py
index 344d2280e3f4..75b824332aee 100644
--- a/qiskit/transpiler/passes/utils/filter_op_nodes.py
+++ b/qiskit/transpiler/passes/utils/filter_op_nodes.py
@@ -18,6 +18,8 @@
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.passes.utils import control_flow
+from qiskit._accelerate.filter_op_nodes import filter_op_nodes
+
class FilterOpNodes(TransformationPass):
"""Remove all operations that match a filter function
@@ -59,7 +61,5 @@ def __init__(self, predicate: Callable[[DAGOpNode], bool]):
@control_flow.trivial_recurse
def run(self, dag: DAGCircuit) -> DAGCircuit:
"""Run the RemoveBarriers pass on `dag`."""
- for node in dag.op_nodes():
- if not self.predicate(node):
- dag.remove_op_node(node)
+ filter_op_nodes(dag, self.predicate)
return dag
From b80b454c8763f650b134a514faf959aee51d9dd3 Mon Sep 17 00:00:00 2001
From: Matthew Treinish
Date: Thu, 5 Sep 2024 19:29:26 -0400
Subject: [PATCH 03/11] Fix clippy failures with Rust 1.81.0 (#13100)
The recently released Rust 1.81.0 introduced some new on by default
clippy rules and these rules are flagging issues in the rust code in the
library. While we use Rust 1.70 for clippy in CI and these won't cause
failures until we raise our MSRV to >= 1.81.0 these clippy
warnings/failures are still good to fix as the either make the code more
consise and/or efficient. This commit fixes these issues identified by
clippy.
---
crates/accelerate/src/results/marginalization.rs | 9 ++-------
crates/circuit/src/operations.rs | 5 +----
2 files changed, 3 insertions(+), 11 deletions(-)
diff --git a/crates/accelerate/src/results/marginalization.rs b/crates/accelerate/src/results/marginalization.rs
index 5260287e771d..b9e7ec4ba65c 100644
--- a/crates/accelerate/src/results/marginalization.rs
+++ b/crates/accelerate/src/results/marginalization.rs
@@ -26,16 +26,11 @@ fn marginalize(
indices: Option>,
) -> HashMap {
let mut out_counts: HashMap = HashMap::with_capacity(counts.len());
- let clbit_size = counts
- .keys()
- .next()
- .unwrap()
- .replace(|c| c == '_' || c == ' ', "")
- .len();
+ let clbit_size = counts.keys().next().unwrap().replace(['_', ' '], "").len();
let all_indices: Vec = (0..clbit_size).collect();
counts
.iter()
- .map(|(k, v)| (k.replace(|c| c == '_' || c == ' ', ""), *v))
+ .map(|(k, v)| (k.replace(['_', ' '], ""), *v))
.for_each(|(k, v)| match &indices {
Some(indices) => {
if all_indices == *indices {
diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs
index 8cc2256af518..a3fd2fc83c3d 100644
--- a/crates/circuit/src/operations.rs
+++ b/crates/circuit/src/operations.rs
@@ -2225,10 +2225,7 @@ impl Operation for PyGate {
fn standard_gate(&self) -> Option {
Python::with_gil(|py| -> Option {
match self.gate.getattr(py, intern!(py, "_standard_gate")) {
- Ok(stdgate) => match stdgate.extract(py) {
- Ok(out_gate) => out_gate,
- Err(_) => None,
- },
+ Ok(stdgate) => stdgate.extract(py).unwrap_or_default(),
Err(_) => None,
}
})
From 1a4193a1c52f73f081407b8f234da1b154198da6 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 6 Sep 2024 13:08:35 +0000
Subject: [PATCH 04/11] Bump bytemuck from 1.17.1 to 1.18.0 (#13105)
Bumps [bytemuck](https://github.com/Lokathor/bytemuck) from 1.17.1 to 1.18.0.
- [Changelog](https://github.com/Lokathor/bytemuck/blob/main/changelog.md)
- [Commits](https://github.com/Lokathor/bytemuck/compare/v1.17.1...v1.18.0)
---
updated-dependencies:
- dependency-name: bytemuck
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
Cargo.lock | 4 ++--
Cargo.toml | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 7921b7aeba83..9745b67b7405 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -120,9 +120,9 @@ dependencies = [
[[package]]
name = "bytemuck"
-version = "1.17.1"
+version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2"
+checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae"
dependencies = [
"bytemuck_derive",
]
diff --git a/Cargo.toml b/Cargo.toml
index 4f7f8ad86b96..2f5fd8ce077b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,7 +14,7 @@ license = "Apache-2.0"
#
# Each crate can add on specific features freely as it inherits.
[workspace.dependencies]
-bytemuck = "1.17"
+bytemuck = "1.18"
indexmap.version = "2.5.0"
hashbrown.version = "0.14.5"
num-bigint = "0.4"
From 7c409123a369c7df0b86f99ae00fefd41bc142fd Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 6 Sep 2024 13:34:52 +0000
Subject: [PATCH 05/11] Bump faer from 0.19.2 to 0.19.3 (#13104)
Bumps [faer](https://github.com/sarah-ek/faer-rs) from 0.19.2 to 0.19.3.
- [Changelog](https://github.com/sarah-ek/faer-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sarah-ek/faer-rs/commits)
---
updated-dependencies:
- dependency-name: faer
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
Cargo.lock | 4 ++--
crates/accelerate/Cargo.toml | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 9745b67b7405..d00620630e79 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -345,9 +345,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "faer"
-version = "0.19.2"
+version = "0.19.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe8894ad9275f1fd708e2f114f4059e80057ad3cc150b77a3f8a7991dd47ab37"
+checksum = "0821d176d7fd17ea91d8a5ce84c56413e11410f404e418bfd205acf40184d819"
dependencies = [
"bytemuck",
"coe-rs",
diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml
index f444a5c8ed66..4b5c18e5eb93 100644
--- a/crates/accelerate/Cargo.toml
+++ b/crates/accelerate/Cargo.toml
@@ -20,7 +20,7 @@ num-traits = "0.2"
num-complex.workspace = true
rustworkx-core.workspace = true
num-bigint.workspace = true
-faer = "0.19.2"
+faer = "0.19.3"
itertools.workspace = true
qiskit-circuit.workspace = true
thiserror.workspace = true
From 3d4bab2be7fd09f6dcf734a3386355cae6cded39 Mon Sep 17 00:00:00 2001
From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
Date: Fri, 6 Sep 2024 12:26:48 -0400
Subject: [PATCH 06/11] Add method to add instructions to a DAGCircuit from an
iterator of PackedInstruction (#13032)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Initial: Add add_from_iter method to DAGCircuit
- Introduce a method that adds a chain of `PackedInstruction` continuously avoiding the re-linking of each bit's output-node until the very end of the iterator.
- TODO: Add handling of vars
- Add header for a `from_iter` function that will create a `DAGCircuit` based on a chain of `PackedInstruction`.
* Fix: leverage new methods in layers
- Fix incorrect re-insertion of last_node.
* Fix: Keep track of Vars for add_from_iter
- Remove `from_iter`
* Fix: Incorrect modification of last nodes in `add_from_iter`.
- Use `entry` api to either modify or insert a value if missing.
* Fix: Cycling edges in when adding vars.
- A bug that adds duplicate edges to vars has been temporarily fixed. However, the root of this problem hasn't been found yet. A proper fix is pending. For now skip those instances.
* Fix: Remove set collecting all nodes to be connected.
- A set collecting all the new nodes to connect with a new node was preventing additional wires to connect to subsequent nodes.
* Fix: Adapt to #13033
* Refactor: `add_from_iter` is now called `extend` to stick with `Rust` nomenclature.
* Fix docstring
- Caught by @ElePT
Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com>
* Fix: Remove duplicate vars check
* Fix: Corrections from code review.
- Use Entry API to modify last nodes in the var.
- Build new_nodes with an allocated vec.
- Add comment explaining the removal of the edge between the output node and its predecessor.
* Fix: Improper use of `Entry API`.
- Use `or_insert_with` instead of `or_insert` to perform actions before inserting a value.
---------
Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com>
---
crates/circuit/src/dag_circuit.rs | 141 +++++++++++++++++++++++++++++-
1 file changed, 138 insertions(+), 3 deletions(-)
diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs
index 32b3a77ed24c..4d41660708d3 100644
--- a/crates/circuit/src/dag_circuit.rs
+++ b/crates/circuit/src/dag_circuit.rs
@@ -4350,9 +4350,7 @@ def _format(operand):
let mut new_layer = self.copy_empty_like(py, vars_mode)?;
- for (node, _) in op_nodes {
- new_layer.push_back(py, node.clone())?;
- }
+ new_layer.extend(py, op_nodes.iter().map(|(inst, _)| (*inst).clone()))?;
let new_layer_op_nodes = new_layer.op_nodes(false).filter_map(|node_index| {
match new_layer.dag.node_weight(node_index) {
@@ -6366,6 +6364,143 @@ impl DAGCircuit {
Err(DAGCircuitError::new_err("Specified node is not an op node"))
}
}
+
+ /// Extends the DAG with valid instances of [PackedInstruction]
+ pub fn extend(&mut self, py: Python, iter: I) -> PyResult>
+ where
+ I: IntoIterator,
+ {
+ // Create HashSets to keep track of each bit/var's last node
+ let mut qubit_last_nodes: HashMap = HashMap::default();
+ let mut clbit_last_nodes: HashMap = HashMap::default();
+ // TODO: Refactor once Vars are in rust
+ // Dict [ Var: (int, VarWeight)]
+ let vars_last_nodes: Bound = PyDict::new_bound(py);
+
+ // Consume into iterator to obtain size hint
+ let iter = iter.into_iter();
+ // Store new nodes to return
+ let mut new_nodes = Vec::with_capacity(iter.size_hint().1.unwrap_or_default());
+ for instr in iter {
+ let op_name = instr.op.name();
+ let (all_cbits, vars): (Vec, Option>) = {
+ if self.may_have_additional_wires(py, &instr) {
+ let mut clbits: HashSet =
+ HashSet::from_iter(self.cargs_interner.get(instr.clbits).iter().copied());
+ let (additional_clbits, additional_vars) =
+ self.additional_wires(py, instr.op.view(), instr.condition())?;
+ for clbit in additional_clbits {
+ clbits.insert(clbit);
+ }
+ (clbits.into_iter().collect(), Some(additional_vars))
+ } else {
+ (self.cargs_interner.get(instr.clbits).to_vec(), None)
+ }
+ };
+
+ // Increment the operation count
+ self.increment_op(op_name);
+
+ // Get the correct qubit indices
+ let qubits_id = instr.qubits;
+
+ // Insert op-node to graph.
+ let new_node = self.dag.add_node(NodeType::Operation(instr));
+ new_nodes.push(new_node);
+
+ // Check all the qubits in this instruction.
+ for qubit in self.qargs_interner.get(qubits_id) {
+ // Retrieve each qubit's last node
+ let qubit_last_node = *qubit_last_nodes.entry(*qubit).or_insert_with(|| {
+ // If the qubit is not in the last nodes collection, the edge between the output node and its predecessor.
+ // Then, store the predecessor's NodeIndex in the last nodes collection.
+ let output_node = self.qubit_io_map[qubit.0 as usize][1];
+ let (edge_id, predecessor_node) = self
+ .dag
+ .edges_directed(output_node, Incoming)
+ .next()
+ .map(|edge| (edge.id(), edge.source()))
+ .unwrap();
+ self.dag.remove_edge(edge_id);
+ predecessor_node
+ });
+ qubit_last_nodes
+ .entry(*qubit)
+ .and_modify(|val| *val = new_node);
+ self.dag
+ .add_edge(qubit_last_node, new_node, Wire::Qubit(*qubit));
+ }
+
+ // Check all the clbits in this instruction.
+ for clbit in all_cbits {
+ let clbit_last_node = *clbit_last_nodes.entry(clbit).or_insert_with(|| {
+ // If the qubit is not in the last nodes collection, the edge between the output node and its predecessor.
+ // Then, store the predecessor's NodeIndex in the last nodes collection.
+ let output_node = self.clbit_io_map[clbit.0 as usize][1];
+ let (edge_id, predecessor_node) = self
+ .dag
+ .edges_directed(output_node, Incoming)
+ .next()
+ .map(|edge| (edge.id(), edge.source()))
+ .unwrap();
+ self.dag.remove_edge(edge_id);
+ predecessor_node
+ });
+ clbit_last_nodes
+ .entry(clbit)
+ .and_modify(|val| *val = new_node);
+ self.dag
+ .add_edge(clbit_last_node, new_node, Wire::Clbit(clbit));
+ }
+
+ // If available, check all the vars in this instruction
+ for var in vars.iter().flatten() {
+ let var_last_node = if let Some(result) = vars_last_nodes.get_item(var)? {
+ let node: usize = result.extract()?;
+ vars_last_nodes.del_item(var)?;
+ NodeIndex::new(node)
+ } else {
+ // If the var is not in the last nodes collection, the edge between the output node and its predecessor.
+ // Then, store the predecessor's NodeIndex in the last nodes collection.
+ let output_node = self.var_output_map.get(py, var).unwrap();
+ let (edge_id, predecessor_node) = self
+ .dag
+ .edges_directed(output_node, Incoming)
+ .next()
+ .map(|edge| (edge.id(), edge.source()))
+ .unwrap();
+ self.dag.remove_edge(edge_id);
+ predecessor_node
+ };
+
+ vars_last_nodes.set_item(var, new_node.index())?;
+ self.dag
+ .add_edge(var_last_node, new_node, Wire::Var(var.clone_ref(py)));
+ }
+ }
+
+ // Add the output_nodes back to qargs
+ for (qubit, node) in qubit_last_nodes {
+ let output_node = self.qubit_io_map[qubit.0 as usize][1];
+ self.dag.add_edge(node, output_node, Wire::Qubit(qubit));
+ }
+
+ // Add the output_nodes back to cargs
+ for (clbit, node) in clbit_last_nodes {
+ let output_node = self.clbit_io_map[clbit.0 as usize][1];
+ self.dag.add_edge(node, output_node, Wire::Clbit(clbit));
+ }
+
+ // Add the output_nodes back to vars
+ for item in vars_last_nodes.items() {
+ let (var, node): (PyObject, usize) = item.extract()?;
+ let output_node = self.var_output_map.get(py, &var).unwrap();
+ self.dag
+ .add_edge(NodeIndex::new(node), output_node, Wire::Var(var));
+ }
+
+ Ok(new_nodes)
+ }
}
/// Add to global phase. Global phase can only be Float or ParameterExpression so this
From 41dc41888ec60a7aab9b93aaa0aaebb5d4711a77 Mon Sep 17 00:00:00 2001
From: Matthew Treinish
Date: Fri, 6 Sep 2024 16:52:42 -0400
Subject: [PATCH 07/11] Fully port CheckMap to Rust (#13030)
* Fully port CheckMap to Rust
This commit migrates the entirety of the CheckMap analysis pass to Rust.
The pass operates solely in the rust domain and returns an
`Option<(String, [u32; 2])>` to Python which is used to set the two
property set fields appropriately. All the analysis of the dag is done
in Rust. There is still Python interaction required though because
control flow operations are only defined in Python. However the
interaction is minimal and only to get the circuits for control flow
blocks and converting them into DAGs (at least until #13001 is complete).
This commit is based on top of #12959 and will need to be rebased after
that merges.
Closes #12251
Part of #12208
* Use a Vec for wire_map instead of a HashMap
This commit switches to using a Vec for the internal wire_map
used to map control flow qubits. A HashMap was originally used because
in Python a dictionary is used. However, in the rust domain the inner
qubits are contiguous integers starting from 0 so a Vec can be used for
better performance in the case we have control flow.
* Update crates/accelerate/src/check_map.rs
Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
---------
Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
---
crates/accelerate/src/check_map.rs | 98 +++++++++++++++++++++
crates/accelerate/src/lib.rs | 1 +
crates/pyext/src/lib.rs | 13 +--
qiskit/__init__.py | 1 +
qiskit/transpiler/passes/utils/check_map.py | 33 +++----
5 files changed, 117 insertions(+), 29 deletions(-)
create mode 100644 crates/accelerate/src/check_map.rs
diff --git a/crates/accelerate/src/check_map.rs b/crates/accelerate/src/check_map.rs
new file mode 100644
index 000000000000..8ebf50cd372d
--- /dev/null
+++ b/crates/accelerate/src/check_map.rs
@@ -0,0 +1,98 @@
+// 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 hashbrown::HashSet;
+use pyo3::intern;
+use pyo3::prelude::*;
+use pyo3::wrap_pyfunction;
+
+use qiskit_circuit::circuit_data::CircuitData;
+use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType};
+use qiskit_circuit::imports::CIRCUIT_TO_DAG;
+use qiskit_circuit::operations::{Operation, OperationRef};
+use qiskit_circuit::Qubit;
+
+fn recurse<'py>(
+ py: Python<'py>,
+ dag: &'py DAGCircuit,
+ edge_set: &'py HashSet<[u32; 2]>,
+ wire_map: Option<&'py [Qubit]>,
+) -> PyResult