From 1e0f9c08f7420e93606b9fee160e4e55c14cda3c Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 27 May 2024 22:52:13 -0400 Subject: [PATCH] Add: Make `Target` Representable in Rust - Rename `InstructionProperties` as `BaseInstructionProperties`. - Remove `Calibration` from the rust space. - Restore `gate_map`, `coupling_map`, `instruction_schedule_map`, and `instruction_durations` to rust. - Remove all unnecessary data structures from rust space. - Other tweaks and fixes. --- .../src/target_transpiler/gate_map.rs | 178 --- .../instruction_properties.rs | 118 +- .../src/target_transpiler/macro_rules.rs | 194 --- .../accelerate/src/target_transpiler/mod.rs | 1203 ++--------------- .../src/target_transpiler/property_map.rs | 192 --- qiskit/transpiler/target.py | 823 ++++++++++- 6 files changed, 940 insertions(+), 1768 deletions(-) delete mode 100644 crates/accelerate/src/target_transpiler/gate_map.rs delete mode 100644 crates/accelerate/src/target_transpiler/macro_rules.rs delete mode 100644 crates/accelerate/src/target_transpiler/property_map.rs diff --git a/crates/accelerate/src/target_transpiler/gate_map.rs b/crates/accelerate/src/target_transpiler/gate_map.rs deleted file mode 100644 index 0db200a99bef..000000000000 --- a/crates/accelerate/src/target_transpiler/gate_map.rs +++ /dev/null @@ -1,178 +0,0 @@ -// 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 super::{macro_rules::key_like_set_iterator, property_map::PropsMap}; -use hashbrown::HashSet; -use indexmap::{set::IntoIter, IndexMap, IndexSet}; -use itertools::Itertools; -use pyo3::{ - exceptions::PyKeyError, - prelude::*, - pyclass, - types::{PyDict, PySet}, -}; - -type GateMapType = IndexMap>; - -// Creates a Key-Like object for the Gate Map keys. -// This is done in an effort to keep the insertion order of the keys. -key_like_set_iterator!( - GateMapKeys, - GateMapIter, - keys, - String, - IntoIter, - "", - "gate_map_keys" -); - -/** -Mapping of Instruction Names and ``PropsMaps`` (``Qargs``: ``InstructionProperties``) present -on the ``Target``. - -This structure keeps track of which qubits an instruction is affecting and the properties of -said instruction on those qubits. - */ -#[pyclass(mapping, module = "qiskit._accelerate.target")] -#[derive(Debug, Clone)] -pub struct GateMap { - pub map: GateMapType, -} - -#[pymethods] -impl GateMap { - /// Create empty instance of a GateMap. - #[new] - pub fn new() -> Self { - Self::default() - } - - /// Checks whether an instruction is present on the ``Target``'s gate map. - pub fn __contains__(&self, key: &Bound) -> bool { - if let Ok(key) = key.extract::() { - self.map.contains_key(&key) - } else { - false - } - } - - /// Check the equality of two gate_maps in the Python space. - fn __eq__(slf: PyRef, other: &Bound) -> PyResult { - if let Ok(dict) = other.downcast::() { - for key in dict.keys() { - if let Ok(key) = key.extract::() { - if !slf.map.contains_key(&key) { - return Ok(false); - } else if let (Some(value), Ok(Some(other_value))) = - (slf.map.get(&key), dict.get_item(key)) - { - let comparison = other_value.eq(value)?; - if !comparison { - return Ok(false); - } - } - } else { - return Ok(false); - } - } - Ok(slf.map.len() == dict.len()) - } else { - Ok(false) - } - } - - /// Access a value in the GateMap using a Key. - /// - /// Args: - /// key (str): The instruction name key. - /// - /// Return: - /// ``PropsMap`` object at that slot. - /// Raises: - /// KeyError if the ``key`` is not in the ``GateMap``. - pub fn __getitem__(&self, py: Python<'_>, key: String) -> PyResult> { - if let Some(item) = self.map.get(&key) { - Ok(item.clone_ref(py)) - } else { - Err(PyKeyError::new_err(format!( - "Key {:#?} not in target.", - key - ))) - } - } - - /// Access a value in the GateMap using a Key. - /// - /// Args: - /// key (str): The instruction name key. - /// default (Option[PyAny]): The default value to be returned. - /// - /// Returns: - /// ``PropsMap`` value if found, otherwise returns ``default``. - #[pyo3(signature = (key, default=None))] - fn get(slf: PyRef, key: String, default: Option>) -> PyObject { - match slf.__getitem__(slf.py(), key) { - Ok(value) => value.into_py(slf.py()), - Err(_) => match default { - Some(value) => value.into(), - None => slf.py().None(), - }, - } - } - - /// Returns number of present keys in the GateMap - fn __len__(slf: PyRef) -> usize { - slf.map.len() - } - - /// Returns the iterator of the Keys in the GateMap. - pub fn __iter__(&self, py: Python<'_>) -> PyResult> { - let iter = GateMapIter { - iter: self.keys().keys.into_iter(), - }; - Py::new(py, iter) - } - - /// Returns the Keys in the GateMap as an ordered set of Strings. - pub fn keys(&self) -> GateMapKeys { - GateMapKeys { - keys: self.map.keys().cloned().collect::>(), - } - } - - /// Returns the values of the GateMap as a list of ``PropsMap`` objects. - pub fn values(&self) -> Vec> { - self.map.values().cloned().collect_vec() - } - - /// Returns they (keys, values) pairs as a list of (``str``, ``PropsMap``) - pub fn items(&self) -> Vec<(String, Py)> { - self.map.clone().into_iter().collect_vec() - } - - pub fn __setstate__(&mut self, state: Vec<(String, Py)>) -> PyResult<()> { - self.map = IndexMap::from_iter(state); - Ok(()) - } - - pub fn __getstate__(&self) -> Vec<(String, Py)> { - self.items() - } -} - -impl Default for GateMap { - fn default() -> Self { - Self { - map: IndexMap::new(), - } - } -} diff --git a/crates/accelerate/src/target_transpiler/instruction_properties.rs b/crates/accelerate/src/target_transpiler/instruction_properties.rs index a14c4b15ab1c..b9da5a6bf969 100644 --- a/crates/accelerate/src/target_transpiler/instruction_properties.rs +++ b/crates/accelerate/src/target_transpiler/instruction_properties.rs @@ -10,11 +10,7 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use pyo3::{ - prelude::*, - pyclass, - types::{IntoPyDict, PyType}, -}; +use pyo3::{prelude::*, pyclass}; /** A representation of the properties of a gate implementation. @@ -27,18 +23,16 @@ custom attributes for those custom/additional properties by the backend. */ #[pyclass(subclass, module = "qiskit._accelerate.target")] #[derive(Clone, Debug)] -pub struct InstructionProperties { +pub struct BaseInstructionProperties { #[pyo3(get, set)] pub duration: Option, #[pyo3(get, set)] pub error: Option, - #[pyo3(get)] - pub _calibration: PyObject, } #[pymethods] -impl InstructionProperties { - /// Create a new ``InstructionProperties`` object +impl BaseInstructionProperties { + /// Create a new ``BaseInstructionProperties`` object /// /// Args: /// duration (Option): The duration, in seconds, of the instruction on the @@ -47,102 +41,26 @@ impl InstructionProperties { /// set of qubits. /// calibration (Option): The pulse representation of the instruction. #[new] - #[pyo3(text_signature = "(/, duration: float | None = None, - error: float | None = None, - calibration: Schedule | ScheduleBlock | CalibrationEntry | None = None,)")] - pub fn new( - py: Python<'_>, - duration: Option, - error: Option, - calibration: Option>, - ) -> Self { - let mut instruction_prop = InstructionProperties { - error, - duration, - _calibration: py.None(), - }; - if let Some(calibration) = calibration { - let _ = instruction_prop.set_calibration(py, calibration); - } - instruction_prop - } - - /// The pulse representation of the instruction. - /// - /// .. note:: - /// - /// This attribute always returns a Qiskit pulse program, but it is internally - /// wrapped by the :class:`.CalibrationEntry` to manage unbound parameters - /// and to uniformly handle different data representation, - /// for example, un-parsed Pulse Qobj JSON that a backend provider may provide. - /// - /// This value can be overridden through the property setter in following manner. - /// When you set either :class:`.Schedule` or :class:`.ScheduleBlock` this is - /// always treated as a user-defined (custom) calibration and - /// the transpiler may automatically attach the calibration data to the output circuit. - /// This calibration data may appear in the wire format as an inline calibration, - /// which may further update the backend standard instruction set architecture. - /// - /// If you are a backend provider who provides a default calibration data - /// that is not needed to be attached to the transpiled quantum circuit, - /// you can directly set :class:`.CalibrationEntry` instance to this attribute, - /// in which you should set :code:`user_provided=False` when you define - /// calibration data for the entry. End users can still intentionally utilize - /// the calibration data, for example, to run pulse-level simulation of the circuit. - /// However, such entry doesn't appear in the wire format, and backend must - /// use own definition to compile the circuit down to the execution format. - #[getter] - pub fn get_calibration(&self, py: Python<'_>) -> PyResult { - if !&self._calibration.is_none(py) { - return self._calibration.call_method0(py, "get_schedule"); - } - Ok(py.None()) + #[pyo3(signature = (duration=None, error=None))] + pub fn new(_py: Python<'_>, duration: Option, error: Option) -> Self { + Self { error, duration } } - #[setter] - pub fn set_calibration(&mut self, py: Python<'_>, calibration: Bound) -> PyResult<()> { - let module = py.import_bound("qiskit.pulse.schedule")?; - // Import Schedule and ScheduleBlock types. - let schedule_type = module.getattr("Schedule")?; - let schedule_type = schedule_type.downcast::()?; - let schedule_block_type = module.getattr("ScheduleBlock")?; - let schedule_block_type = schedule_block_type.downcast::()?; - if calibration.is_instance(schedule_block_type)? - || calibration.is_instance(schedule_type)? - { - // Import the calibration_entries module - let calibration_entries = py.import_bound("qiskit.pulse.calibration_entries")?; - // Import the schedule def class. - let schedule_def = calibration_entries.getattr("ScheduleDef")?; - // Create a ScheduleDef instance. - let new_entry: Bound = schedule_def.call0()?; - // Definethe schedule, make sure it is user provided. - let args = (calibration,); - let kwargs = [("user_provided", true)].into_py_dict_bound(py); - new_entry.call_method("define", args, Some(&kwargs))?; - self._calibration = new_entry.unbind(); - } else { - self._calibration = calibration.unbind(); - } - Ok(()) - } - - fn __getstate__(&self) -> PyResult<(Option, Option, Option<&PyObject>)> { - Ok((self.duration, self.error, Some(&self._calibration))) + fn __getstate__(&self) -> PyResult<(Option, Option)> { + Ok((self.duration, self.error)) } fn __setstate__( &mut self, - py: Python<'_>, + _py: Python<'_>, state: (Option, Option, Bound), ) -> PyResult<()> { self.duration = state.0; self.error = state.1; - self.set_calibration(py, state.2)?; Ok(()) } - fn __repr__(&self, py: Python<'_>) -> PyResult { + fn __repr__(&self, _py: Python<'_>) -> PyResult { let mut output = "InstructionProperties(".to_owned(); if let Some(duration) = self.duration { output.push_str("duration="); @@ -159,20 +77,6 @@ impl InstructionProperties { } else { output.push_str("error=None, "); } - - if !self.get_calibration(py)?.is_none(py) { - output.push_str( - format!( - "calibration={:?})", - self.get_calibration(py)? - .call_method0(py, "__str__")? - .extract::(py)? - ) - .as_str(), - ); - } else { - output.push_str("calibration=None)"); - } Ok(output) } } diff --git a/crates/accelerate/src/target_transpiler/macro_rules.rs b/crates/accelerate/src/target_transpiler/macro_rules.rs deleted file mode 100644 index 1ea68c6ab591..000000000000 --- a/crates/accelerate/src/target_transpiler/macro_rules.rs +++ /dev/null @@ -1,194 +0,0 @@ -// 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. - -/** -Creates an ordered set key-like collection that will be preserve insertion order in Python -while keeping the convenience of the ``set`` data structure. - */ -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 __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() - } - } - }; -} - -pub(crate) use key_like_set_iterator; diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 67d6a1ef2608..c83c00dcf33f 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -12,34 +12,25 @@ #![allow(clippy::too_many_arguments)] -mod gate_map; mod instruction_properties; -mod macro_rules; -mod property_map; use hashbrown::HashSet; -use indexmap::{set::IntoIter as IndexSetIntoIter, IndexMap, IndexSet}; +use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; use pyo3::{ exceptions::{PyAttributeError, PyIndexError, PyKeyError, PyValueError}, prelude::*, pyclass, sync::GILOnceCell, - types::{IntoPyDict, PyDict, PyList, PySet, PyString, PyTuple, PyType}, + types::{PyList, PyType}, }; -use smallvec::smallvec; +use smallvec::{smallvec, SmallVec}; use crate::nlayout::PhysicalQubit; -use instruction_properties::InstructionProperties; -use property_map::PropsMap; +use instruction_properties::BaseInstructionProperties; -use self::{ - exceptions::{QiskitError, TranspilerError}, - gate_map::{GateMap, GateMapIter, GateMapKeys}, - macro_rules::key_like_set_iterator, - property_map::{PropsMapKeys, Qargs}, -}; +use self::exceptions::TranspilerError; mod exceptions { use pyo3::import_exception_bound; @@ -50,21 +41,9 @@ mod exceptions { /// Import isclass function from python. static ISCLASS: GILOnceCell = GILOnceCell::new(); -/// Import standard gate name mapping from python. -static STANDARD_GATE_NAME_MAPPING: GILOnceCell> = GILOnceCell::new(); - -/// Import qubits_props_from_qubits function from python. -static QUBIT_PROPS_FROM_QUBITS: GILOnceCell = GILOnceCell::new(); - /// Import parameter class from python. static PARAMETER_CLASS: GILOnceCell> = GILOnceCell::new(); -/// Import gate class from python. -static GATE_CLASS: GILOnceCell = GILOnceCell::new(); - -/// Import instruction_schedule_map from python. -static INSTRUCTION_SCHEDULE_MAP: GILOnceCell = GILOnceCell::new(); - /// Helper function to import inspect.isclass from python. fn isclass(object: &Bound) -> PyResult { ISCLASS @@ -79,57 +58,6 @@ fn isclass(object: &Bound) -> PyResult { .extract::(object.py()) } -/// Helper function to import standard gate name mapping from python. -fn get_standard_gate_name_mapping(py: Python<'_>) -> PyResult> { - Ok(STANDARD_GATE_NAME_MAPPING - .get_or_init(py, || -> IndexMap { - Python::with_gil(|py| -> PyResult> { - let inspect_module: Bound = - py.import_bound("qiskit.circuit.library.standard_gates")?; - let is_class_method: Bound = - inspect_module.getattr("get_standard_gate_name_mapping")?; - is_class_method - .call0()? - .extract::>() - }) - .unwrap() - }) - .clone()) -} - -/// Helper function to obtain the qubit props list from some Target Properties. -fn qubit_props_list_from_props(properties: &Bound) -> PyResult> { - let kwargs = [("properties", properties)].into_py_dict_bound(properties.py()); - let props_list = QUBIT_PROPS_FROM_QUBITS - .get_or_init(properties.py(), || -> PyObject { - Python::with_gil(|py| -> PyResult { - let qiskit_backend_comp_module = - py.import_bound("qiskit.providers.backend_compat")?; - let qubit_props_list_funct = - qiskit_backend_comp_module.getattr("qubit_props_list_from_props")?; - Ok(qubit_props_list_funct.into()) - }) - .unwrap() - }) - .call_bound(properties.py(), (), Some(&kwargs))?; - props_list.extract::>(properties.py()) -} - -fn get_instruction_schedule_map_class(py: Python<'_>) -> PyResult { - Ok(INSTRUCTION_SCHEDULE_MAP - .get_or_init(py, || { - Python::with_gil(|py| -> PyResult { - let inst_sched_map_module = - py.import_bound("qiskit.pulse.instruction_schedule_map")?; - let inst_sched_map_class = - inst_sched_map_module.getattr("InstructionScheduleMap")?; - Ok(inst_sched_map_class.into()) - }) - .unwrap() - }) - .clone_ref(py)) -} - fn get_parameter(py: Python<'_>) -> PyResult<&Py> { Ok(PARAMETER_CLASS.get_or_init(py, || -> Py { Python::with_gil(|py| -> PyResult> { @@ -142,19 +70,6 @@ fn get_parameter(py: Python<'_>) -> PyResult<&Py> { })) } -fn make_parameter(py: Python<'_>, args: impl IntoPy>) -> PyResult { - let parameter_class = PARAMETER_CLASS.get_or_init(py, || -> Py { - Python::with_gil(|py| -> PyResult> { - let parameter_class = py - .import_bound("qiskit.circuit.parameter")? - .getattr("Parameter")?; - Ok(parameter_class.downcast::()?.clone().unbind()) - }) - .unwrap() - }); - parameter_class.call1(py, args) -} - pub fn tupleize(py: Python<'_>, qargs: Qargs) -> PyObject { match qargs.len() { 1 => qargs @@ -177,32 +92,22 @@ pub fn tupleize(py: Python<'_>, qargs: Qargs) -> PyObject { } } -fn make_gate( - py: Python<'_>, - args: impl IntoPy>, - kwargs: Option<&Bound>, -) -> PyResult { - let gate_class = GATE_CLASS.get_or_init(py, || -> PyObject { - Python::with_gil(|py| -> PyResult { - let gate_class = py.import_bound("qiskit.circuit.gate")?.getattr("Gate")?; - Ok(gate_class.into()) - }) - .unwrap() - }); - gate_class.call_bound(py, args, kwargs) -} - // Custom types -type ErrorDictType<'a> = IndexMap>>; -key_like_set_iterator!( - TargetOpNames, - TargetOpNamesIter, - operations, +type Qargs = SmallVec<[PhysicalQubit; 4]>; +type GateMap = IndexMap; +type PropsMap = IndexMap, Option>; +type GateMapState = Vec<( String, - IndexSetIntoIter, - "An iterator for the group of operation names in the target", - "target_op_names" -); + Vec<(Option, Option)>, +)>; + +// Temporary interpretation of Param +#[derive(Debug, Clone, FromPyObject)] +enum Param { + Float(f64), + ParameterExpression(PyObject), +} + /** 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 @@ -307,13 +212,10 @@ pub struct Target { #[pyo3(get, set)] pub concurrent_measurements: Vec>, gate_map: GateMap, - gate_name_map: IndexMap, + #[pyo3(get)] + _gate_name_map: IndexMap, global_operations: IndexMap>, qarg_gate_map: IndexMap, Option>>, - instruction_durations: Option, - instruction_schedule_map: Option, - #[pyo3(get, set)] - coupling_graph: Option, non_global_strict_basis: Option>, non_global_basis: Option>, } @@ -407,12 +309,9 @@ impl Target { qubit_properties, concurrent_measurements: concurrent_measurements.unwrap_or(Vec::new()), gate_map: GateMap::new(), - gate_name_map: IndexMap::new(), + _gate_name_map: IndexMap::new(), global_operations: IndexMap::new(), qarg_gate_map: IndexMap::new(), - coupling_graph: None, - instruction_durations: None, - instruction_schedule_map: None, non_global_basis: None, non_global_strict_basis: None, }) @@ -483,54 +382,28 @@ impl Target { /// AttributeError: If gate is already in map /// TranspilerError: If an operation class is passed in for ``instruction`` and no name /// is specified or ``properties`` is set. - #[pyo3(signature = (instruction, properties=None, name=None))] + #[pyo3(signature = (instruction, name, is_class, properties=None))] fn add_instruction( &mut self, - py: Python<'_>, + _py: Python<'_>, instruction: &Bound, - properties: Option, Option>>>, - name: Option, + name: String, + is_class: bool, + properties: Option, ) -> PyResult<()> { // Unwrap instruction name - let instruction_name: String; - let mut properties = properties; - if !isclass(instruction)? { - if let Some(name) = name { - instruction_name = name; - } else { - instruction_name = instruction - .getattr("name")? - .downcast::()? - .to_string(); - } - } else { - if let Some(name) = name { - instruction_name = name; - } else { - return Err(TranspilerError::new_err( - "A name must be specified when defining a supported global operation by class", - )); - } - if properties.is_some() { - return Err(TranspilerError::new_err( - "An instruction added globally by class can't have properties set.", - )); - } - } - if properties.is_none() { - properties = Some(IndexMap::from_iter([(None, None)].into_iter())); - } - if self.gate_map.map.contains_key(&instruction_name) { + let properties = properties; + + if self.gate_map.contains_key(&name) { return Err(PyAttributeError::new_err(format!( "Instruction {:?} is already in the target", - instruction_name + name ))); } - self.gate_name_map - .insert(instruction_name.clone(), instruction.clone().unbind()); - let mut qargs_val: IndexMap, Option>> = - IndexMap::new(); - if isclass(instruction)? { + self._gate_name_map + .insert(name.clone(), instruction.clone().unbind()); + let mut qargs_val: PropsMap = PropsMap::new(); + if is_class { qargs_val = IndexMap::from_iter([(None, None)].into_iter()); } else if let Some(properties) = properties { let inst_num_qubits = instruction.getattr("num_qubits")?.extract::()?; @@ -538,9 +411,9 @@ impl Target { self.global_operations .entry(inst_num_qubits) .and_modify(|e| { - e.insert(instruction_name.clone()); + e.insert(name.clone()); }) - .or_insert(HashSet::from_iter([instruction_name.clone()])); + .or_insert(HashSet::from_iter([name.clone()])); } for qarg in properties.keys() { let mut qarg_obj = None; @@ -572,19 +445,13 @@ impl Target { .entry(qarg_obj) .and_modify(|e| { if let Some(e) = e { - e.insert(instruction_name.clone()); + e.insert(name.clone()); } }) - .or_insert(Some(HashSet::from([instruction_name.clone()]))); + .or_insert(Some(HashSet::from([name.clone()]))); } } - self.gate_map.map.insert( - instruction_name, - Py::new(py, PropsMap::new(Some(qargs_val)))?, - ); - self.coupling_graph = None; - self.instruction_durations = None; - self.instruction_schedule_map = None; + self.gate_map.insert(name, qargs_val); self.non_global_basis = None; self.non_global_strict_basis = None; Ok(()) @@ -601,301 +468,31 @@ impl Target { #[pyo3(text_signature = "(instruction, qargs, properties, /,)")] fn update_instruction_properties( &mut self, - _py: Python<'_>, instruction: String, qargs: Option, - properties: Option>, + properties: Option, ) -> PyResult<()> { - if !self.gate_map.map.contains_key(&instruction) { + if !self.gate_map.contains_key(&instruction) { return Err(PyKeyError::new_err(format!( "Provided instruction: '{:?}' not in this Target.", &instruction ))); }; - let mut prop_map = self.gate_map.map[&instruction].extract::(_py)?; - if !(prop_map.map.contains_key(&qargs)) { + let mut prop_map = self.gate_map[&instruction].clone(); + if !(prop_map.contains_key(&qargs)) { return Err(PyKeyError::new_err(format!( "Provided qarg {:?} not in this Target for {:?}.", &qargs.unwrap_or_default(), &instruction ))); } - prop_map.map.entry(qargs).and_modify(|e| *e = properties); - let prop_map_obj = Py::new(_py, prop_map)?; + prop_map.entry(qargs).and_modify(|e| *e = properties); self.gate_map - .map .entry(instruction) - .and_modify(|e| *e = prop_map_obj.clone_ref(_py)); - self.instruction_durations = None; - self.instruction_schedule_map = None; - Ok(()) - } - - /// Update the target from an instruction schedule map. - /// - /// If the input instruction schedule map contains new instructions not in - /// the target they will be added. However, if it contains additional qargs - /// for an existing instruction in the target it will error. - /// - /// Args: - /// inst_map (InstructionScheduleMap): The instruction - /// inst_name_map (dict): An optional dictionary that maps any - /// instruction name in ``inst_map`` to an instruction object. - /// If not provided, instruction is pulled from the standard Qiskit gates, - /// and finally custom gate instance is created with schedule name. - /// error_dict (dict): A dictionary of errors of the form:: - /// - /// {gate_name: {qarg: error}} - /// - /// for example:: - /// - /// {'rx': {(0, ): 1.4e-4, (1, ): 1.2e-4}} - /// - /// For each entry in the ``inst_map`` if ``error_dict`` is defined - /// a when updating the ``Target`` the error value will be pulled from - /// this dictionary. If one is not found in ``error_dict`` then - /// ``None`` will be used. - #[pyo3(signature = (inst_map, /, inst_name_map=None, error_dict=None))] - fn update_from_instruction_schedule_map( - &mut self, - py: Python<'_>, - inst_map: &Bound, - inst_name_map: Option>>, - error_dict: Option, - ) -> PyResult<()> { - let get_calibration = inst_map.getattr("_get_calibration_entry")?; - // Expand name mapping with custom gate name provided by user. - let mut qiskit_inst_name_map = get_standard_gate_name_mapping(py)?; - - if let Some(inst_name_map) = inst_name_map.as_ref() { - for (key, value) in inst_name_map.iter() { - qiskit_inst_name_map.insert(key.to_owned(), value.to_owned().into()); - } - } - - let inst_map_instructions = inst_map.getattr("instructions")?.extract::>()?; - for inst_name in inst_map_instructions { - // Prepare dictionary of instruction properties - let mut out_prop: IndexMap, Option>> = - IndexMap::new(); - let inst_map_qubit_instruction_for_name = - inst_map.call_method1("qubits_with_instruction", (&inst_name,))?; - let inst_map_qubit_instruction_for_name = - inst_map_qubit_instruction_for_name.downcast::()?; - for qargs in inst_map_qubit_instruction_for_name { - let qargs_: Qargs = if let Ok(qargs_to_tuple) = qargs.extract::() { - qargs_to_tuple - } else { - smallvec![qargs.extract::()?] - }; - let opt_qargs = Some(qargs_.clone()); - let mut props: Option> = - if let Some(prop_value) = self.gate_map.map.get(&inst_name) { - prop_value - .call_method1(py, "get", (&opt_qargs.clone().into_py(py), py.None()))? - .extract::>>(py)? - .map(|prop| prop.clone_ref(py)) - } else { - None - }; - - let entry = get_calibration.call1((&inst_name, opt_qargs))?; - let entry_comparison: bool = if let Some(props) = &props { - !entry.eq(&props.getattr(py, "_calibration")?)? - } else { - !entry.is_none() - }; - if entry.getattr("user_provided")?.extract::()? && entry_comparison { - let mut duration: Option = None; - if let Some(dt) = self.dt { - if let Ok(entry_duration) = - entry.call_method0("get_schedule")?.getattr("duration") - { - duration = Some(dt * entry_duration.extract::()?); - } - } - props = Some(Py::new( - py, - InstructionProperties::new(py, duration, None, Some(entry)), - )?); - } else if props.is_none() { - continue; - } - - if let Some(error_dict) = error_dict.as_ref() { - if let Some(error_dict_name) = error_dict.get(&inst_name) { - if let (Some(error_prop), Some(props_)) = - (error_dict_name.get(&qargs_), props.as_mut()) - { - props_.setattr(py, "error", error_prop.extract::>()?)?; - } - } - } - out_prop.insert(Some(qargs_), props); - } - if out_prop.is_empty() { - continue; - } - // Prepare Qiskit Gate object assigned to the entries - if !self.gate_map.map.contains_key(&inst_name) { - // Entry not found: Add new instruction - if qiskit_inst_name_map.contains_key(&inst_name) { - // Remove qargs with length that doesn't match with instruction qubit number - let inst_obj = &qiskit_inst_name_map[&inst_name]; - let mut normalized_props: IndexMap< - Option, - Option>, - > = IndexMap::new(); - for (qargs, prop) in out_prop.iter() { - if qargs.as_ref().map(|x| x.len()).unwrap_or_default() - != inst_obj.getattr(py, "num_qubits")?.extract::(py)? - { - continue; - } - normalized_props.insert(qargs.to_owned(), prop.to_owned()); - } - self.add_instruction( - py, - inst_obj.bind(py), - Some(normalized_props), - Some(inst_name), - )?; - } else { - // Check qubit length parameter name uniformity. - let mut qlen: IndexSet = IndexSet::new(); - let param_names: Bound = PySet::empty_bound(py)?; - let inst_map_qubit_instruction_for_name = - inst_map.call_method1("qubits_with_instruction", (&inst_name,))?; - let inst_map_qubit_instruction_for_name = - inst_map_qubit_instruction_for_name.downcast::()?; - for qargs in inst_map_qubit_instruction_for_name { - let qargs_ = if let Ok(qargs_ext) = qargs.extract::>() { - qargs_ext - } else { - Some(smallvec![qargs.extract::()?]) - }; - let cal = if let Some(Some(prop)) = out_prop.get(&qargs_) { - Some(prop.getattr(py, "_calibration")?) - } else { - None - }; - qlen.insert(qargs_.map(|x| x.len()).unwrap_or_default()); - if let Some(cal) = cal { - let params = cal - .call_method0(py, "get_signature")? - .getattr(py, "parameters")? - .call_method0(py, "keys")?; - let params = params - .bind(py) - .iter()? - .map(|x| match x { - Ok(x) => x.to_object(py), - Err(_) => py.None(), - }) - .collect::>(); - param_names.add(PyTuple::new_bound(py, params))?; - } - } - if qlen.len() > 1 || param_names.len() > 1 { - return Err(QiskitError::new_err(format!( - "Schedules for {:?} are defined non-uniformly for - multiple qubit lengths {:?}, - or different parameter names {:?}. - Provide these schedules with inst_name_map or define them with - different names for different gate parameters.", - &inst_name, - qlen.iter().collect::>(), - param_names, - ))); - } - if let Some(param) = param_names.iter().next() { - if param.is_truthy()? { - let params = param - .iter()? - .flat_map(|x| -> PyResult { make_parameter(py, (x?,)) }) - .collect_vec(); - let kwargs = [ - ("name", inst_name.as_str().into_py(py)), - ("num_qubits", qlen.iter().next().to_object(py)), - ("params", params.to_object(py)), - ] - .into_py_dict_bound(py); - let inst_obj = make_gate(py, (), Some(&kwargs))?; - self.add_instruction( - py, - inst_obj.bind(py), - Some(out_prop.to_owned()), - Some(inst_name.to_owned()), - )?; - } else { - let kwargs = [ - ("name", inst_name.as_str().into_py(py)), - ("num_qubits", qlen.iter().next().to_object(py)), - ("params", PyList::empty_bound(py).to_object(py)), - ] - .into_py_dict_bound(py); - let inst_obj = make_gate(py, (), Some(&kwargs))?; - self.add_instruction( - py, - inst_obj.bind(py), - Some(out_prop.to_owned()), - Some(inst_name.to_owned()), - )?; - } - } - } - } else { - // Entry found: Update "existing" instructions. - for (qargs, prop) in out_prop.into_iter() { - if let Some(gate_inst) = self.gate_map.map.get(&inst_name) { - if !gate_inst - .call_method1(py, "__contains__", (&qargs.clone().into_py(py),))? - .extract::(py)? - { - continue; - } - } - self.update_instruction_properties(py, inst_name.to_owned(), qargs, prop)?; - } - } - } + .and_modify(|e| *e = prop_map); Ok(()) } - /// Return an :class:`~qiskit.pulse.InstructionScheduleMap` for the - /// instructions in the target with a pulse schedule defined. - /// - /// Returns: - /// InstructionScheduleMap: The instruction schedule map for the - /// instructions in this target with a pulse schedule defined. - #[pyo3(text_signature = "(/)")] - fn instruction_schedule_map(&mut self, py: Python<'_>) -> PyResult { - if let Some(schedule_map) = self.instruction_schedule_map.as_ref() { - return Ok(schedule_map.to_owned()); - } - - let out_inst_schedule_map = get_instruction_schedule_map_class(py)?.call0(py)?; - for (instruction, props_map) in self.gate_map.map.iter() { - for (qarg, properties) in props_map.extract::(py)?.map.iter() { - // Directly getting calibration entry to invoke .get_schedule(). - // This keeps PulseQobjDef unparsed. - if let Some(properties) = properties { - let cal_entry = &properties.getattr(py, "_calibration")?; - if !cal_entry.is_none(py) { - out_inst_schedule_map.call_method1( - py, - "_add", - (instruction, qarg.to_owned(), cal_entry), - )?; - } - } - } - } - let out_inst_schedule_map = out_inst_schedule_map; - self.instruction_schedule_map = Some(out_inst_schedule_map.clone()); - Ok(out_inst_schedule_map) - } - /// Get the qargs for a given operation name /// /// Args: @@ -903,21 +500,12 @@ impl Target { /// Returns: /// set: The set of qargs the gate instance applies to. #[pyo3(text_signature = "(operation, /,)")] - fn qargs_for_operation_name( - &self, - py: Python<'_>, - operation: String, - ) -> PyResult> { - if let Some(gate_map_oper) = self.gate_map.map.get(&operation) { - if gate_map_oper - .call_method1(py, "__contains__", (py.None(),))? - .extract::(py)? - { + fn qargs_for_operation_name(&self, operation: String) -> PyResult>> { + if let Some(gate_map_oper) = self.gate_map.get(&operation) { + if gate_map_oper.contains_key(&None) { return Ok(None); } - let qargs = gate_map_oper - .call_method0(py, "keys")? - .extract::(py)?; + let qargs: Vec = gate_map_oper.keys().flatten().cloned().collect(); Ok(Some(qargs)) } else { Err(PyKeyError::new_err(format!( @@ -926,61 +514,6 @@ impl Target { } } - /// Get an InstructionDurations object from the target - /// - /// Returns: - /// InstructionDurations: The instruction duration represented in the target - #[pyo3(text_signature = "(/,)")] - fn durations(&mut self, py: Python<'_>) -> PyResult> { - if self.instruction_durations.is_some() { - return Ok(self.instruction_durations.to_owned()); - } - let mut out_durations: Vec<(&String, Option, f64, &str)> = vec![]; - for (instruction, props_map) in self.gate_map.map.iter() { - for (qarg, properties) in props_map.extract::(py)?.map.iter() { - if let Some(properties) = properties { - if let Some(duration) = properties.getattr(py, "duration")?.extract(py)? { - out_durations.push(( - instruction, - qarg.to_owned().map(|x| tupleize(py, x)), - duration, - "s", - )) - } - } - } - } - let instruction_duration_class = py - .import_bound("qiskit.transpiler.instruction_durations")? - .getattr("InstructionDurations")?; - let kwargs = [("dt", self.dt)].into_py_dict_bound(py); - self.instruction_durations = Some( - instruction_duration_class - .call((out_durations,), Some(&kwargs))? - .unbind(), - ); - Ok(self.instruction_durations.to_owned()) - } - - /// Get an :class:`~qiskit.transpiler.TimingConstraints` object from the target - /// - /// Returns: - /// TimingConstraints: The timing constraints represented in the ``Target`` - #[pyo3(text_signature = "(/,)")] - fn timing_constraints(&self, py: Python<'_>) -> PyResult { - let timing_constraints_class = py - .import_bound("qiskit.transpiler.timing_constraints")? - .getattr("TimingConstraints")?; - Ok(timing_constraints_class - .call1(( - self.granularity, - self.min_length, - self.pulse_alignment, - self.acquire_alignment, - ))? - .unbind()) - } - /// Get the operation class object for a given name /// /// Args: @@ -992,8 +525,8 @@ impl Target { /// operations. #[pyo3(text_signature = "(instruction, /)")] fn operation_from_name(&self, py: Python<'_>, instruction: String) -> PyResult { - if let Some(gate_obj) = self.gate_name_map.get(&instruction) { - Ok(gate_obj.to_object(py)) + if let Some(gate_obj) = self._gate_name_map.get(&instruction) { + Ok(gate_obj.clone_ref(py)) } else { Err(PyKeyError::new_err(format!( "Instruction {:?} not in target", @@ -1017,32 +550,35 @@ impl Target { /// Raises: /// KeyError: If qargs is not in target #[pyo3(text_signature = "(/, qargs=None)")] - fn operations_for_qargs(&self, py: Python<'_>, qargs: Option) -> PyResult> { - let res = PyList::empty_bound(py); + fn operations_for_qargs( + &self, + py: Python<'_>, + qargs: Option, + ) -> PyResult> { + let mut res: Vec = vec![]; if let Some(qargs) = qargs.as_ref() { if qargs .iter() .any(|x| !(0..self.num_qubits.unwrap_or_default()).contains(&x.index())) { - // TODO: Throw Python Exception return Err(PyKeyError::new_err(format!("{:?} not in target.", qargs))); } } if let Some(Some(gate_map_qarg)) = self.qarg_gate_map.get(&qargs) { for x in gate_map_qarg { - res.append(&self.gate_name_map[x])?; + res.push(self._gate_name_map[x].clone_ref(py)); } } if let Some(qargs) = qargs.as_ref() { - if let Some(qarg) = self.global_operations.get(&qargs.len()) { - for arg in qarg { - res.append(arg)?; + if let Some(inst_set) = self.global_operations.get(&qargs.len()) { + for inst in inst_set { + res.push(self._gate_name_map[inst].clone_ref(py)); } } } - for op in self.gate_name_map.values() { - if isclass(op.bind(py))? { - res.append(op)?; + for (name, op) in self._gate_name_map.iter() { + if self.gate_map[name].contains_key(&None) { + res.push(op.clone_ref(py)); } } if res.is_empty() { @@ -1053,7 +589,7 @@ impl Target { } }))); } - Ok(res.into()) + Ok(res) } /// Get the operation names for a specified qargs tuple @@ -1071,7 +607,7 @@ impl Target { #[pyo3(text_signature = "(/, qargs=None)")] fn operation_names_for_qargs( &self, - py: Python<'_>, + _py: Python<'_>, qargs: Option, ) -> PyResult> { // When num_qubits == 0 we return globally defined operators @@ -1091,8 +627,8 @@ impl Target { if let Some(Some(qarg_gate_map_arg)) = self.qarg_gate_map.get(&qargs).as_ref() { res.extend(qarg_gate_map_arg); } - for (name, op) in self.gate_name_map.iter() { - if isclass(op.bind(py))? { + for name in self._gate_name_map.keys() { + if self.gate_map[name].contains_key(&None) { res.insert(name); } } @@ -1201,7 +737,7 @@ impl Target { qargs = None; } if let Some(operation_class) = operation_class { - for (op_name, obj) in self.gate_name_map.iter() { + for (op_name, obj) in self._gate_name_map.iter() { if isclass(obj.bind(py))? { if !operation_class.eq(obj)? { continue; @@ -1242,19 +778,13 @@ impl Target { } } if let Some(_qargs) = &qargs { - if self.gate_map.map.contains_key(op_name) { - let gate_map_name = &self.gate_map.map[op_name]; - if gate_map_name - .call_method1(py, "__contains__", (qargs.clone().into_py(py),))? - .extract::(py)? - { + if self.gate_map.contains_key(op_name) { + let gate_map_name = &self.gate_map[op_name]; + if gate_map_name.contains_key(&qargs) { return Ok(true); } - if gate_map_name - .call_method1(py, "__contains__", (py.None(),))? - .extract::(py)? - { - let qubit_comparison = self.gate_name_map[op_name] + if gate_map_name.contains_key(&None) { + let qubit_comparison = self._gate_name_map[op_name] .getattr(py, "num_qubits")? .extract::(py)?; return Ok(qubit_comparison == _qargs.len() @@ -1279,9 +809,9 @@ impl Target { } if let Some(operation_names) = &operation_name { - if self.gate_map.map.contains_key(operation_names) { + if self.gate_map.contains_key(operation_names) { if let Some(parameters) = parameters { - let obj = self.gate_name_map[operation_names].to_owned(); + let obj = self._gate_name_map[operation_names].to_owned(); if isclass(obj.bind(py))? { if let Some(_qargs) = qargs { let qarg_set: HashSet = _qargs.iter().cloned().collect(); @@ -1319,18 +849,12 @@ impl Target { } if let Some(_qargs) = qargs.as_ref() { let qarg_set: HashSet = _qargs.iter().cloned().collect(); - if let Some(gate_prop_name) = self.gate_map.map.get(operation_names) { - if gate_prop_name - .call_method1(py, "__contains__", (qargs.clone().into_py(py),))? - .extract::(py)? - { + if let Some(gate_prop_name) = self.gate_map.get(operation_names) { + if gate_prop_name.contains_key(&qargs) { return Ok(true); } - if gate_prop_name - .call_method1(py, "__contains__", (py.None(),))? - .extract::(py)? - { - let obj = &self.gate_name_map[operation_names]; + if gate_prop_name.contains_key(&None) { + let obj = &self._gate_name_map[operation_names]; if isclass(obj.bind(py))? { if qargs.is_none() || _qargs.iter().all(|qarg| { @@ -1352,7 +876,7 @@ impl Target { } } else { // Duplicate case is if it contains none - let obj = &self.gate_name_map[operation_names]; + let obj = &self._gate_name_map[operation_names]; if isclass(obj.bind(py))? { if qargs.is_none() || _qargs @@ -1365,7 +889,7 @@ impl Target { return Ok(false); } } else { - let qubit_comparison = self.gate_name_map[operation_names] + let qubit_comparison = self._gate_name_map[operation_names] .getattr(py, "num_qubits")? .extract::(py)?; return Ok(qubit_comparison == _qargs.len() @@ -1382,77 +906,6 @@ impl Target { Ok(false) } - /// Return whether the instruction (operation + qubits) defines a calibration. - /// - /// Args: - /// operation_name: The name of the operation for the instruction. - /// qargs: The tuple of qubit indices for the instruction. - /// - /// Returns: - // Returns ``True`` if the calibration is supported and ``False`` if it isn't. - #[pyo3(text_signature = "( /, operation_name: str, qargs: tuple[int, ...],)")] - fn has_calibration( - &self, - py: Python<'_>, - operation_name: String, - qargs: Option, - ) -> PyResult { - if !self.gate_map.map.contains_key(&operation_name) { - return Ok(false); - } - if self.gate_map.map.contains_key(&operation_name) { - let gate_map_qarg = &self.gate_map.map[&operation_name]; - if let Some(oper_qarg) = &gate_map_qarg - .call_method1(py, "get", (qargs.into_py(py),))? - .extract::>(py)? - { - return Ok(!oper_qarg._calibration.is_none(py)); - } else { - return Ok(false); - } - } - Ok(false) - } - - /// Get calibrated pulse schedule for the instruction. - /// - /// If calibration is templated with parameters, one can also provide those values - /// to build a schedule with assigned parameters. - /// - /// Args: - /// operation_name: The name of the operation for the instruction. - /// qargs: The tuple of qubit indices for the instruction. - /// args: Parameter values to build schedule if any. - /// kwargs: Parameter values with name to build schedule if any. - /// - /// Returns: - /// Calibrated pulse schedule of corresponding instruction. - #[pyo3( - signature = (operation_name, qargs=None, *args, **kwargs), - text_signature = "( /, operation_name: str, qargs: tuple[int, ...], *args: ParameterValueType, **kwargs: ParameterValueType,)" - )] - fn get_calibration( - &self, - py: Python<'_>, - operation_name: String, - qargs: Option, - args: &Bound<'_, PyTuple>, - kwargs: Option<&Bound<'_, PyDict>>, - ) -> PyResult { - if !self.has_calibration(py, operation_name.clone(), qargs.clone())? { - return Err(PyKeyError::new_err(format!( - "Calibration of instruction {:?} for qubit {:?} is not defined.", - operation_name, qargs - ))); - } - self.gate_map.map[&operation_name] - .call_method1(py, "get", (qargs.into_py(py),))? - .extract::>(py)? - .unwrap() - ._calibration - .call_method_bound(py, "get_schedule", args, kwargs) - } - /// Get the instruction properties for a specific instruction tuple /// /// This method is to be used in conjunction with the @@ -1487,15 +940,17 @@ impl Target { /// Returns: /// InstructionProperties: The instruction properties for the specified instruction tuple #[pyo3(text_signature = "(/, index: int)")] - fn instruction_properties(&self, py: Python<'_>, index: usize) -> PyResult { + fn instruction_properties( + &self, + _py: Python<'_>, + index: usize, + ) -> PyResult> { let mut index_counter = 0; - for (_operation, props_map) in self.gate_map.map.iter() { - let gate_map_oper = props_map - .call_method0(py, "values")? - .extract::>>>(py)?; + for (_operation, props_map) in self.gate_map.iter() { + let gate_map_oper = props_map.values(); for inst_props in gate_map_oper { if index_counter == index { - return Ok(inst_props.to_object(py)); + return Ok(inst_props.to_owned()); } index_counter += 1; } @@ -1526,7 +981,7 @@ impl Target { #[pyo3(signature = (/, strict_direction=false,), text_signature = "(/, strict_direction=False)")] fn get_non_global_operation_names( &mut self, - py: Python<'_>, + _py: Python<'_>, strict_direction: bool, ) -> PyResult> { let mut search_set: HashSet> = HashSet::new(); @@ -1564,18 +1019,14 @@ impl Target { .entry(qarg.to_owned().unwrap_or_default().len()) .or_insert(0) += 1; } - for (inst, qargs_props) in self.gate_map.map.iter() { - let mut qarg_len = qargs_props - .call_method0(py, "__len__")? - .extract::(py)?; - let qargs_keys = qargs_props - .call_method0(py, "keys")? - .extract::(py)?; - let qarg_sample = qargs_keys.keys.iter().next().cloned(); + for (inst, qargs_props) in self.gate_map.iter() { + let mut qarg_len = qargs_props.len(); + let qargs_keys: IndexSet<&Option> = qargs_props.keys().collect(); + let qarg_sample = qargs_keys.iter().next().cloned(); if let Some(qarg_sample) = qarg_sample { if !strict_direction { let mut qarg_set = HashSet::new(); - for qarg in qargs_keys.keys { + for qarg in qargs_keys { let mut qarg_set_vec: Qargs = smallvec![]; if let Some(qarg) = qarg { let mut to_vec = qarg.to_owned(); @@ -1606,14 +1057,14 @@ impl Target { /// The set of qargs in the target. #[getter] - fn qargs(&self) -> PyResult> { - let qargs: IndexSet> = self.qarg_gate_map.keys().cloned().collect(); + fn qargs(&self) -> PyResult>> { + let qargs: Vec> = self.qarg_gate_map.keys().cloned().collect(); // Modify logic to account for the case of {None} let next_entry = qargs.iter().flatten().next(); - if qargs.len() == 1 && (qargs.iter().next().is_none() || next_entry.is_none()) { + if qargs.len() == 1 && (qargs.first().unwrap().is_none() || next_entry.is_none()) { return Ok(None); } - Ok(Some(PropsMapKeys { keys: qargs })) + Ok(Some(qargs.into_iter().flatten().collect_vec())) } /// Get the list of tuples ``(:class:`~qiskit.circuit.Instruction`, (qargs))`` @@ -1623,20 +1074,13 @@ impl Target { /// ``(class, None)`` where class is the actual operation class that /// is globally defined. #[getter] - fn instructions(&self, py: Python<'_>) -> PyResult)>> { + fn instructions(&self, py: Python<'_>) -> PyResult)>> { // Get list of instructions. - let mut instruction_list: Vec<(PyObject, Option)> = vec![]; + let mut instruction_list: Vec<(PyObject, Option)> = vec![]; // Add all operations and dehash qargs. - for (op, props_map) in self.gate_map.map.iter() { - for qarg in props_map - .call_method0(py, "keys")? - .extract::(py)? - .keys - { - let instruction_pair = ( - self.gate_name_map[op].clone_ref(py), - qarg.clone().map(|x| tupleize(py, x)), - ); + for (op, props_map) in self.gate_map.iter() { + for qarg in props_map.keys() { + let instruction_pair = (self._gate_name_map[op].clone_ref(py), qarg.clone()); instruction_list.push(instruction_pair); } } @@ -1645,16 +1089,14 @@ impl Target { } /// Get the operation names in the target. #[getter] - fn operation_names(&self) -> TargetOpNames { - return TargetOpNames { - operations: self.gate_map.map.keys().cloned().collect(), - }; + fn operation_names(&self) -> Vec { + self.gate_map.keys().cloned().collect() } /// Get the operation objects in the target. #[getter] fn operations(&self) -> Vec { - return Vec::from_iter(self.gate_name_map.values().cloned()); + return Vec::from_iter(self._gate_name_map.values().cloned()); } /// Returns a sorted list of physical qubits. @@ -1663,380 +1105,10 @@ impl Target { Vec::from_iter(0..self.num_qubits.unwrap_or_default()) } - /// Create a target object from the individual global configuration - /// - /// Prior to the creation of the :class:`~.Target` class, the constraints - /// of a backend were represented by a collection of different objects - /// which combined represent a subset of the information contained in - /// the :class:`~.Target`. This function provides a simple interface - /// to convert those separate objects to a :class:`~.Target`. - /// - /// This constructor will use the input from ``basis_gates``, ``num_qubits``, - /// and ``coupling_map`` to build a base model of the backend and the - /// ``instruction_durations``, ``backend_properties``, and ``inst_map`` inputs - /// are then queried (in that order) based on that model to look up the properties - /// of each instruction and qubit. If there is an inconsistency between the inputs - /// any extra or conflicting information present in ``instruction_durations``, - /// ``backend_properties``, or ``inst_map`` will be ignored. - /// - /// Args: - /// basis_gates: The list of basis gate names for the backend. For the - /// target to be created these names must either be in the output - /// from :func:`~.get_standard_gate_name_mapping` or present in the - /// specified ``custom_name_mapping`` argument. - /// num_qubits: The number of qubits supported on the backend. - /// coupling_map: The coupling map representing connectivity constraints - /// on the backend. If specified all gates from ``basis_gates`` will - /// be supported on all qubits (or pairs of qubits). - /// inst_map: The instruction schedule map representing the pulse - /// :class:`~.Schedule` definitions for each instruction. If this - /// is specified ``coupling_map`` must be specified. The - /// ``coupling_map`` is used as the source of truth for connectivity - /// and if ``inst_map`` is used the schedule is looked up based - /// on the instructions from the pair of ``basis_gates`` and - /// ``coupling_map``. If you want to define a custom gate for - /// a particular qubit or qubit pair, you can manually build :class:`.Target`. - /// backend_properties: The :class:`~.BackendProperties` object which is - /// used for instruction properties and qubit properties. - /// If specified and instruction properties are intended to be used - /// then the ``coupling_map`` argument must be specified. This is - /// only used to lookup error rates and durations (unless - /// ``instruction_durations`` is specified which would take - /// precedence) for instructions specified via ``coupling_map`` and - /// ``basis_gates``. - /// instruction_durations: Optional instruction durations for instructions. If specified - /// it will take priority for setting the ``duration`` field in the - /// :class:`~InstructionProperties` objects for the instructions in the target. - /// concurrent_measurements(list): A list of sets of qubits that must be - /// measured together. This must be provided - /// as a nested list like ``[[0, 1], [2, 3, 4]]``. - /// dt: The system time resolution of input signals in seconds - /// timing_constraints: Optional timing constraints to include in the - /// :class:`~.Target` - /// custom_name_mapping: An optional dictionary that maps custom gate/operation names in - /// ``basis_gates`` to an :class:`~.Operation` object representing that - /// gate/operation. By default, most standard gates names are mapped to the - /// standard gate object from :mod:`qiskit.circuit.library` this only needs - /// to be specified if the input ``basis_gates`` defines gates in names outside - /// that set. - /// - /// Returns: - /// Target: the target built from the input configuration - /// - /// Raises: - /// TranspilerError: If the input basis gates contain > 2 qubits and ``coupling_map`` is - /// specified. - /// KeyError: If no mapping is available for a specified ``basis_gate``. - #[classmethod] - fn from_configuration( - _cls: &Bound<'_, PyType>, - py: Python<'_>, - basis_gates: Vec, - num_qubits: Option, - coupling_map: Option, - inst_map: Option>, - backend_properties: Option<&Bound>, - instruction_durations: Option, - concurrent_measurements: Option>>, - dt: Option, - timing_constraints: Option, - custom_name_mapping: Option>>, - ) -> PyResult { - let mut num_qubits = num_qubits; - let mut granularity: i32 = 1; - let mut min_length: usize = 1; - let mut pulse_alignment: i32 = 1; - let mut acquire_alignment: i32 = 1; - if let Some(timing_constraints) = timing_constraints { - granularity = timing_constraints - .getattr(py, "granularity")? - .extract::(py)?; - min_length = timing_constraints - .getattr(py, "min_length")? - .extract::(py)?; - pulse_alignment = timing_constraints - .getattr(py, "pulse_alignment")? - .extract::(py)?; - acquire_alignment = timing_constraints - .getattr(py, "acquire_alignment")? - .extract::(py)?; - } - let mut qubit_properties = None; - if let Some(backend_properties) = backend_properties { - qubit_properties = Some(qubit_props_list_from_props(backend_properties)?); - } - let mut target = Self::new( - None, - num_qubits, - dt, - Some(granularity), - Some(min_length), - Some(pulse_alignment), - Some(acquire_alignment), - qubit_properties, - concurrent_measurements, - )?; - let mut name_mapping = get_standard_gate_name_mapping(py)?; - if let Some(custom_name_mapping) = custom_name_mapping { - for (key, value) in custom_name_mapping.into_iter() { - name_mapping.insert(key, value.into()); - } - } - - /* - While BackendProperties can also contain coupling information we - rely solely on CouplingMap to determine connectivity. This is because - in legacy transpiler usage (and implicitly in the BackendV1 data model) - the coupling map is used to define connectivity constraints and - the properties is only used for error rate and duration population. - If coupling map is not specified we ignore the backend_properties - */ - if let Some(coupling_map) = coupling_map { - let mut one_qubit_gates: Vec = vec![]; - let mut two_qubit_gates: Vec = vec![]; - let mut global_ideal_variable_width_gates: Vec = vec![]; - if num_qubits.is_none() { - num_qubits = Some( - coupling_map - .getattr(py, "graph")? - .call_method0(py, "edge_list")? - .downcast_bound::(py)? - .len(), - ) - } - for gate in basis_gates { - if let Some(gate_obj) = name_mapping.get(&gate) { - let gate_obj_num_qubits = - gate_obj.getattr(py, "num_qubits")?.extract::(py)?; - if gate_obj_num_qubits == 1 { - one_qubit_gates.push(gate); - } else if gate_obj_num_qubits == 2 { - two_qubit_gates.push(gate); - } else if isclass(gate_obj.bind(py))? { - global_ideal_variable_width_gates.push(gate) - } else { - return Err(TranspilerError::new_err( - format!( - "The specified basis gate: {gate} has {gate_obj_num_qubits} \ - qubits. This constructor method only supports fixed width operations \ - with <= 2 qubits (because connectivity is defined on a CouplingMap)." - ) - )); - } - } else { - return Err(PyKeyError::new_err(format!( - "The specified basis gate: {gate} is not present in the standard gate names or a \ - provided custom_name_mapping" - ))); - } - } - for gate in one_qubit_gates { - let mut gate_properties: IndexMap< - Option, - Option>, - > = IndexMap::new(); - for qubit in 0..num_qubits.unwrap_or_default() { - let mut error: Option = None; - let mut duration: Option = None; - let mut calibration: Option> = None; - if let Some(backend_properties) = backend_properties { - if duration.is_none() { - duration = match backend_properties - .call_method1("gate_length", (&gate, qubit)) - { - Ok(duration) => Some(duration.extract::()?), - Err(_) => None, - } - } - error = match backend_properties.call_method1("gate_error", (&gate, qubit)) - { - Ok(error) => Some(error.extract::()?), - Err(_) => None, - }; - } - if let Some(inst_map) = &inst_map { - calibration = match inst_map - .call_method1("_get_calibration_entry", (&gate, qubit)) - { - Ok(calibration) => { - if dt.is_some() - && calibration.getattr("user_provided")?.extract::()? - { - duration = Some( - calibration - .call_method0("get_schedule")? - .getattr("duration")? - .extract::()? - * dt.unwrap_or_default(), - ); - } - Some(calibration) - } - Err(_) => None, - } - } - if let Some(instruction_durations) = &instruction_durations { - let kwargs = [("unit", "s")].into_py_dict_bound(py); - duration = match instruction_durations.call_method_bound( - py, - "get", - (&gate, qubit), - Some(&kwargs), - ) { - Ok(duration) => Some(duration.extract::(py)?), - Err(_) => None, - } - } - if error.is_none() && duration.is_none() && calibration.is_none() { - gate_properties - .insert(Some(smallvec![PhysicalQubit::new(qubit as u32)]), None); - } else { - gate_properties.insert( - Some(smallvec![PhysicalQubit::new(qubit as u32)]), - Some(Py::new( - py, - InstructionProperties::new(py, duration, error, calibration), - )?), - ); - } - } - target.add_instruction( - py, - name_mapping[&gate].bind(py), - Some(gate_properties), - Some(gate), - )?; - } - let edges = coupling_map - .call_method0(py, "get_edges")? - .extract::>(py)?; - for gate in two_qubit_gates { - let mut gate_properties: IndexMap< - Option, - Option>, - > = IndexMap::new(); - for edge in edges.as_slice().iter().cloned() { - let mut error: Option = None; - let mut duration: Option = None; - let mut calibration: Option> = None; - if let Some(backend_properties) = backend_properties { - if duration.is_none() { - duration = match backend_properties - .call_method1("gate_length", (&gate, edge)) - { - Ok(duration) => Some(duration.extract::()?), - Err(_) => None, - } - } - error = match backend_properties.call_method1("gate_error", (&gate, edge)) { - Ok(error) => Some(error.extract::()?), - Err(_) => None, - }; - } - if let Some(inst_map) = &inst_map { - calibration = match inst_map - .call_method1("_get_calibration_entry", (&gate, edge)) - { - Ok(calibration) => { - if dt.is_some() - && calibration.getattr("user_provided")?.extract::()? - { - duration = Some( - calibration - .call_method0("get_schedule")? - .getattr("duration")? - .extract::()? - * dt.unwrap_or_default(), - ); - } - Some(calibration) - } - Err(_) => None, - } - } - if let Some(instruction_durations) = &instruction_durations { - let kwargs = [("unit", "s")].into_py_dict_bound(py); - duration = match instruction_durations.call_method_bound( - py, - "get", - (&gate, edge), - Some(&kwargs), - ) { - Ok(duration) => Some(duration.extract::(py)?), - Err(_) => None, - } - } - if error.is_none() && duration.is_none() && calibration.is_none() { - gate_properties.insert( - Some(edge.into_iter().map(PhysicalQubit::new).collect()), - None, - ); - } else { - gate_properties.insert( - Some(edge.into_iter().map(PhysicalQubit::new).collect()), - Some(Py::new( - py, - InstructionProperties::new(py, duration, error, calibration), - )?), - ); - } - } - target.add_instruction( - py, - name_mapping[&gate].bind(py), - Some(gate_properties), - Some(gate), - )?; - } - for gate in global_ideal_variable_width_gates { - target.add_instruction(py, name_mapping[&gate].bind(py), None, Some(gate))?; - } - } else { - for gate in basis_gates { - if !name_mapping.contains_key(&gate) { - return Err(PyKeyError::new_err(format!( - "The specified basis gate: {gate} is not present in the standard gate \ - names or a provided custom_name_mapping" - ))); - } - target.add_instruction(py, name_mapping[&gate].bind(py), None, Some(gate))?; - } - } - Ok(target) - } - // Magic methods: - fn __iter__(slf: PyRef) -> PyResult> { - slf.gate_map.__iter__(slf.py()) - } - - fn __getitem__(&self, py: Python<'_>, key: String) -> PyResult> { - self.gate_map.__getitem__(py, key) - } - - #[pyo3(signature = (key, default=None))] - fn get( - &self, - py: Python<'_>, - key: String, - default: Option>, - ) -> PyResult { - match self.__getitem__(py, key) { - Ok(value) => Ok(value.into_py(py)), - Err(_) => Ok(match default { - Some(value) => value.into(), - None => py.None(), - }), - } - } - fn __len__(&self) -> PyResult { - Ok(self.gate_map.map.len()) - } - - fn __contains__(&self, item: &Bound) -> PyResult { - Ok(self.gate_map.__contains__(item)) + Ok(self.gate_map.len()) } fn __getstate__(&self, py: Python<'_>) -> PyResult> { @@ -2050,13 +1122,24 @@ impl Target { result_list.append(self.acquire_alignment)?; result_list.append(self.qubit_properties.clone())?; result_list.append(self.concurrent_measurements.clone())?; - result_list.append(self.gate_map.__getstate__())?; - result_list.append(self.gate_name_map.clone())?; + result_list.append( + self.gate_map + .clone() + .into_iter() + .map(|(key, value)| { + ( + key, + value + .into_iter() + .collect::, Option)>>(), + ) + }) + .collect::() + .into_py(py), + )?; + result_list.append(self._gate_name_map.clone())?; result_list.append(self.global_operations.clone())?; result_list.append(self.qarg_gate_map.clone().into_iter().collect_vec())?; - result_list.append(self.coupling_graph.clone())?; - result_list.append(self.instruction_durations.clone())?; - result_list.append(self.instruction_schedule_map.clone())?; result_list.append(self.non_global_basis.clone())?; result_list.append(self.non_global_strict_basis.clone())?; Ok(result_list.to_owned().unbind()) @@ -2072,12 +1155,14 @@ impl Target { self.acquire_alignment = state.get_item(6)?.extract::()?; self.qubit_properties = state.get_item(7)?.extract::>>()?; self.concurrent_measurements = state.get_item(8)?.extract::>>()?; - self.gate_map.__setstate__( + self.gate_map = IndexMap::from_iter( state .get_item(9)? - .extract::)>>()?, - )?; - self.gate_name_map = state + .extract::()? + .into_iter() + .map(|(name, prop_map)| (name, IndexMap::from_iter(prop_map.into_iter()))), + ); + self._gate_name_map = state .get_item(10)? .extract::>()?; self.global_operations = state @@ -2088,33 +1173,27 @@ impl Target { .get_item(12)? .extract::, Option>)>>()?, ); - self.coupling_graph = state.get_item(13)?.extract::>()?; - self.instruction_durations = state.get_item(14)?.extract::>()?; - self.instruction_schedule_map = state.get_item(15)?.extract::>()?; - self.non_global_basis = state.get_item(16)?.extract::>>()?; - self.non_global_strict_basis = state.get_item(17)?.extract::>>()?; + self.non_global_basis = state.get_item(13)?.extract::>>()?; + self.non_global_strict_basis = state.get_item(14)?.extract::>>()?; Ok(()) } - fn keys(&self) -> GateMapKeys { - self.gate_map.keys() + fn keys(&self) -> Vec { + self.gate_map.keys().cloned().collect() } - fn values(&self) -> Vec> { - self.gate_map.values() + fn values(&self) -> Vec { + self.gate_map.values().cloned().collect() } - fn items(&self) -> Vec<(String, Py)> { - self.gate_map.items() + fn items(&self) -> Vec<(String, PropsMap)> { + self.gate_map.clone().into_iter().collect_vec() } } #[pymodule] pub fn target(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_class::()?; + m.add_class::()?; 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 deleted file mode 100644 index b402122fc2c9..000000000000 --- a/crates/accelerate/src/target_transpiler/property_map.rs +++ /dev/null @@ -1,192 +0,0 @@ -// This code is part of Qiskit. -// -// (C) Copyright IBM 2024 -// -// This code is licensed under the Apache License, Version 2.0. You may -// obtain a copy of this license in the LICENSE.txt file in the root directory -// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -// -// Any modifications or derivative works of this code must retain this -// copyright notice, and modified files need to carry a notice indicating -// that they have been altered from the originals. - -use hashbrown::HashSet; -use indexmap::{set::IntoIter as IndexSetIntoIter, IndexMap, IndexSet}; -use itertools::Itertools; -use pyo3::types::{PyMapping, PySet}; -use pyo3::{exceptions::PyKeyError, prelude::*, pyclass}; -use smallvec::SmallVec; - -use crate::nlayout::PhysicalQubit; - -use super::instruction_properties::InstructionProperties; -use super::macro_rules::key_like_set_iterator; - -pub type Qargs = SmallVec<[PhysicalQubit; 4]>; -type KeyIterType = IndexSetIntoIter>; -pub type PropsMapItemsType = Vec<(Option, Option>)>; - -key_like_set_iterator!( - PropsMapKeys, - PropsMapIter, - keys, - Option, - KeyIterType, - "", - "props_map_keys" -); - -type PropsMapKV = IndexMap, Option>>; -/** - Mapping containing the properties of an instruction. Represents the relation - ``Qarg : InstructionProperties``. - - Made to directly avoid conversions from an ``IndexMap`` structure in rust to a Python dict. -*/ -#[pyclass(mapping, module = "qiskit._accelerate.target")] -#[derive(Debug, Clone)] -pub struct PropsMap { - pub map: PropsMapKV, -} - -#[pymethods] -impl PropsMap { - /// Create new instance of PropsMap. - /// - /// Args: - /// map (``dict[Qargs: InstructionProperties ]``): - /// mapping of optional ``Qargs`` and optional``InstructionProperties``. - #[new] - pub fn new(map: Option) -> Self { - match map { - Some(map) => PropsMap { map }, - None => PropsMap::default(), - } - } - - /// Check whether some qargs are part of this PropsMap - fn __contains__(&self, key: &Bound) -> bool { - if let Ok(key) = key.extract::>() { - self.map.contains_key(&key) - } else { - false - } - } - - /// Check whether the partial equality of two PropMaps. - /// - /// Partial equality is considered because ``InstructionProperties`` is non comparable. - fn __eq__(slf: PyRef, other: &Bound) -> PyResult { - if let Ok(dict) = other.downcast::() { - for key in dict.keys()?.iter()? { - if let Ok(qargs) = key?.extract::>() { - if !slf.map.contains_key(&qargs) { - return Ok(false); - } - } else { - return Ok(false); - } - } - Ok(dict.len()? == slf.map.len()) - } else if let Ok(prop_keys) = other.extract::() { - for key in prop_keys.map.keys() { - if !slf.map.contains_key(key) { - return Ok(false); - } - } - Ok(prop_keys.map.len() == slf.map.len()) - } else { - Ok(false) - } - } - - /// Access a value in the GateMap using a Key. - /// - /// Args: - /// key (``Qargs``): The instruction name key. - /// - /// Return: - /// ``InstructionProperties`` object at that slot. - /// Raises: - /// KeyError if the ``key`` is not in the ``PropsMap``. - fn __getitem__(&self, py: Python<'_>, key: Option) -> PyResult { - if let Some(item) = self.map.get(&key) { - Ok(item.to_object(py)) - } else { - Err(PyKeyError::new_err(format!( - "Key {:#?} not in target.", - key.unwrap_or_default() - ))) - } - } - - /// Access a value in the GateMap using a Key. - /// - /// Args: - /// key (str): The instruction name key. - /// default (PyAny): The default value to be returned. - /// - /// Returns: - /// ``PropsMap`` value if found, otherwise returns ``default``. - #[pyo3(signature = (key, default=None))] - fn get(&self, py: Python<'_>, key: Option, default: Option>) -> PyObject { - match self.__getitem__(py, key) { - Ok(value) => value, - Err(_) => match default { - Some(value) => value.into(), - None => py.None(), - }, - } - } - /// Returns the number of keys present - fn __len__(slf: PyRef) -> usize { - slf.map.len() - } - - /// Returns an iterator over the keys of the PropsMap. - fn __iter__(slf: PyRef) -> PyResult> { - let iter = PropsMapIter { - iter: slf - .map - .keys() - .cloned() - .collect::>>() - .into_iter(), - }; - Py::new(slf.py(), iter) - } - - /// Returns an ordered set with all the Keys in the PropsMap. - pub fn keys(&self) -> PropsMapKeys { - PropsMapKeys { - keys: self.map.keys().cloned().collect(), - } - } - - /// Returns a list with all the values in the PropsMap. - fn values(&self) -> Vec>> { - self.map.values().cloned().collect_vec() - } - - /// Returns a list with all they (key, value) pairs (``Qargs``, ``InstructionProperties``) - fn items(&self) -> PropsMapItemsType { - self.map.clone().into_iter().collect_vec() - } - - fn __setstate__(&mut self, state: PropsMapItemsType) -> PyResult<()> { - self.map = IndexMap::from_iter(state); - Ok(()) - } - - fn __getstate__(&self) -> PropsMapItemsType { - self.items() - } -} - -impl Default for PropsMap { - fn default() -> Self { - Self { - map: IndexMap::new(), - } - } -} diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index c3fe7257031e..ce3d273774a0 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -21,34 +21,47 @@ import itertools -from typing import Any +from typing import Optional, List, Any +from collections.abc import Mapping +from collections import defaultdict import datetime import io import logging +import inspect import rustworkx as rx +from qiskit.circuit.parameter import Parameter +from qiskit.circuit.parameterexpression import ParameterValueType +from qiskit.circuit.gate import Gate +from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping +from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap +from qiskit.pulse.calibration_entries import CalibrationEntry, ScheduleDef +from qiskit.pulse.schedule import Schedule, ScheduleBlock from qiskit.transpiler.coupling import CouplingMap -from qiskit.transpiler.instruction_durations import ( # pylint: disable=unused-import - InstructionDurations, -) -from qiskit.transpiler.timing_constraints import TimingConstraints # pylint: disable=unused-import +from qiskit.transpiler.exceptions import TranspilerError +from qiskit.transpiler.instruction_durations import InstructionDurations +from qiskit.transpiler.timing_constraints import TimingConstraints +from qiskit.providers.exceptions import BackendPropertyError +from qiskit.pulse.exceptions import PulseError, UnassignedDurationError +from qiskit.exceptions import QiskitError # import QubitProperties here to provide convenience alias for building a # full target from qiskit.providers.backend import QubitProperties # pylint: disable=unused-import from qiskit.providers.models.backendproperties import BackendProperties +logger = logging.getLogger(__name__) + + # import target class from the rust side from qiskit._accelerate.target import ( # pylint: disable=unused-import Target as Target2, - InstructionProperties as InstructionProperties2, + BaseInstructionProperties, ) -logger = logging.getLogger(__name__) - -class InstructionProperties(InstructionProperties2): +class InstructionProperties(BaseInstructionProperties): """A representation of the properties of a gate implementation. This class provides the optional properties that a backend can provide @@ -62,17 +75,16 @@ def __new__( # pylint: disable=keyword-arg-before-vararg cls, duration=None, # pylint: disable=keyword-arg-before-vararg error=None, # pylint: disable=keyword-arg-before-vararg - calibration=None, # pylint: disable=keyword-arg-before-vararg *args, # pylint: disable=unused-argument **kwargs, # pylint: disable=unused-argument ): - return super().__new__(cls, duration=duration, error=error, calibration=calibration) + return super(InstructionProperties, cls).__new__(cls, duration=duration, error=error) def __init__( self, - duration=None, # pylint: disable=unused-argument - error=None, # pylint: disable=unused-argument - calibration=None, # pylint: disable=unused-argument + duration: float | None = None, + error: float | None = None, + calibration: Schedule | ScheduleBlock | CalibrationEntry | None = None, ): """Create a new ``InstructionProperties`` object @@ -83,6 +95,50 @@ def __init__( set of qubits. calibration: The pulse representation of the instruction. """ + super().__init__() + self._calibration: CalibrationEntry | None = None + self.calibration = calibration + + @property + def calibration(self): + """The pulse representation of the instruction. + + .. note:: + + This attribute always returns a Qiskit pulse program, but it is internally + wrapped by the :class:`.CalibrationEntry` to manage unbound parameters + and to uniformly handle different data representation, + for example, un-parsed Pulse Qobj JSON that a backend provider may provide. + + This value can be overridden through the property setter in following manner. + When you set either :class:`.Schedule` or :class:`.ScheduleBlock` this is + always treated as a user-defined (custom) calibration and + the transpiler may automatically attach the calibration data to the output circuit. + This calibration data may appear in the wire format as an inline calibration, + which may further update the backend standard instruction set architecture. + + If you are a backend provider who provides a default calibration data + that is not needed to be attached to the transpiled quantum circuit, + you can directly set :class:`.CalibrationEntry` instance to this attribute, + in which you should set :code:`user_provided=False` when you define + calibration data for the entry. End users can still intentionally utilize + the calibration data, for example, to run pulse-level simulation of the circuit. + However, such entry doesn't appear in the wire format, and backend must + use own definition to compile the circuit down to the execution format. + + """ + if self._calibration is None: + return None + return self._calibration.get_schedule() + + @calibration.setter + def calibration(self, calibration: Schedule | ScheduleBlock | CalibrationEntry): + if isinstance(calibration, (Schedule, ScheduleBlock)): + new_entry = ScheduleDef() + new_entry.define(calibration, user_provided=True) + else: + new_entry = calibration + self._calibration = new_entry def __repr__(self): return ( @@ -186,6 +242,8 @@ def __new__( acquire_alignment: int = 1, qubit_properties: list | None = None, concurrent_measurements: list | None = None, + *args, # pylint: disable=unused-argument + **kwargs, # pylint: disable=unused-argument ): """ Create a new ``Target`` object @@ -251,38 +309,462 @@ def __new__( concurrent_measurements=concurrent_measurements, ) + def __init__( + self, + description=None, # pylint: disable=unused-argument + num_qubits=0, # pylint: disable=unused-argument + dt=None, # pylint: disable=unused-argument + granularity=1, # pylint: disable=unused-argument + min_length=1, # pylint: disable=unused-argument + pulse_alignment=1, # pylint: disable=unused-argument + acquire_alignment=1, # pylint: disable=unused-argument + qubit_properties=None, # pylint: disable=unused-argument + concurrent_measurements=None, # pylint: disable=unused-argument + ): + # A nested mapping of gate name -> qargs -> properties + self._gate_map = {} + self._coupling_graph = None + self._instruction_durations = None + self._instruction_schedule_map = None + + def add_instruction(self, instruction, properties=None, name=None): + """Add a new instruction to the :class:`~qiskit.transpiler.Target` + + As ``Target`` objects are strictly additive this is the primary method + for modifying a ``Target``. Typically, you will use this to fully populate + a ``Target`` before using it in :class:`~qiskit.providers.BackendV2`. For + example:: + + from qiskit.circuit.library import CXGate + from qiskit.transpiler import Target, InstructionProperties + + target = Target() + cx_properties = { + (0, 1): None, + (1, 0): None, + (0, 2): None, + (2, 0): None, + (0, 3): None, + (2, 3): None, + (3, 0): None, + (3, 2): None + } + target.add_instruction(CXGate(), cx_properties) + + Will add a :class:`~qiskit.circuit.library.CXGate` to the target with no + properties (duration, error, etc) with the coupling edge list: + ``(0, 1), (1, 0), (0, 2), (2, 0), (0, 3), (2, 3), (3, 0), (3, 2)``. If + there are properties available for the instruction you can replace the + ``None`` value in the properties dictionary with an + :class:`~qiskit.transpiler.InstructionProperties` object. This pattern + is repeated for each :class:`~qiskit.circuit.Instruction` the target + supports. + + Args: + instruction (Union[qiskit.circuit.Instruction, Type[qiskit.circuit.Instruction]]): + The operation object to add to the map. If it's parameterized any value + of the parameter can be set. Optionally for variable width + instructions (such as control flow operations such as :class:`~.ForLoop` or + :class:`~MCXGate`) you can specify the class. If the class is specified than the + ``name`` argument must be specified. When a class is used the gate is treated as global + and not having any properties set. + properties (dict): A dictionary of qarg entries to an + :class:`~qiskit.transpiler.InstructionProperties` object for that + instruction implementation on the backend. Properties are optional + for any instruction implementation, if there are no + :class:`~qiskit.transpiler.InstructionProperties` available for the + backend the value can be None. If there are no constraints on the + instruction (as in a noiseless/ideal simulation) this can be set to + ``{None, None}`` which will indicate it runs on all qubits (or all + available permutations of qubits for multi-qubit gates). The first + ``None`` indicates it applies to all qubits and the second ``None`` + indicates there are no + :class:`~qiskit.transpiler.InstructionProperties` for the + instruction. By default, if properties is not set it is equivalent to + passing ``{None: None}``. + name (str): An optional name to use for identifying the instruction. If not + specified the :attr:`~qiskit.circuit.Instruction.name` attribute + of ``gate`` will be used. All gates in the ``Target`` need unique + names. Backends can differentiate between different + parameterization of a single gate by providing a unique name for + each (e.g. `"rx30"`, `"rx60", ``"rx90"`` similar to the example in the + documentation for the :class:`~qiskit.transpiler.Target` class). + Raises: + AttributeError: If gate is already in map + TranspilerError: If an operation class is passed in for ``instruction`` and no name + is specified or ``properties`` is set. + """ + is_class = inspect.isclass(instruction) + if not is_class: + instruction_name = name or instruction.name + else: + # Invalid to have class input without a name with characters set "" is not a valid name + if not name: + raise TranspilerError( + "A name must be specified when defining a supported global operation by class" + ) + if properties is not None: + raise TranspilerError( + "An instruction added globally by class can't have properties set." + ) + instruction_name = name + if properties is None or is_class: + properties = {None: None} + if instruction_name in self._gate_map: + raise AttributeError("Instruction %s is already in the target" % instruction_name) + super().add_instruction(instruction, instruction_name, is_class, properties) + self._gate_map[instruction_name] = properties + self._coupling_graph = None + self._instruction_durations = None + self._instruction_schedule_map = None + + def update_instruction_properties(self, instruction, qargs, properties): + """Update the property object for an instruction qarg pair already in the Target + + Args: + instruction (str): The instruction name to update + qargs (tuple): The qargs to update the properties of + properties (InstructionProperties): The properties to set for this instruction + Raises: + KeyError: If ``instruction`` or ``qarg`` are not in the target + """ + super().update_instruction_properties(instruction, qargs, properties) + self._gate_map[instruction][qargs] = properties + self._instruction_durations = None + self._instruction_schedule_map = None + + def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, error_dict=None): + """Update the target from an instruction schedule map. + + If the input instruction schedule map contains new instructions not in + the target they will be added. However, if it contains additional qargs + for an existing instruction in the target it will error. + + Args: + inst_map (InstructionScheduleMap): The instruction + inst_name_map (dict): An optional dictionary that maps any + instruction name in ``inst_map`` to an instruction object. + If not provided, instruction is pulled from the standard Qiskit gates, + and finally custom gate instance is created with schedule name. + error_dict (dict): A dictionary of errors of the form:: + + {gate_name: {qarg: error}} + + for example:: + + {'rx': {(0, ): 1.4e-4, (1, ): 1.2e-4}} + + For each entry in the ``inst_map`` if ``error_dict`` is defined + a when updating the ``Target`` the error value will be pulled from + this dictionary. If one is not found in ``error_dict`` then + ``None`` will be used. + """ + get_calibration = getattr(inst_map, "_get_calibration_entry") + + # Expand name mapping with custom gate name provided by user. + qiskit_inst_name_map = get_standard_gate_name_mapping() + if inst_name_map is not None: + qiskit_inst_name_map.update(inst_name_map) + + for inst_name in inst_map.instructions: + # Prepare dictionary of instruction properties + out_props = {} + for qargs in inst_map.qubits_with_instruction(inst_name): + try: + qargs = tuple(qargs) + except TypeError: + qargs = (qargs,) + try: + props = self._gate_map[inst_name][qargs] + except (KeyError, TypeError): + props = None + + entry = get_calibration(inst_name, qargs) + if entry.user_provided and getattr(props, "_calibration", None) != entry: + # It only copies user-provided calibration from the inst map. + # Backend defined entry must already exist in Target. + if self.dt is not None: + try: + duration = entry.get_schedule().duration * self.dt + except UnassignedDurationError: + # duration of schedule is parameterized + duration = None + else: + duration = None + props = InstructionProperties( + duration=duration, + calibration=entry, + ) + else: + if props is None: + # Edge case. Calibration is backend defined, but this is not + # registered in the backend target. Ignore this entry. + continue + try: + # Update gate error if provided. + props.error = error_dict[inst_name][qargs] + except (KeyError, TypeError): + pass + out_props[qargs] = props + if not out_props: + continue + # Prepare Qiskit Gate object assigned to the entries + if inst_name not in self._gate_map: + # Entry not found: Add new instruction + if inst_name in qiskit_inst_name_map: + # Remove qargs with length that doesn't match with instruction qubit number + inst_obj = qiskit_inst_name_map[inst_name] + normalized_props = {} + for qargs, prop in out_props.items(): + if len(qargs) != inst_obj.num_qubits: + continue + normalized_props[qargs] = prop + self.add_instruction(inst_obj, normalized_props, name=inst_name) + else: + # Check qubit length parameter name uniformity. + qlen = set() + param_names = set() + for qargs in inst_map.qubits_with_instruction(inst_name): + if isinstance(qargs, int): + qargs = (qargs,) + qlen.add(len(qargs)) + cal = getattr(out_props[tuple(qargs)], "_calibration") + param_names.add(tuple(cal.get_signature().parameters.keys())) + if len(qlen) > 1 or len(param_names) > 1: + raise QiskitError( + f"Schedules for {inst_name} are defined non-uniformly for " + f"multiple qubit lengths {qlen}, " + f"or different parameter names {param_names}. " + "Provide these schedules with inst_name_map or define them with " + "different names for different gate parameters." + ) + inst_obj = Gate( + name=inst_name, + num_qubits=next(iter(qlen)), + params=list(map(Parameter, next(iter(param_names)))), + ) + self.add_instruction(inst_obj, out_props, name=inst_name) + else: + # Entry found: Update "existing" instructions. + for qargs, prop in out_props.items(): + if qargs not in self._gate_map[inst_name]: + continue + self.update_instruction_properties(inst_name, qargs, prop) + + @property + def qargs(self): + """The set of qargs in the target.""" + qargs = super().qargs + print(qargs) + if qargs is None: + return None + qargs = set(tuple(qarg) for qarg in qargs) + return qargs + + def qargs_for_operation_name(self, operation): + """Get the qargs for a given operation name + + Args: + operation (str): The operation name to get qargs for + Returns: + set: The set of qargs the gate instance applies to. + """ + if None in self._gate_map[operation]: + return None + return self._gate_map[operation].keys() + + def durations(self): + """Get an InstructionDurations object from the target + + Returns: + InstructionDurations: The instruction duration represented in the + target + """ + if self._instruction_durations is not None: + return self._instruction_durations + out_durations = [] + for instruction, props_map in self._gate_map.items(): + for qarg, properties in props_map.items(): + if properties is not None and properties.duration is not None: + out_durations.append((instruction, list(qarg), properties.duration, "s")) + self._instruction_durations = InstructionDurations(out_durations, dt=self.dt) + return self._instruction_durations + + def timing_constraints(self): + """Get an :class:`~qiskit.transpiler.TimingConstraints` object from the target + + Returns: + TimingConstraints: The timing constraints represented in the ``Target`` + """ + return TimingConstraints( + self.granularity, self.min_length, self.pulse_alignment, self.acquire_alignment + ) + + def instruction_schedule_map(self): + """Return an :class:`~qiskit.pulse.InstructionScheduleMap` for the + instructions in the target with a pulse schedule defined. + + Returns: + InstructionScheduleMap: The instruction schedule map for the + instructions in this target with a pulse schedule defined. + """ + if self._instruction_schedule_map is not None: + return self._instruction_schedule_map + out_inst_schedule_map = InstructionScheduleMap() + for instruction, qargs in self._gate_map.items(): + for qarg, properties in qargs.items(): + # Directly getting CalibrationEntry not to invoke .get_schedule(). + # This keeps PulseQobjDef un-parsed. + cal_entry = getattr(properties, "_calibration", None) + if cal_entry is not None: + # Use fast-path to add entries to the inst map. + out_inst_schedule_map._add(instruction, qarg, cal_entry) + self._instruction_schedule_map = out_inst_schedule_map + return out_inst_schedule_map + + def has_calibration( + self, + operation_name: str, + qargs: tuple[int, ...], + ) -> bool: + """Return whether the instruction (operation + qubits) defines a calibration. + + Args: + operation_name: The name of the operation for the instruction. + qargs: The tuple of qubit indices for the instruction. + + Returns: + Returns ``True`` if the calibration is supported and ``False`` if it isn't. + """ + qargs = tuple(qargs) + if operation_name not in self._gate_map: + return False + if qargs not in self._gate_map[operation_name]: + return False + return getattr(self._gate_map[operation_name][qargs], "_calibration") is not None + + def get_calibration( + self, + operation_name: str, + qargs: tuple[int, ...], + *args: ParameterValueType, + **kwargs: ParameterValueType, + ) -> Schedule | ScheduleBlock: + """Get calibrated pulse schedule for the instruction. + + If calibration is templated with parameters, one can also provide those values + to build a schedule with assigned parameters. + + Args: + operation_name: The name of the operation for the instruction. + qargs: The tuple of qubit indices for the instruction. + args: Parameter values to build schedule if any. + kwargs: Parameter values with name to build schedule if any. + + Returns: + Calibrated pulse schedule of corresponding instruction. + """ + if not self.has_calibration(operation_name, qargs): + raise KeyError( + f"Calibration of instruction {operation_name} for qubit {qargs} is not defined." + ) + cal_entry = getattr(self._gate_map[operation_name][qargs], "_calibration") + return cal_entry.get_schedule(*args, **kwargs) + + @property + def operation_names(self): + """Get the operation names in the target.""" + return self._gate_map.keys() + + @property + def instructions(self): + """Get the list of tuples ``(:class:`~qiskit.circuit.Instruction`, (qargs))`` + for the target + + For globally defined variable width operations the tuple will be of the form + ``(class, None)`` where class is the actual operation class that + is globally defined. + """ + return [ + (self._gate_name_map[op], qarg) + for op, qargs in self._gate_map.items() + for qarg in qargs + ] + + def instruction_properties(self, index): + """Get the instruction properties for a specific instruction tuple + + This method is to be used in conjunction with the + :attr:`~qiskit.transpiler.Target.instructions` attribute of a + :class:`~qiskit.transpiler.Target` object. You can use this method to quickly + get the instruction properties for an element of + :attr:`~qiskit.transpiler.Target.instructions` by using the index in that list. + However, if you're not working with :attr:`~qiskit.transpiler.Target.instructions` + directly it is likely more efficient to access the target directly via the name + and qubits to get the instruction properties. For example, if + :attr:`~qiskit.transpiler.Target.instructions` returned:: + + [(XGate(), (0,)), (XGate(), (1,))] + + you could get the properties of the ``XGate`` on qubit 1 with:: + + props = target.instruction_properties(1) + + but just accessing it directly via the name would be more efficient:: + + props = target['x'][(1,)] + + (assuming the ``XGate``'s canonical name in the target is ``'x'``) + This is especially true for larger targets as this will scale worse with the number + of instruction tuples in a target. + + Args: + index (int): The index of the instruction tuple from the + :attr:`~qiskit.transpiler.Target.instructions` attribute. For, example + if you want the properties from the third element in + :attr:`~qiskit.transpiler.Target.instructions` you would set this to be ``2``. + Returns: + InstructionProperties: The instruction properties for the specified instruction tuple + """ + instruction_properties = [ + inst_props for qargs in self._gate_map.values() for inst_props in qargs.values() + ] + return instruction_properties[index] + def _build_coupling_graph(self): - self.coupling_graph = rx.PyDiGraph( # pylint: disable=attribute-defined-outside-init + self._coupling_graph = rx.PyDiGraph( # pylint: disable=attribute-defined-outside-init multigraph=False ) - self.coupling_graph.add_nodes_from([{} for _ in range(self.num_qubits)]) + self._coupling_graph.add_nodes_from([{} for _ in range(self.num_qubits)]) for gate, qarg_map in self.items(): if qarg_map is None: - if self.gate_name_map[gate].num_qubits == 2: - self.coupling_graph = None # pylint: disable=attribute-defined-outside-init + if self._gate_name_map[gate].num_qubits == 2: + self._coupling_graph = None # pylint: disable=attribute-defined-outside-init return continue for qarg, properties in qarg_map.items(): if qarg is None: if self.operation_from_name(gate).num_qubits == 2: - self.coupling_graph = None # pylint: disable=attribute-defined-outside-init + self._coupling_graph = ( + None # pylint: disable=attribute-defined-outside-init + ) return continue if len(qarg) == 1: - self.coupling_graph[qarg[0]] = ( + self._coupling_graph[qarg[0]] = ( properties # pylint: disable=attribute-defined-outside-init ) elif len(qarg) == 2: try: - edge_data = self.coupling_graph.get_edge_data(*qarg) + edge_data = self._coupling_graph.get_edge_data(*qarg) edge_data[gate] = properties except rx.NoEdgeBetweenNodes: - self.coupling_graph.add_edge(*qarg, {gate: properties}) + self._coupling_graph.add_edge(*qarg, {gate: properties}) qargs = self.qargs - if self.coupling_graph.num_edges() == 0 and ( + if self._coupling_graph.num_edges() == 0 and ( qargs is None or any(x is None for x in qargs) ): - self.coupling_graph = None # pylint: disable=attribute-defined-outside-init + self._coupling_graph = None # pylint: disable=attribute-defined-outside-init def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): """Get a :class:`~qiskit.transpiler.CouplingMap` from this target. @@ -338,28 +820,49 @@ def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): cmap = CouplingMap() cmap.graph = coupling_graph return cmap - if self.coupling_graph is None: + if self._coupling_graph is None: self._build_coupling_graph() # if there is no connectivity constraints in the coupling graph treat it as not # existing and return - if self.coupling_graph is not None: + if self._coupling_graph is not None: cmap = CouplingMap() if filter_idle_qubits: cmap.graph = self._filter_coupling_graph() else: - cmap.graph = self.coupling_graph.copy() + cmap.graph = self._coupling_graph.copy() return cmap else: return None def _filter_coupling_graph(self): has_operations = set(itertools.chain.from_iterable(x for x in self.qargs if x is not None)) - graph = self.coupling_graph.copy() + graph = self._coupling_graph.copy() to_remove = set(graph.node_indices()).difference(has_operations) if to_remove: graph.remove_nodes_from(list(to_remove)) return graph + def __iter__(self): + return iter(self._gate_map) + + def __getitem__(self, key): + return self._gate_map[key] + + def __len__(self): + return len(self._gate_map) + + def __contains__(self, item): + return item in self._gate_map + + def keys(self): + return self._gate_map.keys() + + def values(self): + return self._gate_map.values() + + def items(self): + return self._gate_map.items() + def __str__(self): output = io.StringIO() if self.description is not None: @@ -368,23 +871,21 @@ def __str__(self): output.write("Target\n") output.write(f"Number of qubits: {self.num_qubits}\n") output.write("Instructions:\n") - for inst, qarg_props in super().items(): + for inst, qarg_props in self._gate_map.items(): output.write(f"\t{inst}\n") for qarg, props in qarg_props.items(): if qarg is None: continue if props is None: - output.write(f"\t\t{tuple(qarg)}\n") + output.write(f"\t\t{qarg}\n") continue - prop_str_pieces = [f"\t\t{tuple(qarg)}:\n"] + prop_str_pieces = [f"\t\t{qarg}:\n"] duration = getattr(props, "duration", None) if duration is not None: - prop_str_pieces.append( - f"\t\t\tDuration: {0 if duration == 0 else duration} sec.\n" - ) + prop_str_pieces.append(f"\t\t\tDuration: {duration if duration > 0 else 0} sec.\n") error = getattr(props, "error", None) if error is not None: - prop_str_pieces.append(f"\t\t\tError Rate: {0 if error == 0 else error}\n") + prop_str_pieces.append(f"\t\t\tError Rate: {error if error > 0 else 0}\n") schedule = getattr(props, "_calibration", None) if schedule is not None: prop_str_pieces.append("\t\t\tWith pulse schedule calibration\n") @@ -398,6 +899,258 @@ def __str__(self): output.write("".join(prop_str_pieces)) return output.getvalue() + def __getstate__(self) -> tuple: + return ( + self._gate_map, + self._coupling_graph, + self._instruction_durations, + self._instruction_schedule_map, + super().__getstate__(), + ) + + def __setstate__(self, state: tuple): + self._gate_map = state[0] + self._coupling_graph = state[1] + self._instruction_durations = state[2] + self._instruction_schedule_map = state[3] + super().__setstate__(state[4]) + + @classmethod + def from_configuration( + cls, + basis_gates: list[str], + num_qubits: int | None = None, + coupling_map: CouplingMap | None = None, + inst_map: InstructionScheduleMap | None = None, + backend_properties: BackendProperties | None = None, + instruction_durations: InstructionDurations | None = None, + concurrent_measurements: Optional[List[List[int]]] = None, + dt: float | None = None, + timing_constraints: TimingConstraints | None = None, + custom_name_mapping: dict[str, Any] | None = None, + ) -> Target: + """Create a target object from the individual global configuration + + Prior to the creation of the :class:`~.Target` class, the constraints + of a backend were represented by a collection of different objects + which combined represent a subset of the information contained in + the :class:`~.Target`. This function provides a simple interface + to convert those separate objects to a :class:`~.Target`. + + This constructor will use the input from ``basis_gates``, ``num_qubits``, + and ``coupling_map`` to build a base model of the backend and the + ``instruction_durations``, ``backend_properties``, and ``inst_map`` inputs + are then queried (in that order) based on that model to look up the properties + of each instruction and qubit. If there is an inconsistency between the inputs + any extra or conflicting information present in ``instruction_durations``, + ``backend_properties``, or ``inst_map`` will be ignored. + + Args: + basis_gates: The list of basis gate names for the backend. For the + target to be created these names must either be in the output + from :func:`~.get_standard_gate_name_mapping` or present in the + specified ``custom_name_mapping`` argument. + num_qubits: The number of qubits supported on the backend. + coupling_map: The coupling map representing connectivity constraints + on the backend. If specified all gates from ``basis_gates`` will + be supported on all qubits (or pairs of qubits). + inst_map: The instruction schedule map representing the pulse + :class:`~.Schedule` definitions for each instruction. If this + is specified ``coupling_map`` must be specified. The + ``coupling_map`` is used as the source of truth for connectivity + and if ``inst_map`` is used the schedule is looked up based + on the instructions from the pair of ``basis_gates`` and + ``coupling_map``. If you want to define a custom gate for + a particular qubit or qubit pair, you can manually build :class:`.Target`. + backend_properties: The :class:`~.BackendProperties` object which is + used for instruction properties and qubit properties. + If specified and instruction properties are intended to be used + then the ``coupling_map`` argument must be specified. This is + only used to lookup error rates and durations (unless + ``instruction_durations`` is specified which would take + precedence) for instructions specified via ``coupling_map`` and + ``basis_gates``. + instruction_durations: Optional instruction durations for instructions. If specified + it will take priority for setting the ``duration`` field in the + :class:`~InstructionProperties` objects for the instructions in the target. + concurrent_measurements(list): A list of sets of qubits that must be + measured together. This must be provided + as a nested list like ``[[0, 1], [2, 3, 4]]``. + dt: The system time resolution of input signals in seconds + timing_constraints: Optional timing constraints to include in the + :class:`~.Target` + custom_name_mapping: An optional dictionary that maps custom gate/operation names in + ``basis_gates`` to an :class:`~.Operation` object representing that + gate/operation. By default, most standard gates names are mapped to the + standard gate object from :mod:`qiskit.circuit.library` this only needs + to be specified if the input ``basis_gates`` defines gates in names outside + that set. + + Returns: + Target: the target built from the input configuration + + Raises: + TranspilerError: If the input basis gates contain > 2 qubits and ``coupling_map`` is + specified. + KeyError: If no mapping is available for a specified ``basis_gate``. + """ + granularity = 1 + min_length = 1 + pulse_alignment = 1 + acquire_alignment = 1 + if timing_constraints is not None: + granularity = timing_constraints.granularity + min_length = timing_constraints.min_length + pulse_alignment = timing_constraints.pulse_alignment + acquire_alignment = timing_constraints.acquire_alignment + + qubit_properties = None + if backend_properties is not None: + # pylint: disable=cyclic-import + from qiskit.providers.backend_compat import qubit_props_list_from_props + + qubit_properties = qubit_props_list_from_props(properties=backend_properties) + + target = cls( + num_qubits=num_qubits, + dt=dt, + granularity=granularity, + min_length=min_length, + pulse_alignment=pulse_alignment, + acquire_alignment=acquire_alignment, + qubit_properties=qubit_properties, + concurrent_measurements=concurrent_measurements, + ) + name_mapping = get_standard_gate_name_mapping() + if custom_name_mapping is not None: + name_mapping.update(custom_name_mapping) + + # While BackendProperties can also contain coupling information we + # rely solely on CouplingMap to determine connectivity. This is because + # in legacy transpiler usage (and implicitly in the BackendV1 data model) + # the coupling map is used to define connectivity constraints and + # the properties is only used for error rate and duration population. + # If coupling map is not specified we ignore the backend_properties + if coupling_map is None: + for gate in basis_gates: + if gate not in name_mapping: + raise KeyError( + f"The specified basis gate: {gate} is not present in the standard gate " + "names or a provided custom_name_mapping" + ) + target.add_instruction(name_mapping[gate], name=gate) + else: + one_qubit_gates = [] + two_qubit_gates = [] + global_ideal_variable_width_gates = [] # pylint: disable=invalid-name + if num_qubits is None: + num_qubits = len(coupling_map.graph) + for gate in basis_gates: + if gate not in name_mapping: + raise KeyError( + f"The specified basis gate: {gate} is not present in the standard gate " + "names or a provided custom_name_mapping" + ) + gate_obj = name_mapping[gate] + if gate_obj.num_qubits == 1: + one_qubit_gates.append(gate) + elif gate_obj.num_qubits == 2: + two_qubit_gates.append(gate) + elif inspect.isclass(gate_obj): + global_ideal_variable_width_gates.append(gate) + else: + raise TranspilerError( + f"The specified basis gate: {gate} has {gate_obj.num_qubits} " + "qubits. This constructor method only supports fixed width operations " + "with <= 2 qubits (because connectivity is defined on a CouplingMap)." + ) + for gate in one_qubit_gates: + gate_properties: dict[tuple, InstructionProperties] = {} + for qubit in range(num_qubits): + error = None + duration = None + calibration = None + if backend_properties is not None: + if duration is None: + try: + duration = backend_properties.gate_length(gate, qubit) + except BackendPropertyError: + duration = None + try: + error = backend_properties.gate_error(gate, qubit) + except BackendPropertyError: + error = None + if inst_map is not None: + try: + calibration = inst_map._get_calibration_entry(gate, qubit) + # If we have dt defined and there is a custom calibration which is user + # generate use that custom pulse schedule for the duration. If it is + # not user generated than we assume it's the same duration as what is + # defined in the backend properties + if dt and calibration.user_provided: + duration = calibration.get_schedule().duration * dt + except PulseError: + calibration = None + # Durations if specified manually should override model objects + if instruction_durations is not None: + try: + duration = instruction_durations.get(gate, qubit, unit="s") + except TranspilerError: + duration = None + + if error is None and duration is None and calibration is None: + gate_properties[(qubit,)] = None + else: + gate_properties[(qubit,)] = InstructionProperties( + duration=duration, error=error, calibration=calibration + ) + target.add_instruction(name_mapping[gate], properties=gate_properties, name=gate) + edges = list(coupling_map.get_edges()) + for gate in two_qubit_gates: + gate_properties = {} + for edge in edges: + error = None + duration = None + calibration = None + if backend_properties is not None: + if duration is None: + try: + duration = backend_properties.gate_length(gate, edge) + except BackendPropertyError: + duration = None + try: + error = backend_properties.gate_error(gate, edge) + except BackendPropertyError: + error = None + if inst_map is not None: + try: + calibration = inst_map._get_calibration_entry(gate, edge) + # If we have dt defined and there is a custom calibration which is user + # generate use that custom pulse schedule for the duration. If it is + # not user generated than we assume it's the same duration as what is + # defined in the backend properties + if dt and calibration.user_provided: + duration = calibration.get_schedule().duration * dt + except PulseError: + calibration = None + # Durations if specified manually should override model objects + if instruction_durations is not None: + try: + duration = instruction_durations.get(gate, edge, unit="s") + except TranspilerError: + duration = None + + if error is None and duration is None and calibration is None: + gate_properties[edge] = None + else: + gate_properties[edge] = InstructionProperties( + duration=duration, error=error, calibration=calibration + ) + target.add_instruction(name_mapping[gate], properties=gate_properties, name=gate) + for gate in global_ideal_variable_width_gates: + target.add_instruction(name_mapping[gate], name=gate) + return target + def target_to_backend_properties(target: Target): """Convert a :class:`~.Target` object into a legacy :class:`~.BackendProperties`"""