From a8cddcd9f30bcb1d9a7d676d221093cf674c8a37 Mon Sep 17 00:00:00 2001
From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
Date: Wed, 5 Jun 2024 19:59:27 -0400
Subject: [PATCH 01/45] Initial: Add equivalence to
`qiskit._accelerate.circuit`
---
Cargo.lock | 1 +
crates/circuit/Cargo.toml | 1 +
crates/circuit/src/equivalence.rs | 474 ++++++++++++++++++++++++++++++
crates/circuit/src/lib.rs | 1 +
4 files changed, 477 insertions(+)
create mode 100644 crates/circuit/src/equivalence.rs
diff --git a/Cargo.lock b/Cargo.lock
index cf8e4f365df9..b6ec645603ee 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1197,6 +1197,7 @@ version = "1.2.0"
dependencies = [
"hashbrown 0.14.5",
"pyo3",
+ "rustworkx-core",
]
[[package]]
diff --git a/crates/circuit/Cargo.toml b/crates/circuit/Cargo.toml
index 6ec38392cc38..6ebf9694afc1 100644
--- a/crates/circuit/Cargo.toml
+++ b/crates/circuit/Cargo.toml
@@ -12,3 +12,4 @@ doctest = false
[dependencies]
hashbrown.workspace = true
pyo3.workspace = true
+rustworkx-core = "0.14.2"
diff --git a/crates/circuit/src/equivalence.rs b/crates/circuit/src/equivalence.rs
new file mode 100644
index 000000000000..124934f889d6
--- /dev/null
+++ b/crates/circuit/src/equivalence.rs
@@ -0,0 +1,474 @@
+// 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 std::{error::Error, fmt::Display};
+
+use exceptions::CircuitError;
+use hashbrown::{HashMap, HashSet};
+use pyo3::prelude::*;
+use rustworkx_core::petgraph::{
+ graph::{DiGraph, EdgeIndex, NodeIndex},
+ visit::EdgeRef,
+};
+
+
+
+mod exceptions {
+ use pyo3::import_exception_bound;
+ import_exception_bound! {qiskit.exceptions, CircuitError}
+}
+// Custom Structs
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Key {
+ pub name: String,
+ pub num_qubits: usize,
+}
+
+#[derive(Debug, Clone)]
+pub struct Equivalence {
+ pub params: Vec,
+ pub circuit: CircuitRep,
+}
+
+// Temporary interpretation of Param
+#[derive(Debug, Clone, FromPyObject)]
+pub enum Param {
+ Float(f64),
+ ParameterExpression(PyObject),
+}
+
+impl Param {
+ fn compare(one: &PyObject, other: &PyObject) -> bool {
+ Python::with_gil(|py| -> PyResult {
+ let other_bound = other.bind(py);
+ other_bound.eq(one)
+ })
+ .unwrap()
+ }
+}
+
+impl PartialEq for Param {
+ fn eq(&self, other: &Self) -> bool {
+ match (self, other) {
+ (Param::Float(s), Param::Float(other)) => s == other,
+ (Param::Float(_), Param::ParameterExpression(_)) => false,
+ (Param::ParameterExpression(_), Param::Float(_)) => false,
+ (Param::ParameterExpression(s), Param::ParameterExpression(other)) => {
+ Self::compare(s, other)
+ }
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct NodeData {
+ key: Key,
+ equivs: Vec,
+}
+
+#[derive(Debug, Clone)]
+pub struct EdgeData {
+ pub index: u32,
+ pub num_gates: usize,
+ pub rule: Equivalence,
+ pub source: Key,
+}
+
+/// Temporary interpretation of Gate
+#[derive(Debug, Clone)]
+pub struct GateRep {
+ object: PyObject,
+ pub num_qubits: Option,
+ pub num_clbits: Option,
+ pub name: Option,
+ pub label: Option,
+ pub params: Vec,
+}
+
+impl FromPyObject<'_> for GateRep {
+ fn extract(ob: &'_ PyAny) -> PyResult {
+ let num_qubits = match ob.getattr("num_qubits") {
+ Ok(num_qubits) => num_qubits.extract::().ok(),
+ Err(_) => None,
+ };
+ let num_clbits = match ob.getattr("num_clbits") {
+ Ok(num_clbits) => num_clbits.extract::().ok(),
+ Err(_) => None,
+ };
+ let name = match ob.getattr("name") {
+ Ok(name) => name.extract::().ok(),
+ Err(_) => None,
+ };
+ let label = match ob.getattr("label") {
+ Ok(label) => label.extract::().ok(),
+ Err(_) => None,
+ };
+ let params = match ob.getattr("params") {
+ Ok(params) => params.extract::>().ok(),
+ Err(_) => Some(vec![]),
+ }
+ .unwrap_or_default();
+ Ok(Self {
+ object: ob.into(),
+ num_qubits,
+ num_clbits,
+ name,
+ label,
+ params,
+ })
+ }
+}
+
+/// Temporary interpretation of Gate
+#[derive(Debug, Clone)]
+pub struct CircuitRep {
+ object: PyObject,
+ pub num_qubits: Option,
+ pub num_clbits: Option,
+ pub label: Option,
+ pub params: Vec,
+ pub data: Vec,
+}
+
+impl FromPyObject<'_> for CircuitRep {
+ fn extract(ob: &'_ PyAny) -> PyResult {
+ let num_qubits = match ob.getattr("num_qubits") {
+ Ok(num_qubits) => num_qubits.extract::().ok(),
+ Err(_) => None,
+ };
+ let num_clbits = match ob.getattr("num_clbits") {
+ Ok(num_clbits) => num_clbits.extract::().ok(),
+ Err(_) => None,
+ };
+ let label = match ob.getattr("label") {
+ Ok(label) => label.extract::().ok(),
+ Err(_) => None,
+ };
+ let params = match ob.getattr("params") {
+ Ok(params) => params.extract::>().ok(),
+ Err(_) => Some(vec![]),
+ }
+ .unwrap_or_default();
+ let data = match ob.getattr("data") {
+ Ok(data) => data.extract::>().ok(),
+ Err(_) => Some(vec![]),
+ }
+ .unwrap_or_default();
+ Ok(Self {
+ object: ob.into(),
+ num_qubits,
+ num_clbits,
+ label,
+ params,
+ data,
+ })
+ }
+}
+
+// Custom Types
+type GraphType = DiGraph;
+type KTIType = HashMap;
+
+#[pyclass]
+#[derive(Debug, Clone)]
+pub struct EquivalenceLibrary {
+ graph: GraphType,
+ key_to_node_index: KTIType,
+ rule_id: usize,
+}
+
+#[pymethods]
+impl EquivalenceLibrary {
+ /// Create a new equivalence library.
+ ///
+ /// Args:
+ /// base (Optional[EquivalenceLibrary]): Base equivalence library to
+ /// be referenced if an entry is not found in this library.
+ #[new]
+ fn new(base: Option<&EquivalenceLibrary>) -> Self {
+ if let Some(base) = base {
+ Self {
+ graph: base.graph.clone(),
+ key_to_node_index: base.key_to_node_index.clone(),
+ rule_id: base.rule_id,
+ }
+ } else {
+ Self {
+ graph: GraphType::new(),
+ key_to_node_index: KTIType::new(),
+ rule_id: 0_usize,
+ }
+ }
+ }
+
+ // TODO: Add a way of returning a graph
+
+ /// Add a new equivalence to the library. Future queries for the Gate
+ /// will include the given circuit, in addition to all existing equivalences
+ /// (including those from base).
+ ///
+ /// Parameterized Gates (those including `qiskit.circuit.Parameters` in their
+ /// `Gate.params`) can be marked equivalent to parameterized circuits,
+ /// provided the parameters match.
+ ///
+ /// Args:
+ /// gate (Gate): A Gate instance.
+ /// equivalent_circuit (QuantumCircuit): A circuit equivalently
+ /// implementing the given Gate.
+ fn add_equivalence(&mut self, gate: GateRep, equivalent_circuit: CircuitRep) -> PyResult<()> {
+ match self.add_equiv(gate, equivalent_circuit) {
+ Ok(_) => Ok(()),
+ Err(e) => Err(CircuitError::new_err(e.message)),
+ }
+ }
+
+ /// Check if a library contains any decompositions for gate.
+
+ /// Args:
+ /// gate (Gate): A Gate instance.
+
+ /// Returns:
+ /// Bool: True if gate has a known decomposition in the library.
+ /// False otherwise.
+ pub fn has_entry(&self, gate: GateRep) -> bool {
+ let key = Key {
+ name: gate.name.unwrap(),
+ num_qubits: gate.num_qubits.unwrap_or_default(),
+ };
+ self.key_to_node_index.contains_key(&key)
+ }
+
+ /// Set the equivalence record for a Gate. Future queries for the Gate
+ /// will return only the circuits provided.
+ ///
+ /// Parameterized Gates (those including `qiskit.circuit.Parameters` in their
+ /// `Gate.params`) can be marked equivalent to parameterized circuits,
+ /// provided the parameters match.
+ ///
+ /// Args:
+ /// gate (Gate): A Gate instance.
+ /// entry (List['QuantumCircuit']) : A list of QuantumCircuits, each
+ /// equivalently implementing the given Gate.
+ fn set_entry(&mut self, gate: GateRep, entry: Vec) -> PyResult<()> {
+ match self.set_entry_native(&gate, &entry) {
+ Ok(_) => Ok(()),
+ Err(e) => Err(CircuitError::new_err(e.message)),
+ }
+ }
+
+ /// Gets the set of QuantumCircuits circuits from the library which
+ /// equivalently implement the given Gate.
+ ///
+ /// Parameterized circuits will have their parameters replaced with the
+ /// corresponding entries from Gate.params.
+ ///
+ /// Args:
+ /// gate (Gate) - Gate: A Gate instance.
+ ///
+ /// Returns:
+ /// List[QuantumCircuit]: A list of equivalent QuantumCircuits. If empty,
+ /// library contains no known decompositions of Gate.
+ ///
+ /// Returned circuits will be ordered according to their insertion in
+ /// the library, from earliest to latest, from top to base. The
+ /// ordering of the StandardEquivalenceLibrary will not generally be
+ /// consistent across Qiskit versions.
+ pub fn get_entry(&self, _gate: GateRep) {
+ todo!()
+ }
+}
+
+// Rust native methods
+impl EquivalenceLibrary {
+ /// Create a new node if key not found
+ fn set_default_node(&mut self, key: Key) -> NodeIndex {
+ *self
+ .key_to_node_index
+ .entry(key.to_owned())
+ .or_insert(self.graph.add_node(NodeData {
+ key,
+ equivs: vec![],
+ }))
+ }
+
+ /// Rust native equivalent to `EquivalenceLibrary.add_equivalence()`
+ ///
+ /// Add a new equivalence to the library. Future queries for the Gate
+ /// will include the given circuit, in addition to all existing equivalences
+ /// (including those from base).
+ ///
+ /// Parameterized Gates (those including `qiskit.circuit.Parameters` in their
+ /// `Gate.params`) can be marked equivalent to parameterized circuits,
+ /// provided the parameters match.
+ ///
+ /// Args:
+ /// gate (Gate): A Gate instance.
+ /// equivalent_circuit (QuantumCircuit): A circuit equivalently
+ /// implementing the given Gate.
+ pub fn add_equiv(
+ &mut self,
+ gate: GateRep,
+ equivalent_circuit: CircuitRep,
+ ) -> Result<(), EquivalenceError> {
+ raise_if_shape_mismatch(&gate, &equivalent_circuit)?;
+ raise_if_param_mismatch(&gate.params, &equivalent_circuit.params)?;
+
+ let key: Key = Key {
+ name: gate.name.unwrap(),
+ num_qubits: gate.num_qubits.unwrap(),
+ };
+ let equiv = Equivalence {
+ params: gate.params,
+ circuit: equivalent_circuit.to_owned(),
+ };
+
+ let target = self.set_default_node(key);
+ let node = self.graph.node_weight_mut(target).unwrap();
+ node.equivs.push(equiv.to_owned());
+
+ let sources: HashSet =
+ HashSet::from_iter(equivalent_circuit.data.iter().map(|inst| Key {
+ name: inst.name.to_owned().unwrap(),
+ num_qubits: inst.num_qubits.unwrap_or_default(),
+ }));
+ let edges = Vec::from_iter(sources.iter().map(|key| {
+ (
+ self.set_default_node(key.to_owned()),
+ target,
+ EdgeData {
+ index: self.rule_id as u32,
+ num_gates: sources.len(),
+ rule: equiv.to_owned(),
+ source: key.to_owned(),
+ },
+ )
+ }));
+ for edge in edges {
+ self.graph.add_edge(edge.0, edge.1, edge.2);
+ }
+ self.rule_id += 1;
+ Ok(())
+ }
+
+ /// Rust native equivalent to `EquivalenceLibrary.set_entry()`
+ /// Set the equivalence record for a Gate. Future queries for the Gate
+ /// will return only the circuits provided.
+ ///
+ /// Parameterized Gates (those including `qiskit.circuit.Parameters` in their
+ /// `Gate.params`) can be marked equivalent to parameterized circuits,
+ /// provided the parameters match.
+ ///
+ /// Args:
+ /// gate (Gate): A Gate instance.
+ /// entry (List['QuantumCircuit']) : A list of QuantumCircuits, each
+ /// equivalently implementing the given Gate.
+ pub fn set_entry_native(
+ &mut self,
+ gate: &GateRep,
+ entry: &Vec,
+ ) -> Result<(), EquivalenceError> {
+ for equiv in entry {
+ raise_if_shape_mismatch(gate, equiv)?;
+ raise_if_param_mismatch(&gate.params, &equiv.params)?;
+ }
+
+ let key = Key {
+ name: gate.name.to_owned().unwrap(),
+ num_qubits: gate.num_qubits.unwrap_or_default(),
+ };
+ let node_index = self.set_default_node(key);
+
+ let graph_ind = &mut self.graph.node_weight_mut(node_index).unwrap();
+ graph_ind.equivs.clear();
+
+ let edges: Vec = self
+ .graph
+ .edges_directed(node_index, rustworkx_core::petgraph::Direction::Incoming)
+ .map(|x| x.id())
+ .collect();
+ for edge in edges {
+ self.graph.remove_edge(edge);
+ }
+ for equiv in entry {
+ self.add_equiv(gate.to_owned(), equiv.to_owned())?
+ }
+ Ok(())
+ }
+
+ /// Get all the equivalences for the given key
+ fn get_equivalences(&self, key: &Key) -> Vec {
+ if let Some(key_in) = self.key_to_node_index.get(key) {
+ self.graph[*key_in].equivs.to_owned()
+ } else {
+ vec![]
+ }
+ }
+}
+
+fn raise_if_param_mismatch(
+ gate_params: &[Param],
+ circuit_parameters: &[Param],
+) -> Result<(), EquivalenceError> {
+ let gate_parameters: Vec<&Param> = gate_params
+ .iter()
+ .filter(|x| matches!(x, Param::ParameterExpression(_)))
+ .collect();
+ if circuit_parameters
+ .iter()
+ .any(|x| gate_parameters.contains(&x))
+ {
+ return Err(EquivalenceError::new_err(format!(
+ "Cannot add equivalence between circuit and gate \
+ of different parameters. Gate params: {:?}. \
+ Circuit params: {:?}.",
+ gate_parameters, circuit_parameters
+ )));
+ }
+ Ok(())
+}
+
+fn raise_if_shape_mismatch(gate: &GateRep, circuit: &CircuitRep) -> Result<(), EquivalenceError> {
+ if gate.num_qubits != circuit.num_qubits || gate.num_clbits != circuit.num_clbits {
+ return Err(EquivalenceError::new_err(format!(
+ "Cannot add equivalence between circuit and gate \
+ of different shapes. Gate: {} qubits and {} clbits. \
+ Circuit: {} qubits and {} clbits.",
+ gate.num_qubits.unwrap_or_default(),
+ gate.num_clbits.unwrap_or_default(),
+ circuit.num_qubits.unwrap_or_default(),
+ circuit.num_clbits.unwrap_or_default()
+ )));
+ }
+ Ok(())
+}
+
+// Errors
+
+#[derive(Debug, Clone)]
+pub struct EquivalenceError {
+ pub message: String,
+}
+
+impl EquivalenceError {
+ pub fn new_err(message: String) -> Self {
+ Self { message }
+ }
+}
+
+impl Error for EquivalenceError {}
+
+impl Display for EquivalenceError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.message)
+ }
+}
diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs
index c186c4243e93..fc8b2c686251 100644
--- a/crates/circuit/src/lib.rs
+++ b/crates/circuit/src/lib.rs
@@ -13,6 +13,7 @@
pub mod circuit_data;
pub mod circuit_instruction;
pub mod dag_node;
+pub mod equivalence;
pub mod intern_context;
use pyo3::prelude::*;
From f53b89c8a7e9d7e66103123013960348295d4f0f Mon Sep 17 00:00:00 2001
From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
Date: Mon, 10 Jun 2024 15:52:32 +0200
Subject: [PATCH 02/45] Add: `build_basis_graph` method
---
crates/circuit/src/equivalence.rs | 111 +++++++++++++++++++++++++-----
1 file changed, 95 insertions(+), 16 deletions(-)
diff --git a/crates/circuit/src/equivalence.rs b/crates/circuit/src/equivalence.rs
index 124934f889d6..c0295e122784 100644
--- a/crates/circuit/src/equivalence.rs
+++ b/crates/circuit/src/equivalence.rs
@@ -10,18 +10,19 @@
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.
-use std::{error::Error, fmt::Display};
+use std::{
+ error::Error,
+ fmt::{Display},
+};
use exceptions::CircuitError;
use hashbrown::{HashMap, HashSet};
-use pyo3::prelude::*;
+use pyo3::{prelude::*};
use rustworkx_core::petgraph::{
graph::{DiGraph, EdgeIndex, NodeIndex},
visit::EdgeRef,
};
-
-
mod exceptions {
use pyo3::import_exception_bound;
import_exception_bound! {qiskit.exceptions, CircuitError}
@@ -34,6 +35,16 @@ pub struct Key {
pub num_qubits: usize,
}
+impl Display for Key {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "Key(name=\"{}\" num_qubits=\"{}\"",
+ self.name, self.num_qubits
+ )
+ }
+}
+
#[derive(Debug, Clone)]
pub struct Equivalence {
pub params: Vec,
@@ -117,7 +128,7 @@ impl FromPyObject<'_> for GateRep {
Ok(params) => params.extract::>().ok(),
Err(_) => Some(vec![]),
}
- .unwrap_or_default();
+ .unwrap();
Ok(Self {
object: ob.into(),
num_qubits,
@@ -137,7 +148,7 @@ pub struct CircuitRep {
pub num_clbits: Option,
pub label: Option,
pub params: Vec,
- pub data: Vec,
+ pub data: Vec,
}
impl FromPyObject<'_> for CircuitRep {
@@ -158,12 +169,12 @@ impl FromPyObject<'_> for CircuitRep {
Ok(params) => params.extract::>().ok(),
Err(_) => Some(vec![]),
}
- .unwrap_or_default();
+ .unwrap();
let data = match ob.getattr("data") {
- Ok(data) => data.extract::>().ok(),
+ Ok(data) => data.extract::>().ok(),
Err(_) => Some(vec![]),
}
- .unwrap_or_default();
+ .unwrap();
Ok(Self {
object: ob.into(),
num_qubits,
@@ -175,6 +186,18 @@ impl FromPyObject<'_> for CircuitRep {
}
}
+#[derive(Debug, Clone)]
+pub struct CircuitInstructionRep {
+ operation: GateRep,
+}
+
+impl FromPyObject<'_> for CircuitInstructionRep {
+ fn extract(ob: &'_ PyAny) -> PyResult {
+ let operation = ob.getattr("operation")?.extract::()?;
+ Ok(Self { operation })
+ }
+}
+
// Custom Types
type GraphType = DiGraph;
type KTIType = HashMap;
@@ -268,17 +291,17 @@ impl EquivalenceLibrary {
/// Gets the set of QuantumCircuits circuits from the library which
/// equivalently implement the given Gate.
- ///
+ ///
/// Parameterized circuits will have their parameters replaced with the
/// corresponding entries from Gate.params.
- ///
+ ///
/// Args:
/// gate (Gate) - Gate: A Gate instance.
- ///
+ ///
/// Returns:
/// List[QuantumCircuit]: A list of equivalent QuantumCircuits. If empty,
/// library contains no known decompositions of Gate.
- ///
+ ///
/// Returned circuits will be ordered according to their insertion in
/// the library, from earliest to latest, from top to base. The
/// ordering of the StandardEquivalenceLibrary will not generally be
@@ -338,8 +361,8 @@ impl EquivalenceLibrary {
let sources: HashSet =
HashSet::from_iter(equivalent_circuit.data.iter().map(|inst| Key {
- name: inst.name.to_owned().unwrap(),
- num_qubits: inst.num_qubits.unwrap_or_default(),
+ name: inst.operation.name.to_owned().unwrap_or_default(),
+ num_qubits: inst.operation.num_qubits.unwrap_or_default(),
}));
let edges = Vec::from_iter(sources.iter().map(|key| {
(
@@ -383,7 +406,7 @@ impl EquivalenceLibrary {
}
let key = Key {
- name: gate.name.to_owned().unwrap(),
+ name: gate.name.to_owned().unwrap_or_default(),
num_qubits: gate.num_qubits.unwrap_or_default(),
};
let node_index = self.set_default_node(key);
@@ -413,6 +436,62 @@ impl EquivalenceLibrary {
vec![]
}
}
+
+ fn build_basis_graph(&self) -> DiGraph> {
+ let mut graph: DiGraph> = DiGraph::new();
+
+ let mut node_map: HashMap = HashMap::new();
+
+ for key in self.key_to_node_index.keys() {
+ let (name, num_qubits) = (key.name.to_owned(), key.num_qubits);
+ let equivalences = self.get_equivalences(key);
+ let basis: String = format!("{{{name}/{num_qubits}}}");
+ for equivalence in equivalences {
+ let decomp_basis: HashSet = HashSet::from_iter(
+ equivalence
+ .circuit
+ .data
+ .iter()
+ .map(|x| (x.operation.name.to_owned(), x.operation.num_qubits))
+ .map(|(name, num_qubits)| {
+ format!(
+ "{}/{}",
+ name.unwrap_or_default(),
+ num_qubits.unwrap_or_default()
+ )
+ }),
+ );
+ let decomp_string = format!(
+ "{{{}}}",
+ decomp_basis
+ .iter()
+ .cloned()
+ .collect::>()
+ .join(", ")
+ );
+ if !node_map.contains_key(&basis) {
+ let basis_node =
+ graph.add_node(format!("basis: {:?}, label: {}", basis, decomp_string));
+ node_map.insert(basis.to_owned(), basis_node);
+ }
+ if node_map.contains_key(&decomp_string) {
+ let decomp_basis_node = graph.add_node(format!(
+ "basis: {:?}, label: {}",
+ decomp_string, decomp_string
+ ));
+ node_map.insert(decomp_string.to_owned(), decomp_basis_node);
+ }
+ let label = format!("{:?}\n{:?}", equivalence.params, equivalence.circuit);
+ let map: HashMap<&str, String> = HashMap::from_iter([
+ ("label", label),
+ ("fontname", "Courier".to_owned()),
+ ("fontsize", 8.to_string()),
+ ]);
+ graph.add_edge(node_map[&basis], node_map[&decomp_string], map);
+ }
+ }
+ graph
+ }
}
fn raise_if_param_mismatch(
From a2156943dfd5ef2f9d676761d105e5f8b35c6b55 Mon Sep 17 00:00:00 2001
From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
Date: Tue, 11 Jun 2024 16:09:51 +0200
Subject: [PATCH 03/45] Add: `EquivalencyLibrary` to
`qiskit._accelerate.circuit` - Add `get_entry` method to obtain an entry from
binding to a `QuantumCircuit`. - Add `rebind_equiv` to bind parameters to
`QuantumCircuit`
---
crates/circuit/src/equivalence.rs | 150 +++++++++++++++++++++++++++---
crates/circuit/src/lib.rs | 1 +
2 files changed, 140 insertions(+), 11 deletions(-)
diff --git a/crates/circuit/src/equivalence.rs b/crates/circuit/src/equivalence.rs
index c0295e122784..a49a0065b920 100644
--- a/crates/circuit/src/equivalence.rs
+++ b/crates/circuit/src/equivalence.rs
@@ -10,14 +10,12 @@
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.
-use std::{
- error::Error,
- fmt::{Display},
-};
+use std::{error::Error, fmt::Display};
use exceptions::CircuitError;
use hashbrown::{HashMap, HashSet};
-use pyo3::{prelude::*};
+use pyo3::sync::GILOnceCell;
+use pyo3::{prelude::*, types::IntoPyDict};
use rustworkx_core::petgraph::{
graph::{DiGraph, EdgeIndex, NodeIndex},
visit::EdgeRef,
@@ -25,10 +23,52 @@ use rustworkx_core::petgraph::{
mod exceptions {
use pyo3::import_exception_bound;
- import_exception_bound! {qiskit.exceptions, CircuitError}
+ import_exception_bound! {qiskit.circuit.exceptions, CircuitError}
+}
+
+/// Helper wrapper around `GILOnceCell` instances that are just intended to store a Python object
+/// that is lazily imported.
+pub struct ImportOnceCell {
+ module: &'static str,
+ object: &'static str,
+ cell: GILOnceCell>,
}
+
+impl ImportOnceCell {
+ const fn new(module: &'static str, object: &'static str) -> Self {
+ Self {
+ module,
+ object,
+ cell: GILOnceCell::new(),
+ }
+ }
+
+ /// Get the underlying GIL-independent reference to the contained object, importing if
+ /// required.
+ #[inline]
+ pub fn get(&self, py: Python) -> &Py {
+ self.cell.get_or_init(py, || {
+ py.import_bound(self.module)
+ .unwrap()
+ .getattr(self.object)
+ .unwrap()
+ .unbind()
+ })
+ }
+
+ /// Get a GIL-bound reference to the contained object, importing if required.
+ #[inline]
+ pub fn get_bound<'py>(&self, py: Python<'py>) -> &Bound<'py, PyAny> {
+ self.get(py).bind(py)
+ }
+}
+
// Custom Structs
+pub static PARAMETER_EXPRESSION: ImportOnceCell =
+ ImportOnceCell::new("qiskit.circuit.parameterexpression", "ParameterExpression");
+pub static QUANTUM_CIRCUIT: ImportOnceCell =
+ ImportOnceCell::new("qiskit.circuit.quantumcircuit", "QuantumCircuit");
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Key {
pub name: String,
@@ -51,11 +91,47 @@ pub struct Equivalence {
pub circuit: CircuitRep,
}
-// Temporary interpretation of Param
-#[derive(Debug, Clone, FromPyObject)]
+#[derive(Clone, Debug)]
pub enum Param {
- Float(f64),
ParameterExpression(PyObject),
+ Float(f64),
+ Obj(PyObject),
+}
+
+impl<'py> FromPyObject<'py> for Param {
+ fn extract_bound(b: &Bound<'py, PyAny>) -> Result {
+ Ok(
+ if b.is_instance(PARAMETER_EXPRESSION.get_bound(b.py()))?
+ || b.is_instance(QUANTUM_CIRCUIT.get_bound(b.py()))?
+ {
+ Param::ParameterExpression(b.clone().unbind())
+ } else if let Ok(val) = b.extract::() {
+ Param::Float(val)
+ } else {
+ Param::Obj(b.clone().unbind())
+ },
+ )
+ }
+}
+
+impl IntoPy for Param {
+ fn into_py(self, py: Python) -> PyObject {
+ match &self {
+ Self::Float(val) => val.to_object(py),
+ Self::ParameterExpression(val) => val.clone_ref(py),
+ Self::Obj(val) => val.clone_ref(py),
+ }
+ }
+}
+
+impl ToPyObject for Param {
+ fn to_object(&self, py: Python) -> PyObject {
+ match self {
+ Self::Float(val) => val.to_object(py),
+ Self::ParameterExpression(val) => val.clone_ref(py),
+ Self::Obj(val) => val.clone_ref(py),
+ }
+ }
}
impl Param {
@@ -77,6 +153,11 @@ impl PartialEq for Param {
(Param::ParameterExpression(s), Param::ParameterExpression(other)) => {
Self::compare(s, other)
}
+ (Param::ParameterExpression(_), Param::Obj(_)) => false,
+ (Param::Float(_), Param::Obj(_)) => false,
+ (Param::Obj(_), Param::ParameterExpression(_)) => false,
+ (Param::Obj(_), Param::Float(_)) => false,
+ (Param::Obj(one), Param::Obj(other)) => Self::compare(one, other),
}
}
}
@@ -186,6 +267,12 @@ impl FromPyObject<'_> for CircuitRep {
}
}
+impl IntoPy for CircuitRep {
+ fn into_py(self, _py: Python<'_>) -> PyObject {
+ self.object
+ }
+}
+
#[derive(Debug, Clone)]
pub struct CircuitInstructionRep {
operation: GateRep,
@@ -306,8 +393,17 @@ impl EquivalenceLibrary {
/// the library, from earliest to latest, from top to base. The
/// ordering of the StandardEquivalenceLibrary will not generally be
/// consistent across Qiskit versions.
- pub fn get_entry(&self, _gate: GateRep) {
- todo!()
+ pub fn get_entry(&self, py: Python<'_>, gate: GateRep) -> Vec {
+ let key = Key {
+ name: gate.name.unwrap_or_default(),
+ num_qubits: gate.num_qubits.unwrap_or_default(),
+ };
+ let query_params = gate.params;
+
+ self.get_equivalences(&key)
+ .into_iter()
+ .filter_map(|equivalence| rebind_equiv(py, equivalence, &query_params).ok())
+ .collect()
}
}
@@ -531,6 +627,32 @@ fn raise_if_shape_mismatch(gate: &GateRep, circuit: &CircuitRep) -> Result<(), E
Ok(())
}
+fn rebind_equiv(
+ py: Python<'_>,
+ equiv: Equivalence,
+ query_params: &[Param],
+) -> PyResult {
+ let (equiv_params, equiv_circuit) = (equiv.params, equiv.circuit);
+ let param_map: Vec<(Param, Param)> = equiv_params
+ .into_iter()
+ .filter_map(|param| {
+ if matches!(param, Param::Obj(_)) {
+ Some(param)
+ } else {
+ None
+ }
+ })
+ .zip(query_params.iter().cloned())
+ .collect();
+ let dict = param_map.as_slice().into_py_dict_bound(py);
+ let kwargs = [("inplace", false), ("flat_input", true)].into_py_dict_bound(py);
+ let equiv =
+ equiv_circuit
+ .object
+ .call_method_bound(py, "assign_parameters", (dict,), Some(&kwargs))?;
+ equiv.extract::(py)
+}
+
// Errors
#[derive(Debug, Clone)]
@@ -551,3 +673,9 @@ impl Display for EquivalenceError {
write!(f, "{}", self.message)
}
}
+
+#[pymodule]
+pub fn equivalence(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
+ m.add_class::()?;
+ Ok(())
+}
diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs
index fc8b2c686251..55787fd7f058 100644
--- a/crates/circuit/src/lib.rs
+++ b/crates/circuit/src/lib.rs
@@ -32,6 +32,7 @@ pub enum SliceOrInt<'a> {
#[pymodule]
pub fn circuit(m: Bound) -> PyResult<()> {
m.add_class::()?;
+ m.add_class::()?;
m.add_class::()?;
m.add_class::()?;
m.add_class::()?;
From 7d0f4deb6c69a99711ce153b562a476ea2a6bc76 Mon Sep 17 00:00:00 2001
From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
Date: Wed, 12 Jun 2024 11:07:46 +0200
Subject: [PATCH 04/45] Add: PyDiGraph converter for `equivalence.rs`
---
crates/circuit/src/equivalence.rs | 155 ++++++++++++++++++------
test/python/circuit/test_equivalence.py | 3 +-
2 files changed, 118 insertions(+), 40 deletions(-)
diff --git a/crates/circuit/src/equivalence.rs b/crates/circuit/src/equivalence.rs
index a49a0065b920..c8fabbcdb2af 100644
--- a/crates/circuit/src/equivalence.rs
+++ b/crates/circuit/src/equivalence.rs
@@ -63,15 +63,20 @@ impl ImportOnceCell {
}
}
-// Custom Structs
-
pub static PARAMETER_EXPRESSION: ImportOnceCell =
ImportOnceCell::new("qiskit.circuit.parameterexpression", "ParameterExpression");
pub static QUANTUM_CIRCUIT: ImportOnceCell =
ImportOnceCell::new("qiskit.circuit.quantumcircuit", "QuantumCircuit");
+pub static PYDIGRAPH: ImportOnceCell = ImportOnceCell::new("rustworkx", "PyDiGraph");
+
+// Custom Structs
+
+#[pyclass(sequence)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Key {
+ #[pyo3(get)]
pub name: String,
+ #[pyo3(get)]
pub num_qubits: usize,
}
@@ -85,12 +90,39 @@ impl Display for Key {
}
}
+#[pyclass(sequence)]
#[derive(Debug, Clone)]
pub struct Equivalence {
+ #[pyo3(get)]
pub params: Vec,
+ #[pyo3(get)]
pub circuit: CircuitRep,
}
+#[pyclass(sequence)]
+#[derive(Debug, Clone)]
+pub struct NodeData {
+ #[pyo3(get)]
+ key: Key,
+ #[pyo3(get)]
+ equivs: Vec,
+}
+
+#[pyclass(sequence)]
+#[derive(Debug, Clone)]
+pub struct EdgeData {
+ #[pyo3(get)]
+ pub index: u32,
+ #[pyo3(get)]
+ pub num_gates: usize,
+ #[pyo3(get)]
+ pub rule: Equivalence,
+ #[pyo3(get)]
+ pub source: Key,
+}
+
+// REPRESENTATIONS of non rust objects
+// Temporary definition of Parameter
#[derive(Clone, Debug)]
pub enum Param {
ParameterExpression(PyObject),
@@ -162,20 +194,6 @@ impl PartialEq for Param {
}
}
-#[derive(Debug, Clone)]
-pub struct NodeData {
- key: Key,
- equivs: Vec,
-}
-
-#[derive(Debug, Clone)]
-pub struct EdgeData {
- pub index: u32,
- pub num_gates: usize,
- pub rule: Equivalence,
- pub source: Key,
-}
-
/// Temporary interpretation of Gate
#[derive(Debug, Clone)]
pub struct GateRep {
@@ -220,8 +238,13 @@ impl FromPyObject<'_> for GateRep {
})
}
}
+impl IntoPy for GateRep {
+ fn into_py(self, _py: Python<'_>) -> PyObject {
+ self.object
+ }
+}
-/// Temporary interpretation of Gate
+/// Temporary interpretation of QuantumCircuit
#[derive(Debug, Clone)]
pub struct CircuitRep {
object: PyObject,
@@ -273,6 +296,7 @@ impl IntoPy for CircuitRep {
}
}
+// Temporary Representation of CircuitInstruction
#[derive(Debug, Clone)]
pub struct CircuitInstructionRep {
operation: GateRep,
@@ -292,9 +316,10 @@ type KTIType = HashMap;
#[pyclass]
#[derive(Debug, Clone)]
pub struct EquivalenceLibrary {
- graph: GraphType,
+ _graph: GraphType,
key_to_node_index: KTIType,
rule_id: usize,
+ graph: Option,
}
#[pymethods]
@@ -308,15 +333,17 @@ impl EquivalenceLibrary {
fn new(base: Option<&EquivalenceLibrary>) -> Self {
if let Some(base) = base {
Self {
- graph: base.graph.clone(),
+ _graph: base._graph.clone(),
key_to_node_index: base.key_to_node_index.clone(),
rule_id: base.rule_id,
+ graph: None,
}
} else {
Self {
- graph: GraphType::new(),
+ _graph: GraphType::new(),
key_to_node_index: KTIType::new(),
rule_id: 0_usize,
+ graph: None,
}
}
}
@@ -405,19 +432,39 @@ impl EquivalenceLibrary {
.filter_map(|equivalence| rebind_equiv(py, equivalence, &query_params).ok())
.collect()
}
+
+ #[getter]
+ fn get_graph(&mut self, py: Python<'_>) -> PyResult {
+ if let Some(graph) = &self.graph {
+ Ok(graph.to_owned())
+ } else {
+ self.graph = Some(to_pygraph(py, &self._graph)?);
+ Ok(self.graph.to_object(py))
+ }
+ }
}
// Rust native methods
impl EquivalenceLibrary {
/// Create a new node if key not found
fn set_default_node(&mut self, key: Key) -> NodeIndex {
- *self
- .key_to_node_index
- .entry(key.to_owned())
- .or_insert(self.graph.add_node(NodeData {
- key,
+ if let Some(value) = self.key_to_node_index.get(&key) {
+ *value
+ } else {
+ let node = self._graph.add_node(NodeData {
+ key: key.to_owned(),
equivs: vec![],
- }))
+ });
+ self.key_to_node_index.insert(key, node);
+ node
+ }
+ // *self
+ // .key_to_node_index
+ // .entry(key.to_owned())
+ // .or_insert(self._graph.add_node(NodeData {
+ // key,
+ // equivs: vec![],
+ // }))
}
/// Rust native equivalent to `EquivalenceLibrary.add_equivalence()`
@@ -452,30 +499,31 @@ impl EquivalenceLibrary {
};
let target = self.set_default_node(key);
- let node = self.graph.node_weight_mut(target).unwrap();
- node.equivs.push(equiv.to_owned());
-
+ if let Some(node) = self._graph.node_weight_mut(target) {
+ node.equivs.push(equiv.to_owned());
+ }
let sources: HashSet =
HashSet::from_iter(equivalent_circuit.data.iter().map(|inst| Key {
name: inst.operation.name.to_owned().unwrap_or_default(),
num_qubits: inst.operation.num_qubits.unwrap_or_default(),
}));
- let edges = Vec::from_iter(sources.iter().map(|key| {
+ let edges = Vec::from_iter(sources.iter().map(|source| {
(
- self.set_default_node(key.to_owned()),
+ self.set_default_node(source.to_owned()),
target,
EdgeData {
index: self.rule_id as u32,
num_gates: sources.len(),
rule: equiv.to_owned(),
- source: key.to_owned(),
+ source: source.to_owned(),
},
)
}));
for edge in edges {
- self.graph.add_edge(edge.0, edge.1, edge.2);
+ self._graph.add_edge(edge.0, edge.1, edge.2);
}
self.rule_id += 1;
+ self.graph = None;
Ok(())
}
@@ -507,27 +555,29 @@ impl EquivalenceLibrary {
};
let node_index = self.set_default_node(key);
- let graph_ind = &mut self.graph.node_weight_mut(node_index).unwrap();
- graph_ind.equivs.clear();
+ if let Some(graph_ind) = self._graph.node_weight_mut(node_index) {
+ graph_ind.equivs.clear();
+ }
let edges: Vec = self
- .graph
+ ._graph
.edges_directed(node_index, rustworkx_core::petgraph::Direction::Incoming)
.map(|x| x.id())
.collect();
for edge in edges {
- self.graph.remove_edge(edge);
+ self._graph.remove_edge(edge);
}
for equiv in entry {
self.add_equiv(gate.to_owned(), equiv.to_owned())?
}
+ self.graph = None;
Ok(())
}
/// Get all the equivalences for the given key
fn get_equivalences(&self, key: &Key) -> Vec {
if let Some(key_in) = self.key_to_node_index.get(key) {
- self.graph[*key_in].equivs.to_owned()
+ self._graph[*key_in].equivs.to_owned()
} else {
vec![]
}
@@ -674,8 +724,37 @@ impl Display for EquivalenceError {
}
}
+fn to_pygraph(py: Python<'_>, pet_graph: &DiGraph) -> PyResult
+where
+ N: IntoPy + Clone,
+ E: IntoPy + Clone,
+{
+ let graph = PYDIGRAPH.get_bound(py).call0()?;
+ let node_weights = pet_graph.node_weights();
+ for node in node_weights {
+ graph.call_method1("add_node", (node.to_owned(),))?;
+ }
+ let edge_weights = pet_graph.edge_indices().map(|edge| {
+ (
+ pet_graph.edge_endpoints(edge).unwrap(),
+ pet_graph.edge_weight(edge).unwrap(),
+ )
+ });
+ for ((source, target), weight) in edge_weights {
+ graph.call_method1(
+ "add_edge",
+ (source.index(), target.index(), weight.to_owned()),
+ )?;
+ }
+ Ok(graph.unbind())
+}
+
#[pymodule]
pub fn equivalence(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::()?;
+ m.add_class::()?;
+ m.add_class::()?;
+ m.add_class::()?;
+ m.add_class::()?;
Ok(())
}
diff --git a/test/python/circuit/test_equivalence.py b/test/python/circuit/test_equivalence.py
index 286941313ae9..f4836e122f72 100644
--- a/test/python/circuit/test_equivalence.py
+++ b/test/python/circuit/test_equivalence.py
@@ -22,9 +22,8 @@
from qiskit.circuit.library import U2Gate
from qiskit.circuit.exceptions import CircuitError
from qiskit.converters import circuit_to_instruction, circuit_to_gate
-from qiskit.circuit import EquivalenceLibrary
+from qiskit._accelerate.circuit import EquivalenceLibrary, Key, Equivalence, NodeData, EdgeData
from qiskit.utils import optionals
-from qiskit.circuit.equivalence import Key, Equivalence, NodeData, EdgeData
from test import QiskitTestCase # pylint: disable=wrong-import-order
from ..visualization.visualization import QiskitVisualizationTestCase, path_to_diagram_reference
From f73581e8acc8915d83f44dcf8e2b53dd8a5b61af Mon Sep 17 00:00:00 2001
From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
Date: Wed, 12 Jun 2024 15:00:10 +0200
Subject: [PATCH 05/45] Add: Extend original equivalence with rust
representation
---
crates/circuit/src/equivalence.rs | 159 +++++++++++-------
crates/circuit/src/lib.rs | 4 +
qiskit/circuit/equivalence.py | 215 +-----------------------
test/python/circuit/test_equivalence.py | 2 +-
4 files changed, 114 insertions(+), 266 deletions(-)
diff --git a/crates/circuit/src/equivalence.rs b/crates/circuit/src/equivalence.rs
index c8fabbcdb2af..b1fbb28227d5 100644
--- a/crates/circuit/src/equivalence.rs
+++ b/crates/circuit/src/equivalence.rs
@@ -10,6 +10,8 @@
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.
+use std::collections::hash_map::DefaultHasher;
+use std::hash::{Hash, Hasher};
use std::{error::Error, fmt::Display};
use exceptions::CircuitError;
@@ -80,11 +82,33 @@ pub struct Key {
pub num_qubits: usize,
}
+#[pymethods]
+impl Key {
+ #[new]
+ fn new(name: String, num_qubits: usize) -> Self {
+ Self { name, num_qubits }
+ }
+
+ fn __eq__(&self, other: Self) -> bool {
+ self.eq(&other)
+ }
+
+ fn __hash__(&self) -> u64 {
+ let mut hasher = DefaultHasher::new();
+ (self.name.to_string(), self.num_qubits).hash(&mut hasher);
+ hasher.finish()
+ }
+
+ fn __repr__(slf: PyRef<'_, Self>) -> String {
+ slf.to_string()
+ }
+}
+
impl Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
- "Key(name=\"{}\" num_qubits=\"{}\"",
+ "Key(name=\'{}\', num_qubits={})",
self.name, self.num_qubits
)
}
@@ -99,6 +123,28 @@ pub struct Equivalence {
pub circuit: CircuitRep,
}
+#[pymethods]
+impl Equivalence {
+ #[new]
+ fn new(params: Vec, circuit: CircuitRep) -> Self {
+ Self { circuit, params }
+ }
+
+ fn __repr__(&self) -> String {
+ self.to_string()
+ }
+}
+
+impl Display for Equivalence {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "Equivalence(params={:?}, circuits={:?})",
+ self.params, self.circuit
+ )
+ }
+}
+
#[pyclass(sequence)]
#[derive(Debug, Clone)]
pub struct NodeData {
@@ -108,6 +154,24 @@ pub struct NodeData {
equivs: Vec,
}
+#[pymethods]
+impl NodeData {
+ #[new]
+ fn new(key: Key, equivs: Vec) -> Self {
+ Self { key, equivs }
+ }
+
+ fn __repr__(&self) -> String {
+ self.to_string()
+ }
+}
+
+impl Display for NodeData {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "NodeData(key={}, equivs={:#?})", self.key, self.equivs)
+ }
+}
+
#[pyclass(sequence)]
#[derive(Debug, Clone)]
pub struct EdgeData {
@@ -121,6 +185,33 @@ pub struct EdgeData {
pub source: Key,
}
+#[pymethods]
+impl EdgeData {
+ #[new]
+ fn new(index: u32, num_gates: usize, rule: Equivalence, source: Key) -> Self {
+ Self {
+ index,
+ num_gates,
+ rule,
+ source,
+ }
+ }
+
+ fn __repr__(&self) -> String {
+ self.to_string()
+ }
+}
+
+impl Display for EdgeData {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "EdgeData(index={}, num_gates={}, rule={}, source={})",
+ self.index, self.num_gates, self.rule, self.source
+ )
+ }
+}
+
// REPRESENTATIONS of non rust objects
// Temporary definition of Parameter
#[derive(Clone, Debug)]
@@ -313,7 +404,7 @@ impl FromPyObject<'_> for CircuitInstructionRep {
type GraphType = DiGraph;
type KTIType = HashMap;
-#[pyclass]
+#[pyclass(subclass, name = "BaseEquivalenceLibrary")]
#[derive(Debug, Clone)]
pub struct EquivalenceLibrary {
_graph: GraphType,
@@ -442,6 +533,14 @@ impl EquivalenceLibrary {
Ok(self.graph.to_object(py))
}
}
+
+ fn keys(&self) -> HashSet {
+ self.key_to_node_index.keys().cloned().collect()
+ }
+
+ fn node_index(&self, key: Key) -> usize {
+ self.key_to_node_index[&key].index()
+ }
}
// Rust native methods
@@ -582,62 +681,6 @@ impl EquivalenceLibrary {
vec![]
}
}
-
- fn build_basis_graph(&self) -> DiGraph> {
- let mut graph: DiGraph> = DiGraph::new();
-
- let mut node_map: HashMap = HashMap::new();
-
- for key in self.key_to_node_index.keys() {
- let (name, num_qubits) = (key.name.to_owned(), key.num_qubits);
- let equivalences = self.get_equivalences(key);
- let basis: String = format!("{{{name}/{num_qubits}}}");
- for equivalence in equivalences {
- let decomp_basis: HashSet = HashSet::from_iter(
- equivalence
- .circuit
- .data
- .iter()
- .map(|x| (x.operation.name.to_owned(), x.operation.num_qubits))
- .map(|(name, num_qubits)| {
- format!(
- "{}/{}",
- name.unwrap_or_default(),
- num_qubits.unwrap_or_default()
- )
- }),
- );
- let decomp_string = format!(
- "{{{}}}",
- decomp_basis
- .iter()
- .cloned()
- .collect::>()
- .join(", ")
- );
- if !node_map.contains_key(&basis) {
- let basis_node =
- graph.add_node(format!("basis: {:?}, label: {}", basis, decomp_string));
- node_map.insert(basis.to_owned(), basis_node);
- }
- if node_map.contains_key(&decomp_string) {
- let decomp_basis_node = graph.add_node(format!(
- "basis: {:?}, label: {}",
- decomp_string, decomp_string
- ));
- node_map.insert(decomp_string.to_owned(), decomp_basis_node);
- }
- let label = format!("{:?}\n{:?}", equivalence.params, equivalence.circuit);
- let map: HashMap<&str, String> = HashMap::from_iter([
- ("label", label),
- ("fontname", "Courier".to_owned()),
- ("fontsize", 8.to_string()),
- ]);
- graph.add_edge(node_map[&basis], node_map[&decomp_string], map);
- }
- }
- graph
- }
}
fn raise_if_param_mismatch(
diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs
index 68e39746d6c4..2a4bc594d1c3 100644
--- a/crates/circuit/src/lib.rs
+++ b/crates/circuit/src/lib.rs
@@ -66,6 +66,10 @@ impl From for BitType {
pub fn circuit(m: Bound) -> PyResult<()> {
m.add_class::()?;
m.add_class::()?;
+ m.add_class::()?;
+ m.add_class::()?;
+ m.add_class::()?;
+ m.add_class::()?;
m.add_class::()?;
m.add_class::()?;
m.add_class::()?;
diff --git a/qiskit/circuit/equivalence.py b/qiskit/circuit/equivalence.py
index 45921c3f2293..28ce5a4b92dd 100644
--- a/qiskit/circuit/equivalence.py
+++ b/qiskit/circuit/equivalence.py
@@ -18,184 +18,18 @@
from rustworkx.visualization import graphviz_draw
import rustworkx as rx
+
from qiskit.exceptions import InvalidFileError
from .exceptions import CircuitError
from .parameter import Parameter
from .parameterexpression import ParameterExpression
-
-Key = namedtuple("Key", ["name", "num_qubits"])
-Equivalence = namedtuple("Equivalence", ["params", "circuit"]) # Ordered to match Gate.params
-NodeData = namedtuple("NodeData", ["key", "equivs"])
-EdgeData = namedtuple("EdgeData", ["index", "num_gates", "rule", "source"])
+from qiskit._accelerate.circuit import BaseEquivalenceLibrary, Key, Equivalence, NodeData, EdgeData
-class EquivalenceLibrary:
+class EquivalenceLibrary(BaseEquivalenceLibrary):
"""A library providing a one-way mapping of Gates to their equivalent
implementations as QuantumCircuits."""
- def __init__(self, *, base=None):
- """Create a new equivalence library.
-
- Args:
- base (Optional[EquivalenceLibrary]): Base equivalence library to
- be referenced if an entry is not found in this library.
- """
- self._base = base
-
- if base is None:
- self._graph = rx.PyDiGraph()
- self._key_to_node_index = {}
- # Some unique identifier for rules.
- self._rule_id = 0
- else:
- self._graph = base._graph.copy()
- self._key_to_node_index = copy.deepcopy(base._key_to_node_index)
- self._rule_id = base._rule_id
-
- @property
- def graph(self) -> rx.PyDiGraph:
- """Return graph representing the equivalence library data.
-
- This property should be treated as read-only as it provides
- a reference to the internal state of the :class:`~.EquivalenceLibrary` object.
- If the graph returned by this property is mutated it could corrupt the
- the contents of the object. If you need to modify the output ``PyDiGraph``
- be sure to make a copy prior to any modification.
-
- Returns:
- PyDiGraph: A graph object with equivalence data in each node.
- """
- return self._graph
-
- def _set_default_node(self, key):
- """Create a new node if key not found"""
- if key not in self._key_to_node_index:
- self._key_to_node_index[key] = self._graph.add_node(NodeData(key=key, equivs=[]))
- return self._key_to_node_index[key]
-
- def add_equivalence(self, gate, equivalent_circuit):
- """Add a new equivalence to the library. Future queries for the Gate
- will include the given circuit, in addition to all existing equivalences
- (including those from base).
-
- Parameterized Gates (those including `qiskit.circuit.Parameters` in their
- `Gate.params`) can be marked equivalent to parameterized circuits,
- provided the parameters match.
-
- Args:
- gate (Gate): A Gate instance.
- equivalent_circuit (QuantumCircuit): A circuit equivalently
- implementing the given Gate.
- """
-
- _raise_if_shape_mismatch(gate, equivalent_circuit)
- _raise_if_param_mismatch(gate.params, equivalent_circuit.parameters)
-
- key = Key(name=gate.name, num_qubits=gate.num_qubits)
- equiv = Equivalence(params=gate.params.copy(), circuit=equivalent_circuit.copy())
-
- target = self._set_default_node(key)
- self._graph[target].equivs.append(equiv)
-
- sources = {
- Key(name=instruction.operation.name, num_qubits=len(instruction.qubits))
- for instruction in equivalent_circuit
- }
- edges = [
- (
- self._set_default_node(source),
- target,
- EdgeData(index=self._rule_id, num_gates=len(sources), rule=equiv, source=source),
- )
- for source in sources
- ]
- self._graph.add_edges_from(edges)
- self._rule_id += 1
-
- def has_entry(self, gate):
- """Check if a library contains any decompositions for gate.
-
- Args:
- gate (Gate): A Gate instance.
-
- Returns:
- Bool: True if gate has a known decomposition in the library.
- False otherwise.
- """
- key = Key(name=gate.name, num_qubits=gate.num_qubits)
-
- return key in self._key_to_node_index
-
- def set_entry(self, gate, entry):
- """Set the equivalence record for a Gate. Future queries for the Gate
- will return only the circuits provided.
-
- Parameterized Gates (those including `qiskit.circuit.Parameters` in their
- `Gate.params`) can be marked equivalent to parameterized circuits,
- provided the parameters match.
-
- Args:
- gate (Gate): A Gate instance.
- entry (List['QuantumCircuit']) : A list of QuantumCircuits, each
- equivalently implementing the given Gate.
- """
- for equiv in entry:
- _raise_if_shape_mismatch(gate, equiv)
- _raise_if_param_mismatch(gate.params, equiv.parameters)
-
- node_index = self._set_default_node(Key(name=gate.name, num_qubits=gate.num_qubits))
- # Remove previous equivalences of this node, leaving in place any later equivalences that
- # were added that use `gate`.
- self._graph[node_index].equivs.clear()
- for parent, child, _ in self._graph.in_edges(node_index):
- # `child` should always be ourselves, but there might be parallel edges.
- self._graph.remove_edge(parent, child)
- for equivalence in entry:
- self.add_equivalence(gate, equivalence)
-
- def get_entry(self, gate):
- """Gets the set of QuantumCircuits circuits from the library which
- equivalently implement the given Gate.
-
- Parameterized circuits will have their parameters replaced with the
- corresponding entries from Gate.params.
-
- Args:
- gate (Gate) - Gate: A Gate instance.
-
- Returns:
- List[QuantumCircuit]: A list of equivalent QuantumCircuits. If empty,
- library contains no known decompositions of Gate.
-
- Returned circuits will be ordered according to their insertion in
- the library, from earliest to latest, from top to base. The
- ordering of the StandardEquivalenceLibrary will not generally be
- consistent across Qiskit versions.
- """
- key = Key(name=gate.name, num_qubits=gate.num_qubits)
- query_params = gate.params
-
- return [_rebind_equiv(equiv, query_params) for equiv in self._get_equivalences(key)]
-
- def keys(self):
- """Return list of keys to key to node index map.
-
- Returns:
- List: Keys to the key to node index map.
- """
- return self._key_to_node_index.keys()
-
- def node_index(self, key):
- """Return node index for a given key.
-
- Args:
- key (Key): Key to an equivalence.
-
- Returns:
- Int: Index to the node in the graph for the given key.
- """
- return self._key_to_node_index[key]
-
def draw(self, filename=None):
"""Draws the equivalence relations available in the library.
@@ -227,12 +61,13 @@ def _build_basis_graph(self):
graph = rx.PyDiGraph()
node_map = {}
- for key in self._key_to_node_index:
- name, num_qubits = key
+ for key in super().keys():
+ name, num_qubits = key.name, key.num_qubits
equivalences = self._get_equivalences(key)
basis = frozenset([f"{name}/{num_qubits}"])
- for params, decomp in equivalences:
+ for equivalence in equivalences:
+ params, decomp = equivalence.params, equivalence.circuit
decomp_basis = frozenset(
f"{name}/{num_qubits}"
for name, num_qubits in {
@@ -260,38 +95,4 @@ def _build_basis_graph(self):
def _get_equivalences(self, key):
"""Get all the equivalences for the given key"""
- return (
- self._graph[self._key_to_node_index[key]].equivs
- if key in self._key_to_node_index
- else []
- )
-
-
-def _raise_if_param_mismatch(gate_params, circuit_parameters):
- gate_parameters = [p for p in gate_params if isinstance(p, ParameterExpression)]
-
- if set(gate_parameters) != circuit_parameters:
- raise CircuitError(
- "Cannot add equivalence between circuit and gate "
- "of different parameters. Gate params: {}. "
- "Circuit params: {}.".format(gate_parameters, circuit_parameters)
- )
-
-
-def _raise_if_shape_mismatch(gate, circuit):
- if gate.num_qubits != circuit.num_qubits or gate.num_clbits != circuit.num_clbits:
- raise CircuitError(
- "Cannot add equivalence between circuit and gate "
- "of different shapes. Gate: {} qubits and {} clbits. "
- "Circuit: {} qubits and {} clbits.".format(
- gate.num_qubits, gate.num_clbits, circuit.num_qubits, circuit.num_clbits
- )
- )
-
-
-def _rebind_equiv(equiv, query_params):
- equiv_params, equiv_circuit = equiv
- param_map = {x: y for x, y in zip(equiv_params, query_params) if isinstance(x, Parameter)}
- equiv = equiv_circuit.assign_parameters(param_map, inplace=False, flat_input=True)
-
- return equiv
+ return self.graph[self.node_index(key)].equivs if key in self.keys() else []
diff --git a/test/python/circuit/test_equivalence.py b/test/python/circuit/test_equivalence.py
index f4836e122f72..acdcf9e9685c 100644
--- a/test/python/circuit/test_equivalence.py
+++ b/test/python/circuit/test_equivalence.py
@@ -22,7 +22,7 @@
from qiskit.circuit.library import U2Gate
from qiskit.circuit.exceptions import CircuitError
from qiskit.converters import circuit_to_instruction, circuit_to_gate
-from qiskit._accelerate.circuit import EquivalenceLibrary, Key, Equivalence, NodeData, EdgeData
+from qiskit.circuit.equivalence import EquivalenceLibrary, Key, Equivalence, NodeData, EdgeData
from qiskit.utils import optionals
from test import QiskitTestCase # pylint: disable=wrong-import-order
From 57e0ef8886fe8ea11feac73aad8610e44cd9d68c Mon Sep 17 00:00:00 2001
From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
Date: Thu, 13 Jun 2024 03:51:38 +0200
Subject: [PATCH 06/45] Fix: Correct circuit parameter extraction
---
crates/circuit/src/equivalence.rs | 87 ++++++++++++++++++++-----------
1 file changed, 56 insertions(+), 31 deletions(-)
diff --git a/crates/circuit/src/equivalence.rs b/crates/circuit/src/equivalence.rs
index b1fbb28227d5..314de290589e 100644
--- a/crates/circuit/src/equivalence.rs
+++ b/crates/circuit/src/equivalence.rs
@@ -216,7 +216,7 @@ impl Display for EdgeData {
// Temporary definition of Parameter
#[derive(Clone, Debug)]
pub enum Param {
- ParameterExpression(PyObject),
+ ParameterExpression(ParamExpRep),
Float(f64),
Obj(PyObject),
}
@@ -227,7 +227,11 @@ impl<'py> FromPyObject<'py> for Param {
if b.is_instance(PARAMETER_EXPRESSION.get_bound(b.py()))?
|| b.is_instance(QUANTUM_CIRCUIT.get_bound(b.py()))?
{
- Param::ParameterExpression(b.clone().unbind())
+ let id = b.getattr("_uuid")?.getattr("hex")?.extract::()?;
+ Param::ParameterExpression(ParamExpRep {
+ id,
+ object: b.clone().unbind(),
+ })
} else if let Ok(val) = b.extract::() {
Param::Float(val)
} else {
@@ -241,7 +245,7 @@ impl IntoPy for Param {
fn into_py(self, py: Python) -> PyObject {
match &self {
Self::Float(val) => val.to_object(py),
- Self::ParameterExpression(val) => val.clone_ref(py),
+ Self::ParameterExpression(val) => val.object.clone_ref(py),
Self::Obj(val) => val.clone_ref(py),
}
}
@@ -251,7 +255,7 @@ impl ToPyObject for Param {
fn to_object(&self, py: Python) -> PyObject {
match self {
Self::Float(val) => val.to_object(py),
- Self::ParameterExpression(val) => val.clone_ref(py),
+ Self::ParameterExpression(val) => val.object.clone_ref(py),
Self::Obj(val) => val.clone_ref(py),
}
}
@@ -263,7 +267,7 @@ impl Param {
let other_bound = other.bind(py);
other_bound.eq(one)
})
- .unwrap()
+ .unwrap_or_default()
}
}
@@ -273,9 +277,7 @@ impl PartialEq for Param {
(Param::Float(s), Param::Float(other)) => s == other,
(Param::Float(_), Param::ParameterExpression(_)) => false,
(Param::ParameterExpression(_), Param::Float(_)) => false,
- (Param::ParameterExpression(s), Param::ParameterExpression(other)) => {
- Self::compare(s, other)
- }
+ (Param::ParameterExpression(s), Param::ParameterExpression(other)) => s.id == other.id,
(Param::ParameterExpression(_), Param::Obj(_)) => false,
(Param::Float(_), Param::Obj(_)) => false,
(Param::Obj(_), Param::ParameterExpression(_)) => false,
@@ -285,6 +287,13 @@ impl PartialEq for Param {
}
}
+/// ParamExpIdentifier
+#[derive(Debug, Clone)]
+pub struct ParamExpRep {
+ id: String,
+ object: PyObject,
+}
+
/// Temporary interpretation of Gate
#[derive(Debug, Clone)]
pub struct GateRep {
@@ -318,7 +327,7 @@ impl FromPyObject<'_> for GateRep {
Ok(params) => params.extract::>().ok(),
Err(_) => Some(vec![]),
}
- .unwrap();
+ .unwrap_or_default();
Ok(Self {
object: ob.into(),
num_qubits,
@@ -360,16 +369,16 @@ impl FromPyObject<'_> for CircuitRep {
Ok(label) => label.extract::().ok(),
Err(_) => None,
};
- let params = match ob.getattr("params") {
- Ok(params) => params.extract::>().ok(),
- Err(_) => Some(vec![]),
- }
- .unwrap();
+ let params = ob
+ .getattr("parameters")?
+ .getattr("data")?
+ .extract::>()
+ .unwrap_or_default();
let data = match ob.getattr("data") {
Ok(data) => data.extract::>().ok(),
Err(_) => Some(vec![]),
}
- .unwrap();
+ .unwrap_or_default();
Ok(Self {
object: ob.into(),
num_qubits,
@@ -391,12 +400,14 @@ impl IntoPy for CircuitRep {
#[derive(Debug, Clone)]
pub struct CircuitInstructionRep {
operation: GateRep,
+ qubits: Vec,
}
impl FromPyObject<'_> for CircuitInstructionRep {
fn extract(ob: &'_ PyAny) -> PyResult {
+ let qubits = ob.getattr("qubits")?.extract::>()?;
let operation = ob.getattr("operation")?.extract::()?;
- Ok(Self { operation })
+ Ok(Self { operation, qubits })
}
}
@@ -470,7 +481,7 @@ impl EquivalenceLibrary {
/// False otherwise.
pub fn has_entry(&self, gate: GateRep) -> bool {
let key = Key {
- name: gate.name.unwrap(),
+ name: gate.name.unwrap_or_default(),
num_qubits: gate.num_qubits.unwrap_or_default(),
};
self.key_to_node_index.contains_key(&key)
@@ -589,8 +600,8 @@ impl EquivalenceLibrary {
raise_if_param_mismatch(&gate.params, &equivalent_circuit.params)?;
let key: Key = Key {
- name: gate.name.unwrap(),
- num_qubits: gate.num_qubits.unwrap(),
+ name: gate.name.unwrap_or_default(),
+ num_qubits: gate.num_qubits.unwrap_or_default(),
};
let equiv = Equivalence {
params: gate.params,
@@ -604,7 +615,7 @@ impl EquivalenceLibrary {
let sources: HashSet =
HashSet::from_iter(equivalent_circuit.data.iter().map(|inst| Key {
name: inst.operation.name.to_owned().unwrap_or_default(),
- num_qubits: inst.operation.num_qubits.unwrap_or_default(),
+ num_qubits: inst.qubits.len(),
}));
let edges = Vec::from_iter(sources.iter().map(|source| {
(
@@ -687,19 +698,28 @@ fn raise_if_param_mismatch(
gate_params: &[Param],
circuit_parameters: &[Param],
) -> Result<(), EquivalenceError> {
- let gate_parameters: Vec<&Param> = gate_params
+ let uid_gate_parameters: HashSet = gate_params
.iter()
- .filter(|x| matches!(x, Param::ParameterExpression(_)))
+ .filter_map(|x| match x {
+ Param::ParameterExpression(param) => Some(param.id.to_string()),
+ Param::Float(_) => None,
+ Param::Obj(_) => None,
+ })
.collect();
- if circuit_parameters
+ let uid_circuit_parameters: HashSet = circuit_parameters
.iter()
- .any(|x| gate_parameters.contains(&x))
- {
+ .filter_map(|x| match x {
+ Param::ParameterExpression(param) => Some(param.id.to_string()),
+ Param::Float(_) => None,
+ Param::Obj(_) => None,
+ })
+ .collect();
+ if uid_gate_parameters != uid_circuit_parameters {
return Err(EquivalenceError::new_err(format!(
"Cannot add equivalence between circuit and gate \
- of different parameters. Gate params: {:?}. \
- Circuit params: {:?}.",
- gate_parameters, circuit_parameters
+ of different parameters. Gate params: {:#?}. \
+ Circuit params: {:#?}.",
+ gate_params, circuit_parameters
)));
}
Ok(())
@@ -729,7 +749,12 @@ fn rebind_equiv(
let param_map: Vec<(Param, Param)> = equiv_params
.into_iter()
.filter_map(|param| {
- if matches!(param, Param::Obj(_)) {
+ println!(
+ "{:#?}: is expr: {}",
+ param,
+ matches!(param, Param::ParameterExpression(_))
+ );
+ if matches!(param, Param::ParameterExpression(_)) {
Some(param)
} else {
None
@@ -739,11 +764,11 @@ fn rebind_equiv(
.collect();
let dict = param_map.as_slice().into_py_dict_bound(py);
let kwargs = [("inplace", false), ("flat_input", true)].into_py_dict_bound(py);
- let equiv =
+ let new_equiv =
equiv_circuit
.object
.call_method_bound(py, "assign_parameters", (dict,), Some(&kwargs))?;
- equiv.extract::(py)
+ new_equiv.extract::(py)
}
// Errors
From 11611e293c78155f3771f0b7ab865c3d2cfe309a Mon Sep 17 00:00:00 2001
From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
Date: Thu, 13 Jun 2024 14:41:12 +0200
Subject: [PATCH 07/45] Add: Stable infrastructure for EquivalenceLibrary -
TODO: Make elements pickleable.
---
Cargo.lock | 1 +
crates/circuit/Cargo.toml | 1 +
crates/circuit/src/equivalence.rs | 159 ++++++++++++++++--
crates/circuit/src/lib.rs | 8 +-
qiskit/__init__.py | 1 +
qiskit/circuit/equivalence.py | 8 +-
.../passes/basis/basis_translator.py | 11 +-
7 files changed, 162 insertions(+), 27 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index b6ec645603ee..6e2fd814155a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1196,6 +1196,7 @@ name = "qiskit-circuit"
version = "1.2.0"
dependencies = [
"hashbrown 0.14.5",
+ "itertools 0.13.0",
"pyo3",
"rustworkx-core",
]
diff --git a/crates/circuit/Cargo.toml b/crates/circuit/Cargo.toml
index 6ebf9694afc1..bafd60fbaaec 100644
--- a/crates/circuit/Cargo.toml
+++ b/crates/circuit/Cargo.toml
@@ -11,5 +11,6 @@ doctest = false
[dependencies]
hashbrown.workspace = true
+itertools = "0.13.0"
pyo3.workspace = true
rustworkx-core = "0.14.2"
diff --git a/crates/circuit/src/equivalence.rs b/crates/circuit/src/equivalence.rs
index 314de290589e..303a7da06b27 100644
--- a/crates/circuit/src/equivalence.rs
+++ b/crates/circuit/src/equivalence.rs
@@ -10,6 +10,7 @@
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.
+use itertools::Itertools;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::{error::Error, fmt::Display};
@@ -17,7 +18,9 @@ use std::{error::Error, fmt::Display};
use exceptions::CircuitError;
use hashbrown::{HashMap, HashSet};
use pyo3::sync::GILOnceCell;
+use pyo3::types::PyDict;
use pyo3::{prelude::*, types::IntoPyDict};
+
use rustworkx_core::petgraph::{
graph::{DiGraph, EdgeIndex, NodeIndex},
visit::EdgeRef,
@@ -73,7 +76,7 @@ pub static PYDIGRAPH: ImportOnceCell = ImportOnceCell::new("rustworkx", "PyDiGra
// Custom Structs
-#[pyclass(sequence)]
+#[pyclass(sequence, module = "qiskit._accelerate.circuit.equivalence")]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Key {
#[pyo3(get)]
@@ -85,6 +88,7 @@ pub struct Key {
#[pymethods]
impl Key {
#[new]
+ #[pyo3(signature = (name, num_qubits))]
fn new(name: String, num_qubits: usize) -> Self {
Self { name, num_qubits }
}
@@ -102,6 +106,15 @@ impl Key {
fn __repr__(slf: PyRef<'_, Self>) -> String {
slf.to_string()
}
+
+ fn __getstate__(slf: PyRef) -> (String, usize) {
+ (slf.name.to_owned(), slf.num_qubits)
+ }
+
+ fn __setstate__(mut slf: PyRefMut, state: (String, usize)) {
+ slf.name = state.0;
+ slf.num_qubits = state.1;
+ }
}
impl Display for Key {
@@ -114,8 +127,8 @@ impl Display for Key {
}
}
-#[pyclass(sequence)]
-#[derive(Debug, Clone)]
+#[pyclass(sequence, module = "qiskit._accelerate.circuit.equivalence")]
+#[derive(Debug, Clone, PartialEq)]
pub struct Equivalence {
#[pyo3(get)]
pub params: Vec,
@@ -126,6 +139,7 @@ pub struct Equivalence {
#[pymethods]
impl Equivalence {
#[new]
+ #[pyo3(signature = (params, circuit))]
fn new(params: Vec, circuit: CircuitRep) -> Self {
Self { circuit, params }
}
@@ -133,6 +147,19 @@ impl Equivalence {
fn __repr__(&self) -> String {
self.to_string()
}
+
+ fn __eq__(&self, other: Self) -> bool {
+ self.eq(&other)
+ }
+
+ fn __getstate__(slf: PyRef) -> (Vec, CircuitRep) {
+ (slf.params.to_owned(), slf.circuit.to_owned())
+ }
+
+ fn __setstate__(mut slf: PyRefMut, state: (Vec, CircuitRep)) {
+ slf.params = state.0;
+ slf.circuit = state.1;
+ }
}
impl Display for Equivalence {
@@ -145,8 +172,8 @@ impl Display for Equivalence {
}
}
-#[pyclass(sequence)]
-#[derive(Debug, Clone)]
+#[pyclass(sequence, module = "qiskit._accelerate.circuit.equivalence")]
+#[derive(Debug, Clone, PartialEq)]
pub struct NodeData {
#[pyo3(get)]
key: Key,
@@ -157,6 +184,7 @@ pub struct NodeData {
#[pymethods]
impl NodeData {
#[new]
+ #[pyo3(signature = (key, equivs))]
fn new(key: Key, equivs: Vec) -> Self {
Self { key, equivs }
}
@@ -164,19 +192,37 @@ impl NodeData {
fn __repr__(&self) -> String {
self.to_string()
}
+
+ fn __eq__(&self, other: Self) -> bool {
+ self.eq(&other)
+ }
+
+ fn __getstate__(slf: PyRef) -> (Key, Vec) {
+ (slf.key.to_owned(), slf.equivs.to_owned())
+ }
+
+ fn __setstate__(mut slf: PyRefMut, state: (Key, Vec)) {
+ slf.key = state.0;
+ slf.equivs = state.1;
+ }
}
impl Display for NodeData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "NodeData(key={}, equivs={:#?})", self.key, self.equivs)
+ write!(
+ f,
+ "NodeData(key={}, equivs=[{}])",
+ self.key,
+ self.equivs.iter().format(", ")
+ )
}
}
-#[pyclass(sequence)]
-#[derive(Debug, Clone)]
+#[pyclass(sequence, module = "qiskit._accelerate.circuit.equivalence")]
+#[derive(Debug, Clone, PartialEq)]
pub struct EdgeData {
#[pyo3(get)]
- pub index: u32,
+ pub index: usize,
#[pyo3(get)]
pub num_gates: usize,
#[pyo3(get)]
@@ -188,7 +234,8 @@ pub struct EdgeData {
#[pymethods]
impl EdgeData {
#[new]
- fn new(index: u32, num_gates: usize, rule: Equivalence, source: Key) -> Self {
+ #[pyo3(signature = (index, num_gates, rule, source))]
+ fn new(index: usize, num_gates: usize, rule: Equivalence, source: Key) -> Self {
Self {
index,
num_gates,
@@ -200,6 +247,26 @@ impl EdgeData {
fn __repr__(&self) -> String {
self.to_string()
}
+
+ fn __eq__(&self, other: Self) -> bool {
+ self.eq(&other)
+ }
+
+ fn __getstate__(slf: PyRef) -> (usize, usize, Equivalence, Key) {
+ (
+ slf.index,
+ slf.num_gates,
+ slf.rule.to_owned(),
+ slf.source.to_owned(),
+ )
+ }
+
+ fn __setstate__(mut slf: PyRefMut, state: (usize, usize, Equivalence, Key)) {
+ slf.index = state.0;
+ slf.num_gates = state.1;
+ slf.rule = state.2;
+ slf.source = state.3;
+ }
}
impl Display for EdgeData {
@@ -390,6 +457,12 @@ impl FromPyObject<'_> for CircuitRep {
}
}
+impl PartialEq for CircuitRep {
+ fn eq(&self, other: &Self) -> bool {
+ self.object.is(&other.object)
+ }
+}
+
impl IntoPy for CircuitRep {
fn into_py(self, _py: Python<'_>) -> PyObject {
self.object
@@ -415,7 +488,11 @@ impl FromPyObject<'_> for CircuitInstructionRep {
type GraphType = DiGraph;
type KTIType = HashMap;
-#[pyclass(subclass, name = "BaseEquivalenceLibrary")]
+#[pyclass(
+ subclass,
+ name = "BaseEquivalenceLibrary",
+ module = "qiskit._accelerate.circuit.equivalence"
+)]
#[derive(Debug, Clone)]
pub struct EquivalenceLibrary {
_graph: GraphType,
@@ -552,6 +629,59 @@ impl EquivalenceLibrary {
fn node_index(&self, key: Key) -> usize {
self.key_to_node_index[&key].index()
}
+
+ fn __getstate__(slf: PyRef) -> PyResult> {
+ let ret = PyDict::new_bound(slf.py());
+ ret.set_item("rule_id", slf.rule_id)?;
+ let key_to_usize_node: HashMap<(String, usize), usize> = HashMap::from_iter(
+ slf.key_to_node_index
+ .iter()
+ .map(|(key, val)| ((key.name.to_string(), key.num_qubits), val.index())),
+ );
+ ret.set_item("key_to_node_index", key_to_usize_node.into_py(slf.py()))?;
+ let graph_nodes: Vec = slf._graph.node_weights().cloned().collect();
+ ret.set_item("graph_nodes", graph_nodes.into_py(slf.py()))?;
+ let graph_edges: Vec<(usize, usize, EdgeData)> = slf
+ ._graph
+ .edge_indices()
+ .map(|edge_id| {
+ (
+ slf._graph.edge_endpoints(edge_id).unwrap(),
+ slf._graph.edge_weight(edge_id).unwrap(),
+ )
+ })
+ .map(|((source, target), weight)| (source.index(), target.index(), weight.to_owned()))
+ .collect_vec();
+ ret.set_item("graph_edges", graph_edges.into_py(slf.py()))?;
+ Ok(ret)
+ }
+
+ fn __setstate__(mut slf: PyRefMut, state: &Bound<'_, PyDict>) -> PyResult<()> {
+ slf.rule_id = state.get_item("rule_id")?.unwrap().extract()?;
+ slf.key_to_node_index = state
+ .get_item("key_to_node_index")?
+ .unwrap()
+ .extract::>()?
+ .into_iter()
+ .map(|(key, val)| (key, NodeIndex::new(val)))
+ .collect();
+ let graph_nodes: Vec = state.get_item("graph_nodes")?.unwrap().extract()?;
+ let graph_edges: Vec<(usize, usize, EdgeData)> =
+ state.get_item("graph_edges")?.unwrap().extract()?;
+ slf._graph = GraphType::new();
+ for node_weight in graph_nodes {
+ slf._graph.add_node(node_weight);
+ }
+ for (source_node, target_node, edge_weight) in graph_edges {
+ slf._graph.add_edge(
+ NodeIndex::new(source_node),
+ NodeIndex::new(target_node),
+ edge_weight,
+ );
+ }
+ slf.graph = None;
+ Ok(())
+ }
}
// Rust native methods
@@ -622,7 +752,7 @@ impl EquivalenceLibrary {
self.set_default_node(source.to_owned()),
target,
EdgeData {
- index: self.rule_id as u32,
+ index: self.rule_id,
num_gates: sources.len(),
rule: equiv.to_owned(),
source: source.to_owned(),
@@ -749,11 +879,6 @@ fn rebind_equiv(
let param_map: Vec<(Param, Param)> = equiv_params
.into_iter()
.filter_map(|param| {
- println!(
- "{:#?}: is expr: {}",
- param,
- matches!(param, Param::ParameterExpression(_))
- );
if matches!(param, Param::ParameterExpression(_)) {
Some(param)
} else {
diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs
index 2a4bc594d1c3..f11b3c78b389 100644
--- a/crates/circuit/src/lib.rs
+++ b/crates/circuit/src/lib.rs
@@ -19,8 +19,8 @@ mod bit_data;
mod interner;
mod packed_instruction;
-use pyo3::prelude::*;
use pyo3::types::PySlice;
+use pyo3::{prelude::*, wrap_pymodule};
/// A private enumeration type used to extract arguments to pymethod
/// that may be either an index or a slice
@@ -64,12 +64,8 @@ impl From for BitType {
#[pymodule]
pub fn circuit(m: Bound) -> PyResult<()> {
+ m.add_wrapped(wrap_pymodule!(equivalence::equivalence))?;
m.add_class::()?;
- m.add_class::()?;
- m.add_class::()?;
- m.add_class::()?;
- m.add_class::()?;
- m.add_class::()?;
m.add_class::()?;
m.add_class::()?;
m.add_class::()?;
diff --git a/qiskit/__init__.py b/qiskit/__init__.py
index fce544333478..4326da36f7a0 100644
--- a/qiskit/__init__.py
+++ b/qiskit/__init__.py
@@ -60,6 +60,7 @@
# We manually define them on import so people can directly import qiskit._accelerate.* submodules
# and not have to rely on attribute access. No action needed for top-level extension packages.
sys.modules["qiskit._accelerate.circuit"] = _accelerate.circuit
+sys.modules["qiskit._accelerate.circuit.equivalence"] = _accelerate.circuit.equivalence
sys.modules["qiskit._accelerate.convert_2q_block_matrix"] = _accelerate.convert_2q_block_matrix
sys.modules["qiskit._accelerate.dense_layout"] = _accelerate.dense_layout
sys.modules["qiskit._accelerate.error_map"] = _accelerate.error_map
diff --git a/qiskit/circuit/equivalence.py b/qiskit/circuit/equivalence.py
index 28ce5a4b92dd..418ab231e66d 100644
--- a/qiskit/circuit/equivalence.py
+++ b/qiskit/circuit/equivalence.py
@@ -23,7 +23,13 @@
from .exceptions import CircuitError
from .parameter import Parameter
from .parameterexpression import ParameterExpression
-from qiskit._accelerate.circuit import BaseEquivalenceLibrary, Key, Equivalence, NodeData, EdgeData
+from qiskit._accelerate.circuit.equivalence import (
+ BaseEquivalenceLibrary,
+ Key,
+ Equivalence,
+ NodeData,
+ EdgeData,
+)
class EquivalenceLibrary(BaseEquivalenceLibrary):
diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py
index bcac1f5d4faf..4df901f90408 100644
--- a/qiskit/transpiler/passes/basis/basis_translator.py
+++ b/qiskit/transpiler/passes/basis/basis_translator.py
@@ -32,7 +32,7 @@
)
from qiskit.dagcircuit import DAGCircuit
from qiskit.converters import circuit_to_dag, dag_to_circuit
-from qiskit.circuit.equivalence import Key, NodeData
+from qiskit.circuit.equivalence import Key, NodeData, Equivalence
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.exceptions import TranspilerError
@@ -541,7 +541,7 @@ def _basis_search(equiv_lib, source_basis, target_basis):
logger.debug("Begining basis search from %s to %s.", source_basis, target_basis)
source_basis = {
- (gate_name, gate_num_qubits)
+ Key(gate_name, gate_num_qubits)
for gate_name, gate_num_qubits in source_basis
if gate_name not in target_basis
}
@@ -559,7 +559,12 @@ def _basis_search(equiv_lib, source_basis, target_basis):
# we add a dummy node and connect it with gates in the target basis.
# we'll start the search from this dummy node.
- dummy = graph.add_node(NodeData(key="key", equivs=[("dummy starting node", 0)]))
+ dummy = graph.add_node(
+ NodeData(
+ key=Key("key", 0),
+ equivs=[Equivalence([], QuantumCircuit(0, name="dummy starting node"))],
+ )
+ )
try:
graph.add_edges_from_no_data(
From 1bf0316cf7560deb3965ffecf3fd0e60b208b7bc Mon Sep 17 00:00:00 2001
From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
Date: Fri, 14 Jun 2024 23:01:20 +0200
Subject: [PATCH 08/45] Add: Default methods to equivalence data structures.
---
crates/circuit/src/equivalence.rs | 55 +++++++++++++++++++++++++------
1 file changed, 45 insertions(+), 10 deletions(-)
diff --git a/crates/circuit/src/equivalence.rs b/crates/circuit/src/equivalence.rs
index 303a7da06b27..68b945535765 100644
--- a/crates/circuit/src/equivalence.rs
+++ b/crates/circuit/src/equivalence.rs
@@ -88,7 +88,7 @@ pub struct Key {
#[pymethods]
impl Key {
#[new]
- #[pyo3(signature = (name, num_qubits))]
+ #[pyo3(signature = (name="".to_string(), num_qubits=0))]
fn new(name: String, num_qubits: usize) -> Self {
Self { name, num_qubits }
}
@@ -127,8 +127,17 @@ impl Display for Key {
}
}
+impl Default for Key {
+ fn default() -> Self {
+ Self {
+ name: "".to_string(),
+ num_qubits: 0,
+ }
+ }
+}
+
#[pyclass(sequence, module = "qiskit._accelerate.circuit.equivalence")]
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Default)]
pub struct Equivalence {
#[pyo3(get)]
pub params: Vec,
@@ -139,7 +148,7 @@ pub struct Equivalence {
#[pymethods]
impl Equivalence {
#[new]
- #[pyo3(signature = (params, circuit))]
+ #[pyo3(signature = (params=vec![], circuit=CircuitRep::default()))]
fn new(params: Vec, circuit: CircuitRep) -> Self {
Self { circuit, params }
}
@@ -166,14 +175,14 @@ impl Display for Equivalence {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
- "Equivalence(params={:?}, circuits={:?})",
+ "Equivalence(params={:?}, circuit={})",
self.params, self.circuit
)
}
}
#[pyclass(sequence, module = "qiskit._accelerate.circuit.equivalence")]
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Default)]
pub struct NodeData {
#[pyo3(get)]
key: Key,
@@ -184,7 +193,7 @@ pub struct NodeData {
#[pymethods]
impl NodeData {
#[new]
- #[pyo3(signature = (key, equivs))]
+ #[pyo3(signature = (key=Key::default(), equivs=vec![]))]
fn new(key: Key, equivs: Vec) -> Self {
Self { key, equivs }
}
@@ -219,7 +228,7 @@ impl Display for NodeData {
}
#[pyclass(sequence, module = "qiskit._accelerate.circuit.equivalence")]
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Default)]
pub struct EdgeData {
#[pyo3(get)]
pub index: usize,
@@ -234,7 +243,7 @@ pub struct EdgeData {
#[pymethods]
impl EdgeData {
#[new]
- #[pyo3(signature = (index, num_gates, rule, source))]
+ #[pyo3(signature = (index=0, num_gates=0, rule=Equivalence::default(), source=Key::default()))]
fn new(index: usize, num_gates: usize, rule: Equivalence, source: Key) -> Self {
Self {
index,
@@ -457,6 +466,19 @@ impl FromPyObject<'_> for CircuitRep {
}
}
+impl Display for CircuitRep {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let py_rep_str = Python::with_gil(|py| -> PyResult {
+ match self.object.call_method0(py, "__repr__") {
+ Ok(str_obj) => str_obj.extract::(py),
+ Err(_) => Ok("None".to_owned()),
+ }
+ })
+ .unwrap();
+ write!(f, "{}", py_rep_str)
+ }
+}
+
impl PartialEq for CircuitRep {
fn eq(&self, other: &Self) -> bool {
self.object.is(&other.object)
@@ -469,6 +491,19 @@ impl IntoPy for CircuitRep {
}
}
+impl Default for CircuitRep {
+ fn default() -> Self {
+ Self {
+ object: Python::with_gil(|py| py.None()),
+ num_qubits: None,
+ num_clbits: None,
+ label: None,
+ params: vec![],
+ data: vec![],
+ }
+ }
+}
+
// Temporary Representation of CircuitInstruction
#[derive(Debug, Clone)]
pub struct CircuitInstructionRep {
@@ -661,9 +696,9 @@ impl EquivalenceLibrary {
slf.key_to_node_index = state
.get_item("key_to_node_index")?
.unwrap()
- .extract::>()?
+ .extract::>()?
.into_iter()
- .map(|(key, val)| (key, NodeIndex::new(val)))
+ .map(|((name, num_qubits), val)| (Key::new(name, num_qubits), NodeIndex::new(val)))
.collect();
let graph_nodes: Vec = state.get_item("graph_nodes")?.unwrap().extract()?;
let graph_edges: Vec<(usize, usize, EdgeData)> =
From ab57e3e2a68ac2a7532d6b73e0511f9501ed8f67 Mon Sep 17 00:00:00 2001
From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
Date: Sat, 15 Jun 2024 00:17:54 +0200
Subject: [PATCH 09/45] Fix: Adapt to new Gate Structure
---
crates/circuit/src/circuit_instruction.rs | 13 +-
crates/circuit/src/equivalence.rs | 345 +++++++---------------
crates/circuit/src/imports.rs | 2 +-
crates/circuit/src/operations.rs | 23 ++
4 files changed, 132 insertions(+), 251 deletions(-)
diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs
index 2bb90367082d..122a3cd47ce6 100644
--- a/crates/circuit/src/circuit_instruction.rs
+++ b/crates/circuit/src/circuit_instruction.rs
@@ -659,10 +659,7 @@ impl CircuitInstruction {
/// Take a reference to a `CircuitInstruction` and convert the operation
/// inside that to a python side object.
-pub(crate) fn operation_type_to_py(
- py: Python,
- circuit_inst: &CircuitInstruction,
-) -> PyResult {
+pub fn operation_type_to_py(py: Python, circuit_inst: &CircuitInstruction) -> PyResult {
let (label, duration, unit, condition) = match &circuit_inst.extra_attrs {
None => (None, None, None, None),
Some(extra_attrs) => (
@@ -688,7 +685,7 @@ pub(crate) fn operation_type_to_py(
/// a Python side full-fat Qiskit operation as a PyObject. This is typically
/// used by accessor functions that need to return an operation to Qiskit, such
/// as accesing `CircuitInstruction.operation`.
-pub(crate) fn operation_type_and_data_to_py(
+pub fn operation_type_and_data_to_py(
py: Python,
operation: &OperationType,
params: &[Param],
@@ -727,8 +724,8 @@ pub(crate) fn operation_type_and_data_to_py(
/// A container struct that contains the output from the Python object to
/// conversion to construct a CircuitInstruction object
-#[derive(Debug)]
-pub(crate) struct OperationTypeConstruct {
+#[derive(Debug, Clone)]
+pub struct OperationTypeConstruct {
pub operation: OperationType,
pub params: SmallVec<[Param; 3]>,
pub label: Option,
@@ -740,7 +737,7 @@ pub(crate) struct OperationTypeConstruct {
/// Convert an inbound Python object for a Qiskit operation and build a rust
/// representation of that operation. This will map it to appropriate variant
/// of operation type based on class
-pub(crate) fn convert_py_to_operation_type(
+pub fn convert_py_to_operation_type(
py: Python,
py_op: PyObject,
) -> PyResult {
diff --git a/crates/circuit/src/equivalence.rs b/crates/circuit/src/equivalence.rs
index 68b945535765..a22feb512bfe 100644
--- a/crates/circuit/src/equivalence.rs
+++ b/crates/circuit/src/equivalence.rs
@@ -11,13 +11,13 @@
// that they have been altered from the originals.
use itertools::Itertools;
+use smallvec::{smallvec, SmallVec};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::{error::Error, fmt::Display};
use exceptions::CircuitError;
use hashbrown::{HashMap, HashSet};
-use pyo3::sync::GILOnceCell;
use pyo3::types::PyDict;
use pyo3::{prelude::*, types::IntoPyDict};
@@ -26,52 +26,18 @@ use rustworkx_core::petgraph::{
visit::EdgeRef,
};
+use crate::circuit_instruction::{
+ convert_py_to_operation_type, CircuitInstruction as CircuitInstructionBase,
+ OperationTypeConstruct,
+};
+use crate::imports::ImportOnceCell;
+use crate::operations::Param;
+use crate::operations::{Operation, OperationType};
+
mod exceptions {
use pyo3::import_exception_bound;
import_exception_bound! {qiskit.circuit.exceptions, CircuitError}
}
-
-/// Helper wrapper around `GILOnceCell` instances that are just intended to store a Python object
-/// that is lazily imported.
-pub struct ImportOnceCell {
- module: &'static str,
- object: &'static str,
- cell: GILOnceCell>,
-}
-
-impl ImportOnceCell {
- const fn new(module: &'static str, object: &'static str) -> Self {
- Self {
- module,
- object,
- cell: GILOnceCell::new(),
- }
- }
-
- /// Get the underlying GIL-independent reference to the contained object, importing if
- /// required.
- #[inline]
- pub fn get(&self, py: Python) -> &Py {
- self.cell.get_or_init(py, || {
- py.import_bound(self.module)
- .unwrap()
- .getattr(self.object)
- .unwrap()
- .unbind()
- })
- }
-
- /// Get a GIL-bound reference to the contained object, importing if required.
- #[inline]
- pub fn get_bound<'py>(&self, py: Python<'py>) -> &Bound<'py, PyAny> {
- self.get(py).bind(py)
- }
-}
-
-pub static PARAMETER_EXPRESSION: ImportOnceCell =
- ImportOnceCell::new("qiskit.circuit.parameterexpression", "ParameterExpression");
-pub static QUANTUM_CIRCUIT: ImportOnceCell =
- ImportOnceCell::new("qiskit.circuit.quantumcircuit", "QuantumCircuit");
pub static PYDIGRAPH: ImportOnceCell = ImportOnceCell::new("rustworkx", "PyDiGraph");
// Custom Structs
@@ -82,14 +48,14 @@ pub struct Key {
#[pyo3(get)]
pub name: String,
#[pyo3(get)]
- pub num_qubits: usize,
+ pub num_qubits: u32,
}
#[pymethods]
impl Key {
#[new]
#[pyo3(signature = (name="".to_string(), num_qubits=0))]
- fn new(name: String, num_qubits: usize) -> Self {
+ fn new(name: String, num_qubits: u32) -> Self {
Self { name, num_qubits }
}
@@ -107,11 +73,11 @@ impl Key {
slf.to_string()
}
- fn __getstate__(slf: PyRef) -> (String, usize) {
+ fn __getstate__(slf: PyRef) -> (String, u32) {
(slf.name.to_owned(), slf.num_qubits)
}
- fn __setstate__(mut slf: PyRefMut, state: (String, usize)) {
+ fn __setstate__(mut slf: PyRefMut, state: (String, u32)) {
slf.name = state.0;
slf.num_qubits = state.1;
}
@@ -140,7 +106,7 @@ impl Default for Key {
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Equivalence {
#[pyo3(get)]
- pub params: Vec,
+ pub params: SmallVec<[Param; 3]>,
#[pyo3(get)]
pub circuit: CircuitRep,
}
@@ -148,8 +114,8 @@ pub struct Equivalence {
#[pymethods]
impl Equivalence {
#[new]
- #[pyo3(signature = (params=vec![], circuit=CircuitRep::default()))]
- fn new(params: Vec, circuit: CircuitRep) -> Self {
+ #[pyo3(signature = (params=smallvec![], circuit=CircuitRep::default()))]
+ fn new(params: SmallVec<[Param; 3]>, circuit: CircuitRep) -> Self {
Self { circuit, params }
}
@@ -161,11 +127,11 @@ impl Equivalence {
self.eq(&other)
}
- fn __getstate__(slf: PyRef) -> (Vec, CircuitRep) {
+ fn __getstate__(slf: PyRef) -> (SmallVec<[Param; 3]>, CircuitRep) {
(slf.params.to_owned(), slf.circuit.to_owned())
}
- fn __setstate__(mut slf: PyRefMut, state: (Vec, CircuitRep)) {
+ fn __setstate__(mut slf: PyRefMut, state: (SmallVec<[Param; 3]>, CircuitRep)) {
slf.params = state.0;
slf.circuit = state.1;
}
@@ -288,135 +254,40 @@ impl Display for EdgeData {
}
}
-// REPRESENTATIONS of non rust objects
-// Temporary definition of Parameter
-#[derive(Clone, Debug)]
-pub enum Param {
- ParameterExpression(ParamExpRep),
- Float(f64),
- Obj(PyObject),
-}
-
-impl<'py> FromPyObject<'py> for Param {
- fn extract_bound(b: &Bound<'py, PyAny>) -> Result {
- Ok(
- if b.is_instance(PARAMETER_EXPRESSION.get_bound(b.py()))?
- || b.is_instance(QUANTUM_CIRCUIT.get_bound(b.py()))?
- {
- let id = b.getattr("_uuid")?.getattr("hex")?.extract::()?;
- Param::ParameterExpression(ParamExpRep {
- id,
- object: b.clone().unbind(),
- })
- } else if let Ok(val) = b.extract::() {
- Param::Float(val)
- } else {
- Param::Obj(b.clone().unbind())
- },
- )
- }
-}
-
-impl IntoPy for Param {
- fn into_py(self, py: Python) -> PyObject {
- match &self {
- Self::Float(val) => val.to_object(py),
- Self::ParameterExpression(val) => val.object.clone_ref(py),
- Self::Obj(val) => val.clone_ref(py),
- }
- }
+// Enum to extract circuit instructions more broadly
+#[derive(Debug, Clone)]
+pub enum CircuitInstruction {
+ Instruction(CircuitInstructionBase),
+ OperationTypeConstruct(OperationTypeConstruct),
}
-impl ToPyObject for Param {
- fn to_object(&self, py: Python) -> PyObject {
+impl CircuitInstruction {
+ #[inline]
+ pub fn operation(&self) -> &OperationType {
match self {
- Self::Float(val) => val.to_object(py),
- Self::ParameterExpression(val) => val.object.clone_ref(py),
- Self::Obj(val) => val.clone_ref(py),
+ CircuitInstruction::Instruction(instruction) => &instruction.operation,
+ CircuitInstruction::OperationTypeConstruct(operation) => &operation.operation,
}
}
-}
-impl Param {
- fn compare(one: &PyObject, other: &PyObject) -> bool {
- Python::with_gil(|py| -> PyResult {
- let other_bound = other.bind(py);
- other_bound.eq(one)
- })
- .unwrap_or_default()
- }
-}
-
-impl PartialEq for Param {
- fn eq(&self, other: &Self) -> bool {
- match (self, other) {
- (Param::Float(s), Param::Float(other)) => s == other,
- (Param::Float(_), Param::ParameterExpression(_)) => false,
- (Param::ParameterExpression(_), Param::Float(_)) => false,
- (Param::ParameterExpression(s), Param::ParameterExpression(other)) => s.id == other.id,
- (Param::ParameterExpression(_), Param::Obj(_)) => false,
- (Param::Float(_), Param::Obj(_)) => false,
- (Param::Obj(_), Param::ParameterExpression(_)) => false,
- (Param::Obj(_), Param::Float(_)) => false,
- (Param::Obj(one), Param::Obj(other)) => Self::compare(one, other),
+ #[inline]
+ pub fn params(&self) -> &[Param] {
+ match self {
+ CircuitInstruction::Instruction(instruction) => &instruction.params,
+ CircuitInstruction::OperationTypeConstruct(operation) => &operation.params,
}
}
}
-/// ParamExpIdentifier
-#[derive(Debug, Clone)]
-pub struct ParamExpRep {
- id: String,
- object: PyObject,
-}
-
-/// Temporary interpretation of Gate
-#[derive(Debug, Clone)]
-pub struct GateRep {
- object: PyObject,
- pub num_qubits: Option,
- pub num_clbits: Option,
- pub name: Option,
- pub label: Option,
- pub params: Vec,
-}
-
-impl FromPyObject<'_> for GateRep {
- fn extract(ob: &'_ PyAny) -> PyResult {
- let num_qubits = match ob.getattr("num_qubits") {
- Ok(num_qubits) => num_qubits.extract::().ok(),
- Err(_) => None,
- };
- let num_clbits = match ob.getattr("num_clbits") {
- Ok(num_clbits) => num_clbits.extract::().ok(),
- Err(_) => None,
- };
- let name = match ob.getattr("name") {
- Ok(name) => name.extract::().ok(),
- Err(_) => None,
- };
- let label = match ob.getattr("label") {
- Ok(label) => label.extract::().ok(),
- Err(_) => None,
- };
- let params = match ob.getattr("params") {
- Ok(params) => params.extract::>().ok(),
- Err(_) => Some(vec![]),
+impl<'py> FromPyObject<'py> for CircuitInstruction {
+ fn extract(ob: &'py PyAny) -> PyResult {
+ match ob.extract::() {
+ Ok(inst) => Ok(Self::Instruction(inst)),
+ Err(_) => Ok(Self::OperationTypeConstruct(convert_py_to_operation_type(
+ ob.py(),
+ ob.into(),
+ )?)),
}
- .unwrap_or_default();
- Ok(Self {
- object: ob.into(),
- num_qubits,
- num_clbits,
- name,
- label,
- params,
- })
- }
-}
-impl IntoPy for GateRep {
- fn into_py(self, _py: Python<'_>) -> PyObject {
- self.object
}
}
@@ -424,23 +295,25 @@ impl IntoPy for GateRep {
#[derive(Debug, Clone)]
pub struct CircuitRep {
object: PyObject,
- pub num_qubits: Option,
- pub num_clbits: Option,
+ pub num_qubits: u32,
+ pub num_clbits: u32,
pub label: Option,
- pub params: Vec,
- pub data: Vec,
+ pub params: SmallVec<[Param; 3]>,
+ pub data: Vec,
}
impl FromPyObject<'_> for CircuitRep {
fn extract(ob: &'_ PyAny) -> PyResult {
let num_qubits = match ob.getattr("num_qubits") {
- Ok(num_qubits) => num_qubits.extract::().ok(),
+ Ok(num_qubits) => num_qubits.extract::().ok(),
Err(_) => None,
- };
+ }
+ .unwrap_or_default();
let num_clbits = match ob.getattr("num_clbits") {
- Ok(num_clbits) => num_clbits.extract::().ok(),
+ Ok(num_clbits) => num_clbits.extract::().ok(),
Err(_) => None,
- };
+ }
+ .unwrap_or_default();
let label = match ob.getattr("label") {
Ok(label) => label.extract::().ok(),
Err(_) => None,
@@ -448,10 +321,10 @@ impl FromPyObject<'_> for CircuitRep {
let params = ob
.getattr("parameters")?
.getattr("data")?
- .extract::>()
+ .extract::>()
.unwrap_or_default();
let data = match ob.getattr("data") {
- Ok(data) => data.extract::>().ok(),
+ Ok(data) => data.extract::>().ok(),
Err(_) => Some(vec![]),
}
.unwrap_or_default();
@@ -495,30 +368,15 @@ impl Default for CircuitRep {
fn default() -> Self {
Self {
object: Python::with_gil(|py| py.None()),
- num_qubits: None,
- num_clbits: None,
+ num_qubits: 0,
+ num_clbits: 0,
label: None,
- params: vec![],
+ params: smallvec![],
data: vec![],
}
}
}
-// Temporary Representation of CircuitInstruction
-#[derive(Debug, Clone)]
-pub struct CircuitInstructionRep {
- operation: GateRep,
- qubits: Vec,
-}
-
-impl FromPyObject<'_> for CircuitInstructionRep {
- fn extract(ob: &'_ PyAny) -> PyResult {
- let qubits = ob.getattr("qubits")?.extract::>()?;
- let operation = ob.getattr("operation")?.extract::()?;
- Ok(Self { operation, qubits })
- }
-}
-
// Custom Types
type GraphType = DiGraph;
type KTIType = HashMap;
@@ -576,7 +434,11 @@ impl EquivalenceLibrary {
/// gate (Gate): A Gate instance.
/// equivalent_circuit (QuantumCircuit): A circuit equivalently
/// implementing the given Gate.
- fn add_equivalence(&mut self, gate: GateRep, equivalent_circuit: CircuitRep) -> PyResult<()> {
+ fn add_equivalence(
+ &mut self,
+ gate: CircuitInstruction,
+ equivalent_circuit: CircuitRep,
+ ) -> PyResult<()> {
match self.add_equiv(gate, equivalent_circuit) {
Ok(_) => Ok(()),
Err(e) => Err(CircuitError::new_err(e.message)),
@@ -591,10 +453,10 @@ impl EquivalenceLibrary {
/// Returns:
/// Bool: True if gate has a known decomposition in the library.
/// False otherwise.
- pub fn has_entry(&self, gate: GateRep) -> bool {
+ pub fn has_entry(&self, gate: CircuitInstruction) -> bool {
let key = Key {
- name: gate.name.unwrap_or_default(),
- num_qubits: gate.num_qubits.unwrap_or_default(),
+ name: gate.operation().name().to_string(),
+ num_qubits: gate.operation().num_qubits(),
};
self.key_to_node_index.contains_key(&key)
}
@@ -610,7 +472,7 @@ impl EquivalenceLibrary {
/// gate (Gate): A Gate instance.
/// entry (List['QuantumCircuit']) : A list of QuantumCircuits, each
/// equivalently implementing the given Gate.
- fn set_entry(&mut self, gate: GateRep, entry: Vec) -> PyResult<()> {
+ fn set_entry(&mut self, gate: CircuitInstruction, entry: Vec) -> PyResult<()> {
match self.set_entry_native(&gate, &entry) {
Ok(_) => Ok(()),
Err(e) => Err(CircuitError::new_err(e.message)),
@@ -634,16 +496,16 @@ impl EquivalenceLibrary {
/// the library, from earliest to latest, from top to base. The
/// ordering of the StandardEquivalenceLibrary will not generally be
/// consistent across Qiskit versions.
- pub fn get_entry(&self, py: Python<'_>, gate: GateRep) -> Vec {
+ pub fn get_entry(&self, py: Python<'_>, gate: CircuitInstruction) -> Vec {
let key = Key {
- name: gate.name.unwrap_or_default(),
- num_qubits: gate.num_qubits.unwrap_or_default(),
+ name: gate.operation().name().to_string(),
+ num_qubits: gate.operation().num_qubits(),
};
- let query_params = gate.params;
+ let query_params = gate.params();
self.get_equivalences(&key)
.into_iter()
- .filter_map(|equivalence| rebind_equiv(py, equivalence, &query_params).ok())
+ .filter_map(|equivalence| rebind_equiv(py, equivalence, query_params).ok())
.collect()
}
@@ -668,7 +530,7 @@ impl EquivalenceLibrary {
fn __getstate__(slf: PyRef) -> PyResult> {
let ret = PyDict::new_bound(slf.py());
ret.set_item("rule_id", slf.rule_id)?;
- let key_to_usize_node: HashMap<(String, usize), usize> = HashMap::from_iter(
+ let key_to_usize_node: HashMap<(String, u32), usize> = HashMap::from_iter(
slf.key_to_node_index
.iter()
.map(|(key, val)| ((key.name.to_string(), key.num_qubits), val.index())),
@@ -696,7 +558,7 @@ impl EquivalenceLibrary {
slf.key_to_node_index = state
.get_item("key_to_node_index")?
.unwrap()
- .extract::>()?
+ .extract::>()?
.into_iter()
.map(|((name, num_qubits), val)| (Key::new(name, num_qubits), NodeIndex::new(val)))
.collect();
@@ -758,18 +620,18 @@ impl EquivalenceLibrary {
/// implementing the given Gate.
pub fn add_equiv(
&mut self,
- gate: GateRep,
+ gate: CircuitInstruction,
equivalent_circuit: CircuitRep,
) -> Result<(), EquivalenceError> {
raise_if_shape_mismatch(&gate, &equivalent_circuit)?;
- raise_if_param_mismatch(&gate.params, &equivalent_circuit.params)?;
+ raise_if_param_mismatch(gate.params(), &equivalent_circuit.params)?;
let key: Key = Key {
- name: gate.name.unwrap_or_default(),
- num_qubits: gate.num_qubits.unwrap_or_default(),
+ name: gate.operation().name().to_string(),
+ num_qubits: gate.operation().num_qubits(),
};
let equiv = Equivalence {
- params: gate.params,
+ params: gate.params().into(),
circuit: equivalent_circuit.to_owned(),
};
@@ -779,8 +641,8 @@ impl EquivalenceLibrary {
}
let sources: HashSet =
HashSet::from_iter(equivalent_circuit.data.iter().map(|inst| Key {
- name: inst.operation.name.to_owned().unwrap_or_default(),
- num_qubits: inst.qubits.len(),
+ name: inst.operation().name().to_owned(),
+ num_qubits: inst.operation().num_qubits(),
}));
let edges = Vec::from_iter(sources.iter().map(|source| {
(
@@ -816,17 +678,17 @@ impl EquivalenceLibrary {
/// equivalently implementing the given Gate.
pub fn set_entry_native(
&mut self,
- gate: &GateRep,
+ gate: &CircuitInstruction,
entry: &Vec,
) -> Result<(), EquivalenceError> {
for equiv in entry {
raise_if_shape_mismatch(gate, equiv)?;
- raise_if_param_mismatch(&gate.params, &equiv.params)?;
+ raise_if_param_mismatch(gate.params(), &equiv.params)?;
}
let key = Key {
- name: gate.name.to_owned().unwrap_or_default(),
- num_qubits: gate.num_qubits.unwrap_or_default(),
+ name: gate.operation().name().to_owned(),
+ num_qubits: gate.operation().num_qubits(),
};
let node_index = self.set_default_node(key);
@@ -863,23 +725,17 @@ fn raise_if_param_mismatch(
gate_params: &[Param],
circuit_parameters: &[Param],
) -> Result<(), EquivalenceError> {
- let uid_gate_parameters: HashSet = gate_params
+ let gate_params = gate_params
.iter()
- .filter_map(|x| match x {
- Param::ParameterExpression(param) => Some(param.id.to_string()),
- Param::Float(_) => None,
- Param::Obj(_) => None,
- })
- .collect();
- let uid_circuit_parameters: HashSet = circuit_parameters
+ .filter(|param| matches!(param, Param::ParameterExpression(_)))
+ .collect_vec();
+ let circuit_parameters = circuit_parameters
.iter()
- .filter_map(|x| match x {
- Param::ParameterExpression(param) => Some(param.id.to_string()),
- Param::Float(_) => None,
- Param::Obj(_) => None,
- })
- .collect();
- if uid_gate_parameters != uid_circuit_parameters {
+ .filter(|param| matches!(param, Param::ParameterExpression(_)))
+ .collect_vec();
+ if gate_params.len() == circuit_parameters.len()
+ && gate_params.iter().any(|x| !circuit_parameters.contains(x))
+ {
return Err(EquivalenceError::new_err(format!(
"Cannot add equivalence between circuit and gate \
of different parameters. Gate params: {:#?}. \
@@ -890,16 +746,21 @@ fn raise_if_param_mismatch(
Ok(())
}
-fn raise_if_shape_mismatch(gate: &GateRep, circuit: &CircuitRep) -> Result<(), EquivalenceError> {
- if gate.num_qubits != circuit.num_qubits || gate.num_clbits != circuit.num_clbits {
+fn raise_if_shape_mismatch(
+ gate: &CircuitInstruction,
+ circuit: &CircuitRep,
+) -> Result<(), EquivalenceError> {
+ if gate.operation().num_qubits() != circuit.num_qubits
+ || gate.operation().num_clbits() != circuit.num_clbits
+ {
return Err(EquivalenceError::new_err(format!(
"Cannot add equivalence between circuit and gate \
of different shapes. Gate: {} qubits and {} clbits. \
Circuit: {} qubits and {} clbits.",
- gate.num_qubits.unwrap_or_default(),
- gate.num_clbits.unwrap_or_default(),
- circuit.num_qubits.unwrap_or_default(),
- circuit.num_clbits.unwrap_or_default()
+ gate.operation().num_qubits(),
+ gate.operation().num_clbits(),
+ circuit.num_qubits,
+ circuit.num_clbits
)));
}
Ok(())
diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs
index 050f7f2e053c..a759a3d7b5ae 100644
--- a/crates/circuit/src/imports.rs
+++ b/crates/circuit/src/imports.rs
@@ -28,7 +28,7 @@ pub struct ImportOnceCell {
}
impl ImportOnceCell {
- const fn new(module: &'static str, object: &'static str) -> Self {
+ pub const fn new(module: &'static str, object: &'static str) -> Self {
Self {
module,
object,
diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs
index ead1b8ee1ebb..eaed5402f24d 100644
--- a/crates/circuit/src/operations.rs
+++ b/crates/circuit/src/operations.rs
@@ -173,6 +173,29 @@ impl ToPyObject for Param {
}
}
+impl Param {
+ fn compare(one: &PyObject, other: &PyObject) -> bool {
+ Python::with_gil(|py| -> PyResult {
+ let other_bound = other.bind(py);
+ Ok(other_bound.eq(one)? || other_bound.is(one))
+ })
+ .unwrap_or_default()
+ }
+}
+
+impl PartialEq for Param {
+ fn eq(&self, other: &Self) -> bool {
+ match (self, other) {
+ (Param::Float(s), Param::Float(other)) => s == other,
+ (Param::ParameterExpression(one), Param::ParameterExpression(other)) => {
+ Self::compare(one, other)
+ }
+ (Param::Obj(one), Param::Obj(other)) => Self::compare(one, other),
+ _ => false,
+ }
+ }
+}
+
#[derive(Clone, Debug, Copy, Eq, PartialEq, Hash)]
#[pyclass(module = "qiskit._accelerate.circuit")]
pub enum StandardGate {
From 0e822b506ea2c7962e25f06aff40cae529976c15 Mon Sep 17 00:00:00 2001
From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
Date: Sat, 15 Jun 2024 22:27:24 +0200
Subject: [PATCH 10/45] Fix: Erroneous display of `Parameters`
---
crates/circuit/src/equivalence.rs | 11 ++++++++---
crates/circuit/src/operations.rs | 14 ++++++++++++++
2 files changed, 22 insertions(+), 3 deletions(-)
diff --git a/crates/circuit/src/equivalence.rs b/crates/circuit/src/equivalence.rs
index a22feb512bfe..9d357b547c5c 100644
--- a/crates/circuit/src/equivalence.rs
+++ b/crates/circuit/src/equivalence.rs
@@ -141,8 +141,9 @@ impl Display for Equivalence {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
- "Equivalence(params={:?}, circuit={})",
- self.params, self.circuit
+ "Equivalence(params=[{}], circuit={})",
+ self.params.iter().format(", "),
+ self.circuit
)
}
}
@@ -354,7 +355,11 @@ impl Display for CircuitRep {
impl PartialEq for CircuitRep {
fn eq(&self, other: &Self) -> bool {
- self.object.is(&other.object)
+ Python::with_gil(|py| -> PyResult {
+ let bound = other.object.bind(py);
+ bound.eq(&self.object)
+ })
+ .unwrap_or_default()
}
}
diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs
index eaed5402f24d..a1cec9c617bb 100644
--- a/crates/circuit/src/operations.rs
+++ b/crates/circuit/src/operations.rs
@@ -196,6 +196,20 @@ impl PartialEq for Param {
}
}
+impl std::fmt::Display for Param {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let display_name: String = Python::with_gil(|py| -> PyResult {
+ match self {
+ Param::ParameterExpression(obj) => obj.call_method0(py, "__repr__")?.extract(py),
+ Param::Float(float_param) => Ok(format!("Parameter({})", float_param)),
+ Param::Obj(obj) => obj.call_method0(py, "__repr__")?.extract(py),
+ }
+ })
+ .unwrap_or("None".to_owned());
+ write!(f, "{}", display_name)
+ }
+}
+
#[derive(Clone, Debug, Copy, Eq, PartialEq, Hash)]
#[pyclass(module = "qiskit._accelerate.circuit")]
pub enum StandardGate {
From 6a033c935893ee978e61a53860ed05a84df2674f Mon Sep 17 00:00:00 2001
From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
Date: Mon, 17 Jun 2024 09:18:36 -0400
Subject: [PATCH 11/45] Format: Fix lint test
---
qiskit/circuit/equivalence.py | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/qiskit/circuit/equivalence.py b/qiskit/circuit/equivalence.py
index 418ab231e66d..8730771f4feb 100644
--- a/qiskit/circuit/equivalence.py
+++ b/qiskit/circuit/equivalence.py
@@ -12,18 +12,12 @@
"""Gate equivalence library."""
-import copy
-from collections import namedtuple
-
from rustworkx.visualization import graphviz_draw
import rustworkx as rx
from qiskit.exceptions import InvalidFileError
-from .exceptions import CircuitError
-from .parameter import Parameter
-from .parameterexpression import ParameterExpression
-from qiskit._accelerate.circuit.equivalence import (
+from qiskit._accelerate.circuit.equivalence import ( # pylint: disable=unused-import
BaseEquivalenceLibrary,
Key,
Equivalence,
From b04cae2ed9a64175bfdd4d1cfeab5f1d320550d2 Mon Sep 17 00:00:00 2001
From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
Date: Mon, 17 Jun 2024 18:06:12 -0400
Subject: [PATCH 12/45] Fix: Use EdgeReferences instead of edge_indices. -
Remove stray comment. - Use match instead of matches!.
---
crates/circuit/src/equivalence.rs | 22 ++++++----------------
1 file changed, 6 insertions(+), 16 deletions(-)
diff --git a/crates/circuit/src/equivalence.rs b/crates/circuit/src/equivalence.rs
index 9d357b547c5c..14fddca6a3b2 100644
--- a/crates/circuit/src/equivalence.rs
+++ b/crates/circuit/src/equivalence.rs
@@ -600,13 +600,6 @@ impl EquivalenceLibrary {
self.key_to_node_index.insert(key, node);
node
}
- // *self
- // .key_to_node_index
- // .entry(key.to_owned())
- // .or_insert(self._graph.add_node(NodeData {
- // key,
- // equivs: vec![],
- // }))
}
/// Rust native equivalent to `EquivalenceLibrary.add_equivalence()`
@@ -779,12 +772,9 @@ fn rebind_equiv(
let (equiv_params, equiv_circuit) = (equiv.params, equiv.circuit);
let param_map: Vec<(Param, Param)> = equiv_params
.into_iter()
- .filter_map(|param| {
- if matches!(param, Param::ParameterExpression(_)) {
- Some(param)
- } else {
- None
- }
+ .filter_map(|param| match param {
+ Param::ParameterExpression(_) => Some(param),
+ _ => None,
})
.zip(query_params.iter().cloned())
.collect();
@@ -828,10 +818,10 @@ where
for node in node_weights {
graph.call_method1("add_node", (node.to_owned(),))?;
}
- let edge_weights = pet_graph.edge_indices().map(|edge| {
+ let edge_weights = pet_graph.edge_references().map(|edge| {
(
- pet_graph.edge_endpoints(edge).unwrap(),
- pet_graph.edge_weight(edge).unwrap(),
+ pet_graph.edge_endpoints(edge.id()).unwrap(),
+ pet_graph.edge_weight(edge.id()).unwrap(),
)
});
for ((source, target), weight) in edge_weights {
From 38436cdb5ed96b5f52a11dd557fafcaf7384bc39 Mon Sep 17 00:00:00 2001
From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
Date: Fri, 21 Jun 2024 11:57:14 -0400
Subject: [PATCH 13/45] Fix: Use StableDiGraph for more stable indexing. -
Remove required py argument for get_entry. - Reformat `to_pygraph` to use
`add_nodes_from` and `add_edges_from`. - Other small fixes.
---
crates/circuit/src/equivalence.rs | 86 +++++++++++++++----------------
1 file changed, 43 insertions(+), 43 deletions(-)
diff --git a/crates/circuit/src/equivalence.rs b/crates/circuit/src/equivalence.rs
index 14fddca6a3b2..cad527f62cc4 100644
--- a/crates/circuit/src/equivalence.rs
+++ b/crates/circuit/src/equivalence.rs
@@ -11,6 +11,9 @@
// that they have been altered from the originals.
use itertools::Itertools;
+use rustworkx_core::petgraph::stable_graph::StableDiGraph;
+use rustworkx_core::petgraph::visit::IntoEdgeReferences;
+
use smallvec::{smallvec, SmallVec};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
@@ -22,7 +25,7 @@ use pyo3::types::PyDict;
use pyo3::{prelude::*, types::IntoPyDict};
use rustworkx_core::petgraph::{
- graph::{DiGraph, EdgeIndex, NodeIndex},
+ graph::{EdgeIndex, NodeIndex},
visit::EdgeRef,
};
@@ -383,7 +386,7 @@ impl Default for CircuitRep {
}
// Custom Types
-type GraphType = DiGraph;
+type GraphType = StableDiGraph;
type KTIType = HashMap;
#[pyclass(
@@ -501,7 +504,7 @@ impl EquivalenceLibrary {
/// the library, from earliest to latest, from top to base. The
/// ordering of the StandardEquivalenceLibrary will not generally be
/// consistent across Qiskit versions.
- pub fn get_entry(&self, py: Python<'_>, gate: CircuitInstruction) -> Vec {
+ pub fn get_entry(&self, gate: CircuitInstruction) -> Vec {
let key = Key {
name: gate.operation().name().to_string(),
num_qubits: gate.operation().num_qubits(),
@@ -510,7 +513,7 @@ impl EquivalenceLibrary {
self.get_equivalences(&key)
.into_iter()
- .filter_map(|equivalence| rebind_equiv(py, equivalence, query_params).ok())
+ .filter_map(|equivalence| rebind_equiv(equivalence, query_params))
.collect()
}
@@ -764,29 +767,29 @@ fn raise_if_shape_mismatch(
Ok(())
}
-fn rebind_equiv(
- py: Python<'_>,
- equiv: Equivalence,
- query_params: &[Param],
-) -> PyResult {
- let (equiv_params, equiv_circuit) = (equiv.params, equiv.circuit);
- let param_map: Vec<(Param, Param)> = equiv_params
- .into_iter()
- .filter_map(|param| match param {
- Param::ParameterExpression(_) => Some(param),
- _ => None,
- })
- .zip(query_params.iter().cloned())
- .collect();
- let dict = param_map.as_slice().into_py_dict_bound(py);
- let kwargs = [("inplace", false), ("flat_input", true)].into_py_dict_bound(py);
- let new_equiv =
- equiv_circuit
- .object
- .call_method_bound(py, "assign_parameters", (dict,), Some(&kwargs))?;
- new_equiv.extract::(py)
+fn rebind_equiv(equiv: Equivalence, query_params: &[Param]) -> Option {
+ Python::with_gil(|py| -> PyResult {
+ let (equiv_params, equiv_circuit) = (equiv.params, equiv.circuit);
+ let param_map: Vec<(Param, Param)> = equiv_params
+ .into_iter()
+ .filter_map(|param| match param {
+ Param::ParameterExpression(_) => Some(param),
+ _ => None,
+ })
+ .zip(query_params.iter().cloned())
+ .collect();
+ let dict = param_map.as_slice().into_py_dict_bound(py);
+ let kwargs = [("inplace", false), ("flat_input", true)].into_py_dict_bound(py);
+ let new_equiv = equiv_circuit.object.call_method_bound(
+ py,
+ "assign_parameters",
+ (dict,),
+ Some(&kwargs),
+ )?;
+ new_equiv.extract::(py)
+ })
+ .ok()
}
-
// Errors
#[derive(Debug, Clone)]
@@ -808,28 +811,25 @@ impl Display for EquivalenceError {
}
}
-fn to_pygraph(py: Python<'_>, pet_graph: &DiGraph) -> PyResult
+fn to_pygraph(py: Python<'_>, pet_graph: &StableDiGraph) -> PyResult
where
N: IntoPy + Clone,
E: IntoPy + Clone,
{
let graph = PYDIGRAPH.get_bound(py).call0()?;
- let node_weights = pet_graph.node_weights();
- for node in node_weights {
- graph.call_method1("add_node", (node.to_owned(),))?;
- }
- let edge_weights = pet_graph.edge_references().map(|edge| {
- (
- pet_graph.edge_endpoints(edge.id()).unwrap(),
- pet_graph.edge_weight(edge.id()).unwrap(),
- )
- });
- for ((source, target), weight) in edge_weights {
- graph.call_method1(
- "add_edge",
- (source.index(), target.index(), weight.to_owned()),
- )?;
- }
+ let node_weights: Vec = pet_graph.node_weights().cloned().collect();
+ graph.call_method1("add_nodes_from", (node_weights,))?;
+ let edge_weights: Vec<(usize, usize, E)> = pet_graph
+ .edge_references()
+ .map(|edge| {
+ (
+ edge.source().index(),
+ edge.target().index(),
+ edge.weight().to_owned(),
+ )
+ })
+ .collect();
+ graph.call_method1("add_edges_from", (edge_weights,))?;
Ok(graph.unbind())
}
From e16e57bff4de30bc99b313b0cbd82570d68a4fd1 Mon Sep 17 00:00:00 2001
From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
Date: Fri, 21 Jun 2024 15:50:05 -0400
Subject: [PATCH 14/45] Fix: Use `clone` instead of `to_owned` - Use
`clone_ref` for the PyObject Graph instance.
---
crates/circuit/src/equivalence.rs | 44 +++++++++++++++++--------------
1 file changed, 24 insertions(+), 20 deletions(-)
diff --git a/crates/circuit/src/equivalence.rs b/crates/circuit/src/equivalence.rs
index 2492f55f142a..c243d64ac7d2 100644
--- a/crates/circuit/src/equivalence.rs
+++ b/crates/circuit/src/equivalence.rs
@@ -77,7 +77,7 @@ impl Key {
}
fn __getstate__(slf: PyRef) -> (String, u32) {
- (slf.name.to_owned(), slf.num_qubits)
+ (slf.name.clone(), slf.num_qubits)
}
fn __setstate__(mut slf: PyRefMut, state: (String, u32)) {
@@ -131,7 +131,7 @@ impl Equivalence {
}
fn __getstate__(slf: PyRef) -> (SmallVec<[Param; 3]>, CircuitRep) {
- (slf.params.to_owned(), slf.circuit.to_owned())
+ (slf.params.clone(), slf.circuit.clone())
}
fn __setstate__(mut slf: PyRefMut, state: (SmallVec<[Param; 3]>, CircuitRep)) {
@@ -177,7 +177,7 @@ impl NodeData {
}
fn __getstate__(slf: PyRef) -> (Key, Vec) {
- (slf.key.to_owned(), slf.equivs.to_owned())
+ (slf.key.clone(), slf.equivs.clone())
}
fn __setstate__(mut slf: PyRefMut