From 37070b26887bdc2f648eebae59118a1d952b8095 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 14 May 2024 15:50:54 -0400 Subject: [PATCH] Add: Macro rules for qargs and other sequences. - Create `QargSet` and `PropsMap` using the new macros. - Return a `TargetOpNames` ordered set to python in `operation_names`. - Remove the Python side `operation_names.` - Fix faulty docstring in `target.py`. - Other tweaks and fixes. --- .../src/target_transpiler/gate_map.rs | 149 +------ .../src/target_transpiler/macro_rules.rs | 414 ++++++++++++++++++ .../accelerate/src/target_transpiler/mod.rs | 24 +- .../src/target_transpiler/property_map.rs | 80 +--- .../accelerate/src/target_transpiler/qargs.rs | 90 +--- qiskit/transpiler/target.py | 10 +- 6 files changed, 470 insertions(+), 297 deletions(-) create mode 100644 crates/accelerate/src/target_transpiler/macro_rules.rs diff --git a/crates/accelerate/src/target_transpiler/gate_map.rs b/crates/accelerate/src/target_transpiler/gate_map.rs index 6695b9c85ba8..9d79b95cc3d8 100644 --- a/crates/accelerate/src/target_transpiler/gate_map.rs +++ b/crates/accelerate/src/target_transpiler/gate_map.rs @@ -10,7 +10,7 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use super::property_map::PropsMap; +use super::{macro_rules::key_like_set_iterator, property_map::PropsMap}; use hashbrown::HashSet; use indexmap::{set::IntoIter, IndexMap, IndexSet}; use itertools::Itertools; @@ -24,144 +24,15 @@ use pyo3::{ type GateMapType = IndexMap>; type GateMapIterType = IntoIter; -#[pyclass(sequence)] -#[derive(Debug, Clone)] -pub struct GateMapKeys { - keys: IndexSet, -} - -#[pymethods] -impl GateMapKeys { - fn __iter__(slf: PyRef) -> PyResult> { - let iter = GateMapIter { - iter: slf.keys.clone().into_iter(), - }; - Py::new(slf.py(), iter) - } - - fn __eq__(slf: PyRef, other: Bound) -> PyResult { - for item in other.iter() { - let key = item.extract::()?; - if !(slf.keys.contains(&key)) { - return Ok(false); - } - } - Ok(true) - } - - fn __len__(slf: PyRef) -> usize { - slf.keys.len() - } - - fn __sub__(&self, other: HashSet) -> GateMapKeys { - GateMapKeys { - keys: self - .keys - .iter() - .cloned() - .collect::>() - .difference(&other) - .cloned() - .collect::>(), - } - } - - fn union(&self, other: &Bound) -> PyResult { - if let Ok(set) = other.extract::() { - Ok(Self { - keys: self.keys.union(&set.keys).cloned().collect(), - }) - } else if let Ok(set) = other.extract::>() { - Ok(Self { - keys: self - .keys - .iter() - .cloned() - .collect::>() - .union(&set) - .cloned() - .collect(), - }) - } else { - Err(PyKeyError::new_err( - "Could not perform union, Wrong Key Types", - )) - } - } - - fn intersection(&self, other: &Bound) -> PyResult { - if let Ok(set) = other.extract::() { - Ok(Self { - keys: self.keys.intersection(&set.keys).cloned().collect(), - }) - } else if let Ok(set) = other.extract::>() { - Ok(Self { - keys: self - .keys - .iter() - .cloned() - .collect::>() - .intersection(&set) - .cloned() - .collect(), - }) - } else { - Err(PyKeyError::new_err( - "Could not perform intersection, Wrong Key Types", - )) - } - } - - fn difference(&self, other: &Bound) -> PyResult { - if let Ok(set) = other.extract::() { - Ok(Self { - keys: self.keys.difference(&set.keys).cloned().collect(), - }) - } else if let Ok(set) = other.extract::>() { - Ok(Self { - keys: self - .keys - .iter() - .cloned() - .collect::>() - .difference(&set) - .cloned() - .collect(), - }) - } else { - Err(PyKeyError::new_err( - "Could not perform difference, Wrong Key Types", - )) - } - } - - fn __ior__(&mut self, other: &Bound) -> PyResult<()> { - self.keys = self.union(other)?.keys; - Ok(()) - } - - fn __iand__(&mut self, other: &Bound) -> PyResult<()> { - self.keys = self.intersection(other)?.keys; - Ok(()) - } - - fn __isub__(&mut self, other: &Bound) -> PyResult<()> { - self.keys = self.difference(other)?.keys; - Ok(()) - } - - fn __contains__(slf: PyRef, obj: String) -> PyResult { - Ok(slf.keys.contains(&obj)) - } - - fn __repr__(slf: PyRef) -> String { - let mut output = "gate_map_keys[".to_owned(); - output.push_str(slf.keys.iter().join(", ").as_str()); - output.push(']'); - output - } -} - +key_like_set_iterator!( + GateMapKeys, + GateMapKeysIter, + keys, + String, + IntoIter, + "", + "gate_map_keys" +); #[pyclass] pub struct GateMapIter { iter: GateMapIterType, diff --git a/crates/accelerate/src/target_transpiler/macro_rules.rs b/crates/accelerate/src/target_transpiler/macro_rules.rs new file mode 100644 index 000000000000..b67f9679253e --- /dev/null +++ b/crates/accelerate/src/target_transpiler/macro_rules.rs @@ -0,0 +1,414 @@ +// 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. + +macro_rules! key_like_set_iterator { + ($name:ident, $iter:ident, $keys:ident, $T:ty, $IterType:ty, $doc:literal, $pyrep:literal) => { + #[doc = $doc] + #[pyclass(sequence, module = "qiskit._accelerate.target")] + #[derive(Debug, Clone)] + pub struct $name { + pub $keys: IndexSet<$T>, + } + + #[pymethods] + impl $name { + #[new] + fn new() -> Self { + Self::default() + } + + fn __iter__(slf: PyRef) -> PyResult> { + let iter = $iter { + iter: slf.$keys.clone().into_iter(), + }; + Py::new(slf.py(), iter) + } + + fn __eq__(slf: PyRef, other: Bound) -> PyResult { + if let Ok(set) = other.downcast::() { + for item in set.iter() { + let key = item.extract::<$T>()?; + if !(slf.$keys.contains(&key)) { + return Ok(false); + } + } + } else if let Ok(self_like) = other.extract::() { + for item in self_like.$keys.iter() { + if !(slf.$keys.contains(item)) { + return Ok(false); + } + } + } + + Ok(true) + } + + fn __len__(slf: PyRef) -> usize { + slf.$keys.len() + } + + fn __sub__(&self, other: &Bound) -> PyResult { + self.difference(other) + } + + fn union(&self, other: &Bound) -> PyResult { + if let Ok(set) = other.extract::() { + Ok(Self { + $keys: self.$keys.union(&set.$keys).cloned().collect(), + }) + } else if let Ok(set) = other.extract::>() { + Ok(Self { + $keys: self + .$keys + .iter() + .cloned() + .collect::>() + .union(&set) + .cloned() + .collect(), + }) + } else { + Err(PyKeyError::new_err( + "Could not perform union, Wrong Key Types", + )) + } + } + + fn intersection(&self, other: &Bound) -> PyResult { + if let Ok(set) = other.extract::() { + Ok(Self { + $keys: self.$keys.intersection(&set.$keys).cloned().collect(), + }) + } else if let Ok(set) = other.extract::>() { + Ok(Self { + $keys: self + .$keys + .iter() + .cloned() + .collect::>() + .intersection(&set) + .cloned() + .collect(), + }) + } else { + Err(PyKeyError::new_err( + "Could not perform intersection, Wrong Key Types", + )) + } + } + + fn difference(&self, other: &Bound) -> PyResult { + if let Ok(set) = other.extract::() { + Ok(Self { + $keys: self.$keys.difference(&set.$keys).cloned().collect(), + }) + } else if let Ok(set) = other.extract::>() { + Ok(Self { + $keys: self + .$keys + .iter() + .cloned() + .collect::>() + .difference(&set) + .cloned() + .collect(), + }) + } else { + Err(PyKeyError::new_err( + "Could not perform difference, Wrong Key Types", + )) + } + } + + fn __ior__(&mut self, other: &Bound) -> PyResult<()> { + self.$keys = self.union(other)?.$keys; + Ok(()) + } + + fn __iand__(&mut self, other: &Bound) -> PyResult<()> { + self.$keys = self.intersection(other)?.$keys; + Ok(()) + } + + fn __isub__(&mut self, other: &Bound) -> PyResult<()> { + self.$keys = self.difference(other)?.$keys; + Ok(()) + } + fn __contains__(slf: PyRef, obj: $T) -> PyResult { + Ok(slf.$keys.contains(&obj)) + } + + fn __repr__(slf: PyRef) -> String { + let mut output = format!("{}([", $pyrep); + output.push_str(slf.$keys.iter().join(", ").as_str()); + output.push_str("])"); + output + } + + fn __getstate__(&self) -> (HashSet<$T>,) { + return (self.$keys.clone().into_iter().collect::>(),); + } + + fn __setstate__(&mut self, state: (HashSet<$T>,)) -> PyResult<()> { + self.$keys = state.0.into_iter().collect::>(); + Ok(()) + } + } + + impl Default for $name { + fn default() -> Self { + Self { + $keys: IndexSet::new(), + } + } + } + + #[pyclass] + pub struct $iter { + pub iter: $IterType, + } + + #[pymethods] + impl $iter { + fn __next__(mut slf: PyRefMut) -> Option<$T> { + slf.iter.next() + } + + fn __iter__(slf: PyRef) -> PyRef { + slf + } + + fn __length_hint__(slf: PyRef) -> usize { + slf.iter.len() + } + } + }; +} + +macro_rules! qargs_key_like_set_iterator { + ($name:ident, $iter:ident, $keys:ident, $IterType:ty, $doc:literal, $pyrep:literal) => { + #[doc = $doc] + #[pyclass(sequence, module = "qiskit._accelerate.target")] + #[derive(Debug, Clone)] + pub struct $name { + pub $keys: IndexSet>, + } + + #[pymethods] + impl $name { + #[new] + fn new() -> Self { + Self::default() + } + + fn __iter__(slf: PyRef) -> PyResult> { + let iter = $iter { + iter: slf.$keys.clone().into_iter(), + }; + Py::new(slf.py(), iter) + } + + fn __eq__(slf: PyRef, other: Bound) -> PyResult { + if let Ok(set) = other.downcast::() { + for item in set.iter() { + let key = item + .extract::>()? + .map(|qargs| qargs.parse_qargs()); + if !(slf.$keys.contains(&key)) { + return Ok(false); + } + } + } else if let Ok(self_like) = other.extract::() { + for item in self_like.$keys.iter() { + if !(slf.$keys.contains(item)) { + return Ok(false); + } + } + } + + Ok(true) + } + + fn __len__(slf: PyRef) -> usize { + slf.$keys.len() + } + + fn __sub__(&self, other: &Bound) -> PyResult { + self.difference(other) + } + + fn union(&self, other: &Bound) -> PyResult { + if let Ok(set) = other.extract::() { + Ok(Self { + $keys: self.$keys.union(&set.$keys).cloned().collect(), + }) + } else if let Ok(set) = other.extract::>>() { + let set: HashSet> = set + .into_iter() + .map(|x| x.map(|y| y.parse_qargs())) + .collect(); + Ok(Self { + $keys: self + .$keys + .iter() + .cloned() + .collect::>>() + .union(&set) + .cloned() + .collect(), + }) + } else { + Err(PyKeyError::new_err( + "Could not perform union, Wrong Key Types", + )) + } + } + + fn intersection(&self, other: &Bound) -> PyResult { + if let Ok(set) = other.extract::() { + Ok(Self { + $keys: self.$keys.intersection(&set.$keys).cloned().collect(), + }) + } else if let Ok(set) = other.extract::>>() { + let set: HashSet> = set + .into_iter() + .map(|x| x.map(|y| y.parse_qargs())) + .collect(); + Ok(Self { + $keys: self + .$keys + .iter() + .cloned() + .collect::>>() + .intersection(&set) + .cloned() + .collect(), + }) + } else { + Err(PyKeyError::new_err( + "Could not perform intersection, Wrong Key Types", + )) + } + } + + fn difference(&self, other: &Bound) -> PyResult { + if let Ok(set) = other.extract::() { + Ok(Self { + $keys: self.$keys.difference(&set.$keys).cloned().collect(), + }) + } else if let Ok(set) = other.extract::>>() { + let set: HashSet> = set + .into_iter() + .map(|x| x.map(|y| y.parse_qargs())) + .collect(); + Ok(Self { + $keys: self + .$keys + .iter() + .cloned() + .collect::>>() + .difference(&set) + .cloned() + .collect(), + }) + } else { + Err(PyKeyError::new_err( + "Could not perform difference, Wrong Key Types", + )) + } + } + + fn __ior__(&mut self, other: &Bound) -> PyResult<()> { + self.$keys = self.union(other)?.$keys; + Ok(()) + } + + fn __iand__(&mut self, other: &Bound) -> PyResult<()> { + self.$keys = self.intersection(other)?.$keys; + Ok(()) + } + + fn __isub__(&mut self, other: &Bound) -> PyResult<()> { + self.$keys = self.difference(other)?.$keys; + Ok(()) + } + + fn __contains__(slf: PyRef, obj: Option) -> PyResult { + let obj = obj.map(|obj| obj.parse_qargs()); + Ok(slf.$keys.contains(&obj)) + } + + fn __repr__(slf: PyRef) -> String { + let mut output = format!("{}[(", $pyrep); + output.push_str( + slf.$keys + .iter() + .map(|x| { + if let Some(x) = x { + x.to_string() + } else { + "None".to_owned() + } + }) + .join(", ") + .as_str(), + ); + output.push(']'); + output + } + + fn __getstate__(&self) -> (HashSet>,) { + return (self + .$keys + .clone() + .into_iter() + .collect::>>(),); + } + + fn __setstate__(&mut self, state: (HashSet>,)) -> PyResult<()> { + self.$keys = state.0.into_iter().collect::>>(); + Ok(()) + } + } + + impl Default for $name { + fn default() -> Self { + Self { + $keys: IndexSet::new(), + } + } + } + + #[pyclass] + pub struct $iter { + pub iter: $IterType, + } + + #[pymethods] + impl $iter { + fn __next__(mut slf: PyRefMut) -> Option> { + slf.iter.next() + } + + fn __iter__(slf: PyRef) -> PyRef { + slf + } + + fn __length_hint__(slf: PyRef) -> usize { + slf.iter.len() + } + } + }; +} + +pub(crate) use key_like_set_iterator; +pub(crate) use qargs_key_like_set_iterator; diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 1db907af0513..c4b634d4e9ff 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -14,11 +14,12 @@ mod gate_map; mod instruction_properties; +mod macro_rules; mod property_map; mod qargs; use hashbrown::HashSet; -use indexmap::{IndexMap, IndexSet}; +use indexmap::{set::IntoIter as IndexSetIntoIter, IndexMap, IndexSet}; use itertools::Itertools; use pyo3::{ exceptions::{PyAttributeError, PyIndexError, PyKeyError, PyValueError}, @@ -37,6 +38,7 @@ use qargs::{Qargs, QargsSet}; use self::{ exceptions::{QiskitError, TranspilerError}, gate_map::{GateMap, GateMapIter, GateMapKeys}, + macro_rules::key_like_set_iterator, property_map::PropsMapKeys, qargs::QargsOrTuple, }; @@ -94,7 +96,15 @@ fn tupleize<'py>(object: &Bound<'py, PyAny>) -> PyResult> { // Custom types type ErrorDictType<'a> = IndexMap>>; - +key_like_set_iterator!( + TargetOpNames, + TargetOpNamesIter, + operations, + String, + IndexSetIntoIter, + "An iterator for the group of operation names in the target", + "target_op_names" +); /** The intent of the ``Target`` object is to inform Qiskit's compiler about the constraints of a particular backend so the compiler can compile an @@ -1567,10 +1577,13 @@ impl Target { } /// Get the operation names in the target. #[getter] - fn operation_names(&self) -> HashSet { - return HashSet::from_iter(self.gate_map.map.keys().cloned()); + fn operation_names(&self) -> TargetOpNames { + return TargetOpNames { + operations: self.gate_map.map.keys().cloned().collect(), + }; } - /// Get the operation names in the target. + + /// Get the operation objects in the target. #[getter] fn operations(&self) -> Vec { return Vec::from_iter(self.gate_name_map.values().cloned()); @@ -2040,5 +2053,6 @@ pub fn target(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; Ok(()) } diff --git a/crates/accelerate/src/target_transpiler/property_map.rs b/crates/accelerate/src/target_transpiler/property_map.rs index 9a6266053e99..158b89af4e9e 100644 --- a/crates/accelerate/src/target_transpiler/property_map.rs +++ b/crates/accelerate/src/target_transpiler/property_map.rs @@ -10,87 +10,27 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +use hashbrown::HashSet; use indexmap::{set::IntoIter as IndexSetIntoIter, IndexMap, IndexSet}; use itertools::Itertools; use pyo3::types::{PyMapping, PySet}; use pyo3::{exceptions::PyKeyError, prelude::*, pyclass}; use super::instruction_properties::InstructionProperties; +use super::macro_rules::qargs_key_like_set_iterator; use super::qargs::{Qargs, QargsOrTuple}; type KeyIterType = IndexSetIntoIter>; pub type PropsMapItemsType = Vec<(Option, Option>)>; -#[pyclass] -struct PropsMapIter { - iter: KeyIterType, -} - -#[pymethods] -impl PropsMapIter { - fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { - slf - } - fn __next__(mut slf: PyRefMut<'_, Self>) -> Option> { - slf.iter.next() - } -} - -#[pyclass(sequence)] -#[derive(Debug, Clone)] -pub struct PropsMapKeys { - pub keys: IndexSet>, -} - -#[pymethods] -impl PropsMapKeys { - fn __iter__(slf: PyRef) -> PyResult> { - let iter = PropsMapIter { - iter: slf.keys.clone().into_iter(), - }; - Py::new(slf.py(), iter) - } - - fn __eq__(slf: PyRef, other: Bound) -> PyResult { - for item in other.iter() { - let qargs = item - .extract::>()? - .map(|qargs| qargs.parse_qargs()); - if !(slf.keys.contains(&qargs)) { - return Ok(false); - } - } - Ok(true) - } - - fn __len__(slf: PyRef) -> usize { - slf.keys.len() - } - - fn __contains__(slf: PyRef, obj: Option) -> PyResult { - let obj = obj.map(|obj| obj.parse_qargs()); - Ok(slf.keys.contains(&obj)) - } - - fn __repr__(slf: PyRef) -> String { - let mut output = "prop_map_keys[".to_owned(); - output.push_str( - slf.keys - .iter() - .map(|x| { - if let Some(x) = x { - x.to_string() - } else { - "None".to_owned() - } - }) - .join(", ") - .as_str(), - ); - output.push(']'); - output - } -} +qargs_key_like_set_iterator!( + PropsMapKeys, + PropsMapIter, + keys, + KeyIterType, + "", + "props_map_keys" +); type PropsMapKV = IndexMap, Option>>; /** diff --git a/crates/accelerate/src/target_transpiler/qargs.rs b/crates/accelerate/src/target_transpiler/qargs.rs index 24c078fadcd8..fadeccab7e66 100644 --- a/crates/accelerate/src/target_transpiler/qargs.rs +++ b/crates/accelerate/src/target_transpiler/qargs.rs @@ -28,7 +28,9 @@ use pyo3::{ }; use smallvec::{smallvec, IntoIter as SmallVecIntoIter, SmallVec}; +use super::macro_rules::qargs_key_like_set_iterator; use crate::nlayout::PhysicalQubit; +use hashbrown::HashSet; pub type QargsTuple = SmallVec<[PhysicalQubit; 4]>; @@ -56,16 +58,12 @@ impl QargsOrTuple { } } -enum QargIterType { - Qarg(SmallVecIntoIter<[PhysicalQubit; 4]>), - QargSet(IntoIter>), -} /** An iterator for the ``Qarg`` class. */ #[pyclass] struct QargsIter { - iter: QargIterType, + iter: SmallVecIntoIter<[PhysicalQubit; 4]>, } #[pymethods] @@ -73,79 +71,19 @@ impl QargsIter { fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { slf } - fn __next__(mut slf: PyRefMut<'_, Self>) -> Option { - match &mut slf.iter { - QargIterType::Qarg(iter) => iter.next().map(|next| next.to_object(slf.py())), - QargIterType::QargSet(iter) => iter.next().map(|next| next.into_py(slf.py())), - } + fn __next__(mut slf: PyRefMut<'_, Self>) -> Option { + slf.iter.next() } } -#[pyclass(sequence)] -#[derive(Debug, Clone)] -pub struct QargsSet { - pub set: IndexSet>, -} - -#[pymethods] -impl QargsSet { - fn __eq__(slf: PyRef, other: Bound) -> PyResult { - for item in other.iter() { - let qargs = if item.is_none() { - None - } else { - Some(item.extract::()?.parse_qargs()) - }; - if !slf.set.contains(&qargs) { - return Ok(false); - } - } - Ok(true) - } - - fn __iter__(slf: PyRef) -> PyResult> { - let iter = QargsIter { - iter: QargIterType::QargSet(slf.set.clone().into_iter()), - }; - Py::new(slf.py(), iter) - } - - fn __getitem__(&self, obj: Bound) -> PyResult> { - let qargs = if obj.is_none() { - None - } else { - Some(obj.extract::()?.parse_qargs()) - }; - if let Some(qargs) = self.set.get(&qargs) { - Ok(qargs.to_owned()) - } else { - Err(PyKeyError::new_err("{:} was not in QargSet.")) - } - } - - fn __len__(slf: PyRef) -> usize { - slf.set.len() - } - - fn __repr__(slf: PyRef) -> String { - let mut output = "qargs_set{".to_owned(); - output.push_str( - slf.set - .iter() - .map(|x| { - if let Some(x) = x { - x.to_string() - } else { - "None".to_owned() - } - }) - .join(", ") - .as_str(), - ); - output.push('}'); - output - } -} +qargs_key_like_set_iterator!( + QargsSet, + QargsSetIter, + set, + IntoIter>, + "", + "qargs_set" +); /** Hashable representation of a Qarg tuple in rust. @@ -174,7 +112,7 @@ impl Qargs { fn __iter__(slf: PyRef<'_, Self>) -> PyResult> { let iter = QargsIter { - iter: QargIterType::Qarg(slf.vec.clone().into_iter()), + iter: slf.vec.clone().into_iter(), }; Py::new(slf.py(), iter) } diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 20354f4c56ed..7111d9964398 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -93,7 +93,7 @@ def __repr__(self): class Target(Target2): """ - The intent of the ``Target`` object is to inform Qiskit's compiler about + The intent of the ``Target`` object is to inform Qiskit's compiler about the constraints of a particular backend so the compiler can compile an input circuit to something that works and is optimized for a device. It currently contains a description of instructions on a backend and their @@ -172,7 +172,8 @@ class Target(Target2): If you need to remove one of these the best option is to iterate over an existing object and create a new subset (or use one of the methods to do this). The object internally caches different views and these - would potentially be invalidated by removals.""" + would potentially be invalidated by removals. + """ def __new__( cls, @@ -250,11 +251,6 @@ def __new__( concurrent_measurements=concurrent_measurements, ) - @property - def operation_names(self): - """Get the operation names in the target.""" - return {x: None for x in super().operation_names}.keys() - def _build_coupling_graph(self): self.coupling_graph = rx.PyDiGraph( # pylint: disable=attribute-defined-outside-init multigraph=False